diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java
index e6aead6f8..baa472de1 100644
--- a/java/google/registry/config/RegistryConfig.java
+++ b/java/google/registry/config/RegistryConfig.java
@@ -23,6 +23,7 @@ import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.net.HostAndPort;
import dagger.Module;
import dagger.Provides;
@@ -904,6 +905,44 @@ public final class RegistryConfig {
return CONFIG_SETTINGS.get();
}
+ /**
+ * Provides the OAuth scopes to check for on access tokens.
+ *
+ *
This list should be a superset of the required OAuth scope set provided below.
+ *
+ *
If we feel the need, we could define additional fixed scopes, similar to the Java remote
+ * API, which requires at least one of:
+ *
+ *
provideAvailableOauthScopes() {
+ return ImmutableSet.of("https://www.googleapis.com/auth/userinfo.email");
+ }
+
+ /**
+ * Provides the required OAuth scopes for simply authenticating.
+ *
+ * This set contains the scopes which must be present to authenticate a user. It should be a
+ * subset of the scopes we request from the OAuth interface, provided above.
+ */
+ @Provides
+ @Config("requiredOauthScopes")
+ public static ImmutableSet provideRequiredOauthScopes() {
+ return ImmutableSet.of("https://www.googleapis.com/auth/userinfo.email");
+ }
+
+ /** Provides the allowed OAuth client IDs (could be multibinding). */
+ @Provides
+ @Config("allowedOauthClientIds")
+ public static ImmutableSet provideAllowedOauthClientIds() {
+ return ImmutableSet.of("PUT.YOUR.PROXY.CLIENT.ID.HERE", "PUT.YOUR.REGTOOL.CLIENT.ID.HERE");
+ }
+
/**
* Returns the help path for the RDAP terms of service.
*
diff --git a/java/google/registry/module/backend/BUILD b/java/google/registry/module/backend/BUILD
index f720e7251..b332a4a1c 100644
--- a/java/google/registry/module/backend/BUILD
+++ b/java/google/registry/module/backend/BUILD
@@ -31,6 +31,7 @@ java_library(
"//java/google/registry/rde/imports",
"//java/google/registry/request",
"//java/google/registry/request:modules",
+ "//java/google/registry/request/auth",
"//java/google/registry/tmch",
"//java/google/registry/util",
"@com_google_appengine_api_1_0_sdk",
diff --git a/java/google/registry/module/backend/BackendComponent.java b/java/google/registry/module/backend/BackendComponent.java
index 1c84ffaeb..437e9cab5 100644
--- a/java/google/registry/module/backend/BackendComponent.java
+++ b/java/google/registry/module/backend/BackendComponent.java
@@ -39,6 +39,7 @@ import google.registry.request.Modules.URLFetchServiceModule;
import google.registry.request.Modules.UrlFetchTransportModule;
import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule;
import google.registry.request.Modules.UserServiceModule;
+import google.registry.request.auth.AuthModule;
import google.registry.util.SystemClock.SystemClockModule;
import google.registry.util.SystemSleeper.SystemSleeperModule;
import javax.inject.Singleton;
@@ -48,6 +49,7 @@ import javax.inject.Singleton;
@Component(
modules = {
AppIdentityCredentialModule.class,
+ AuthModule.class,
BackendRequestComponentModule.class,
BigqueryModule.class,
ConfigModule.class,
diff --git a/java/google/registry/module/backend/BackendRequestHandler.java b/java/google/registry/module/backend/BackendRequestHandler.java
index b1ec15d35..c6b9036cc 100644
--- a/java/google/registry/module/backend/BackendRequestHandler.java
+++ b/java/google/registry/module/backend/BackendRequestHandler.java
@@ -16,6 +16,7 @@ package google.registry.module.backend;
import com.google.appengine.api.users.UserService;
import google.registry.request.RequestHandler;
+import google.registry.request.auth.RequestAuthenticator;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -25,7 +26,8 @@ public class BackendRequestHandler
@Inject BackendRequestHandler(
Provider componentBuilderProvider,
- UserService userService) {
- super(componentBuilderProvider, userService);
+ UserService userService,
+ RequestAuthenticator requestAuthenticator) {
+ super(componentBuilderProvider, userService, requestAuthenticator);
}
}
diff --git a/java/google/registry/module/frontend/BUILD b/java/google/registry/module/frontend/BUILD
index 5f7fd33b7..d10807a80 100644
--- a/java/google/registry/module/frontend/BUILD
+++ b/java/google/registry/module/frontend/BUILD
@@ -18,6 +18,7 @@ java_library(
"//java/google/registry/rdap",
"//java/google/registry/request",
"//java/google/registry/request:modules",
+ "//java/google/registry/request/auth",
"//java/google/registry/ui",
"//java/google/registry/ui/server/registrar",
"//java/google/registry/util",
diff --git a/java/google/registry/module/frontend/FrontendComponent.java b/java/google/registry/module/frontend/FrontendComponent.java
index b801fd3d5..8bcf820fa 100644
--- a/java/google/registry/module/frontend/FrontendComponent.java
+++ b/java/google/registry/module/frontend/FrontendComponent.java
@@ -29,6 +29,7 @@ import google.registry.request.Modules.ModulesServiceModule;
import google.registry.request.Modules.UrlFetchTransportModule;
import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule;
import google.registry.request.Modules.UserServiceModule;
+import google.registry.request.auth.AuthModule;
import google.registry.ui.ConsoleConfigModule;
import google.registry.util.SystemClock.SystemClockModule;
import google.registry.util.SystemSleeper.SystemSleeperModule;
@@ -39,6 +40,7 @@ import javax.inject.Singleton;
@Component(
modules = {
AppIdentityCredentialModule.class,
+ AuthModule.class,
BraintreeModule.class,
ConfigModule.class,
ConsoleConfigModule.class,
diff --git a/java/google/registry/module/frontend/FrontendRequestHandler.java b/java/google/registry/module/frontend/FrontendRequestHandler.java
index 253cf3880..3e00fa63f 100644
--- a/java/google/registry/module/frontend/FrontendRequestHandler.java
+++ b/java/google/registry/module/frontend/FrontendRequestHandler.java
@@ -16,6 +16,7 @@ package google.registry.module.frontend;
import com.google.appengine.api.users.UserService;
import google.registry.request.RequestHandler;
+import google.registry.request.auth.RequestAuthenticator;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -25,7 +26,8 @@ public class FrontendRequestHandler
@Inject FrontendRequestHandler(
Provider componentBuilderProvider,
- UserService userService) {
- super(componentBuilderProvider, userService);
+ UserService userService,
+ RequestAuthenticator requestAuthenticator) {
+ super(componentBuilderProvider, userService, requestAuthenticator);
}
}
diff --git a/java/google/registry/module/tools/BUILD b/java/google/registry/module/tools/BUILD
index 02ec9f6dc..5574a6d6a 100644
--- a/java/google/registry/module/tools/BUILD
+++ b/java/google/registry/module/tools/BUILD
@@ -20,6 +20,7 @@ java_library(
"//java/google/registry/monitoring/whitebox",
"//java/google/registry/request",
"//java/google/registry/request:modules",
+ "//java/google/registry/request/auth",
"//java/google/registry/tools/server",
"//java/google/registry/tools/server/javascrap",
"//java/google/registry/util",
diff --git a/java/google/registry/module/tools/ToolsComponent.java b/java/google/registry/module/tools/ToolsComponent.java
index b9e8bf3e0..422062ea1 100644
--- a/java/google/registry/module/tools/ToolsComponent.java
+++ b/java/google/registry/module/tools/ToolsComponent.java
@@ -33,6 +33,7 @@ import google.registry.request.Modules.ModulesServiceModule;
import google.registry.request.Modules.UrlFetchTransportModule;
import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule;
import google.registry.request.Modules.UserServiceModule;
+import google.registry.request.auth.AuthModule;
import google.registry.util.SystemClock.SystemClockModule;
import google.registry.util.SystemSleeper.SystemSleeperModule;
import javax.inject.Singleton;
@@ -42,6 +43,7 @@ import javax.inject.Singleton;
@Component(
modules = {
AppIdentityCredentialModule.class,
+ AuthModule.class,
ConfigModule.class,
CustomLogicFactoryModule.class,
DatastoreServiceModule.class,
diff --git a/java/google/registry/module/tools/ToolsRequestHandler.java b/java/google/registry/module/tools/ToolsRequestHandler.java
index 0264fc9d9..4aecd8b2c 100644
--- a/java/google/registry/module/tools/ToolsRequestHandler.java
+++ b/java/google/registry/module/tools/ToolsRequestHandler.java
@@ -16,6 +16,7 @@ package google.registry.module.tools;
import com.google.appengine.api.users.UserService;
import google.registry.request.RequestHandler;
+import google.registry.request.auth.RequestAuthenticator;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -25,7 +26,8 @@ public class ToolsRequestHandler
@Inject ToolsRequestHandler(
Provider componentBuilderProvider,
- UserService userService) {
- super(componentBuilderProvider, userService);
+ UserService userService,
+ RequestAuthenticator requestAuthenticator) {
+ super(componentBuilderProvider, userService, requestAuthenticator);
}
}
diff --git a/java/google/registry/request/Action.java b/java/google/registry/request/Action.java
index ebc618ace..fe70b64ed 100644
--- a/java/google/registry/request/Action.java
+++ b/java/google/registry/request/Action.java
@@ -14,6 +14,7 @@
package google.registry.request;
+import google.registry.request.auth.Auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -61,4 +62,7 @@ public @interface Action {
* {@code web.xml} with {@code *}.
*/
boolean requireLogin() default false;
+
+ /** Authentication settings. */
+ Auth auth() default @Auth;
}
diff --git a/java/google/registry/request/BUILD b/java/google/registry/request/BUILD
index c9accfdb8..b5af3372e 100644
--- a/java/google/registry/request/BUILD
+++ b/java/google/registry/request/BUILD
@@ -11,6 +11,7 @@ java_library(
exclude = ["Modules.java"],
),
deps = [
+ "//java/google/registry/request/auth",
"//java/google/registry/security",
"//java/google/registry/util",
"@com_google_appengine_api_1_0_sdk",
diff --git a/java/google/registry/request/RequestHandler.java b/java/google/registry/request/RequestHandler.java
index 46353552c..f29319120 100644
--- a/java/google/registry/request/RequestHandler.java
+++ b/java/google/registry/request/RequestHandler.java
@@ -27,6 +27,8 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.appengine.api.users.UserService;
import com.google.common.base.Optional;
+import google.registry.request.auth.AuthResult;
+import google.registry.request.auth.RequestAuthenticator;
import google.registry.util.FormattingLogger;
import google.registry.util.TypeUtils.TypeInstantiator;
import java.io.IOException;
@@ -77,6 +79,7 @@ public class RequestHandler> {
private final Router router;
private final Provider requestComponentBuilderProvider;
private final UserService userService;
+ private final RequestAuthenticator requestAuthenticator;
/**
* Constructor for subclasses to create a new request handler for a specific request component.
@@ -89,22 +92,33 @@ public class RequestHandler> {
* be used to construct new instances of the request component (with the required
* request-derived modules provided by this class)
* @param userService an instance of the App Engine UserService API
+ * @param requestAuthenticator an instance of the RequestAuthenticator class
*/
- protected RequestHandler(Provider requestComponentBuilderProvider, UserService userService) {
- this(null, requestComponentBuilderProvider, userService);
+ protected RequestHandler(
+ Provider requestComponentBuilderProvider,
+ UserService userService,
+ RequestAuthenticator requestAuthenticator) {
+ this(null, requestComponentBuilderProvider, userService, requestAuthenticator);
}
/** Creates a new RequestHandler with an explicit component class for test purposes. */
public static > RequestHandler createForTest(
- Class component, Provider requestComponentBuilderProvider, UserService userService) {
+ Class component,
+ Provider requestComponentBuilderProvider,
+ UserService userService,
+ RequestAuthenticator requestAuthenticator) {
return new RequestHandler<>(
- checkNotNull(component), requestComponentBuilderProvider, userService);
+ checkNotNull(component),
+ requestComponentBuilderProvider,
+ userService,
+ requestAuthenticator);
}
private RequestHandler(
@Nullable Class component,
Provider requestComponentBuilderProvider,
- UserService userService) {
+ UserService userService,
+ RequestAuthenticator requestAuthenticator) {
// If the component class isn't explicitly provided, infer it from the class's own typing.
// This is safe only for use by subclasses of RequestHandler where the generic parameter is
// preserved at runtime, so only expose that option via the protected constructor.
@@ -112,6 +126,7 @@ public class RequestHandler> {
component != null ? component : new TypeInstantiator(getClass()){}.getExactType());
this.requestComponentBuilderProvider = checkNotNull(requestComponentBuilderProvider);
this.userService = checkNotNull(userService);
+ this.requestAuthenticator = checkNotNull(requestAuthenticator);
}
/** Runs the appropriate action for a servlet request. */
@@ -152,9 +167,16 @@ public class RequestHandler> {
rsp.sendError(SC_FORBIDDEN, "Invalid " + X_CSRF_TOKEN);
return;
}
+ Optional authResult =
+ requestAuthenticator.authorize(route.get().action().auth(), req);
+ if (!authResult.isPresent()) {
+ rsp.sendError(SC_FORBIDDEN);
+ return;
+ }
+
// Build a new request component using any modules we've constructed by this point.
C component = requestComponentBuilderProvider.get()
- .requestModule(new RequestModule(req, rsp))
+ .requestModule(new RequestModule(req, rsp, authResult.get()))
.build();
// Apply the selected Route to the component to produce an Action instance, and run it.
try {
diff --git a/java/google/registry/request/RequestModule.java b/java/google/registry/request/RequestModule.java
index 62ee20ccb..811124f43 100644
--- a/java/google/registry/request/RequestModule.java
+++ b/java/google/registry/request/RequestModule.java
@@ -26,6 +26,7 @@ import dagger.Module;
import dagger.Provides;
import google.registry.request.HttpException.BadRequestException;
import google.registry.request.HttpException.UnsupportedMediaTypeException;
+import google.registry.request.auth.AuthResult;
import java.io.IOException;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
@@ -40,10 +41,18 @@ public final class RequestModule {
private final HttpServletRequest req;
private final HttpServletResponse rsp;
+ private final AuthResult authResult;
- public RequestModule(HttpServletRequest req, HttpServletResponse rsp) {
+ public RequestModule(
+ HttpServletRequest req, HttpServletResponse rsp) {
+ this(req, rsp, AuthResult.NOT_AUTHENTICATED);
+ }
+
+ public RequestModule(
+ HttpServletRequest req, HttpServletResponse rsp, AuthResult authResult) {
this.req = req;
this.rsp = rsp;
+ this.authResult = authResult;
}
@Provides
@@ -66,6 +75,11 @@ public final class RequestModule {
return rsp;
}
+ @Provides
+ AuthResult provideAuthResult() {
+ return authResult;
+ }
+
@Provides
@RequestPath
static String provideRequestPath(HttpServletRequest req) {
diff --git a/javatests/google/registry/request/BUILD b/javatests/google/registry/request/BUILD
index c1f8e7788..36e31d3f5 100644
--- a/javatests/google/registry/request/BUILD
+++ b/javatests/google/registry/request/BUILD
@@ -12,14 +12,15 @@ java_library(
srcs = glob(["*.java"]),
deps = [
"//java/google/registry/request",
+ "//java/google/registry/request/auth",
"//java/google/registry/security",
- "//javatests/google/registry/security",
"//javatests/google/registry/testing",
"@com_google_appengine_api_1_0_sdk//:testonly",
"@com_google_guava",
"@com_google_guava_testlib",
"@com_google_truth",
"@com_googlecode_json_simple",
+ "@javax_inject",
"@javax_servlet_api",
"@joda_time",
"@junit",
diff --git a/javatests/google/registry/request/RequestHandlerTest.java b/javatests/google/registry/request/RequestHandlerTest.java
index 436a972e7..93cd9c2c2 100644
--- a/javatests/google/registry/request/RequestHandlerTest.java
+++ b/javatests/google/registry/request/RequestHandlerTest.java
@@ -23,15 +23,27 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import com.google.appengine.api.oauth.OAuthServiceFactory;
+import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.testing.NullPointerTester;
import google.registry.request.HttpException.ServiceUnavailableException;
+import google.registry.request.auth.AppEngineInternalAuthenticationMechanism;
+import google.registry.request.auth.Auth;
+import google.registry.request.auth.AuthLevel;
+import google.registry.request.auth.AuthResult;
+import google.registry.request.auth.LegacyAuthenticationMechanism;
+import google.registry.request.auth.OAuthAuthenticationMechanism;
+import google.registry.request.auth.RequestAuthenticator;
import google.registry.testing.AppEngineRule;
import google.registry.testing.InjectRule;
import google.registry.testing.Providers;
import google.registry.testing.UserInfo;
import java.io.PrintWriter;
import java.io.StringWriter;
+import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.After;
@@ -57,31 +69,31 @@ public final class RequestHandlerTest {
public final InjectRule inject = new InjectRule();
@Action(path = "/bumblebee", method = {GET, POST}, isPrefix = true)
- public class BumblebeeTask implements Runnable {
+ public static class BumblebeeTask implements Runnable {
@Override
public void run() {}
}
@Action(path = "/sloth", method = POST, automaticallyPrintOk = true)
- public class SlothTask implements Runnable {
+ public static class SlothTask implements Runnable {
@Override
public void run() {}
}
@Action(path = "/safe-sloth", method = {GET, POST}, xsrfProtection = true, xsrfScope = "vampire")
- public class SafeSlothTask implements Runnable {
+ public static class SafeSlothTask implements Runnable {
@Override
public void run() {}
}
@Action(path = "/users-only", method = GET, requireLogin = true)
- public class UsersOnlyAction implements Runnable {
+ public static class UsersOnlyAction implements Runnable {
@Override
public void run() {}
}
@Action(path = "/fail")
- public final class FailTask implements Runnable {
+ public static final class FailTask implements Runnable {
@Override
public void run() {
throw new ServiceUnavailableException("Set sail for fail");
@@ -89,7 +101,7 @@ public final class RequestHandlerTest {
}
@Action(path = "/failAtConstruction")
- public final class FailAtConstructionTask implements Runnable {
+ public static final class FailAtConstructionTask implements Runnable {
public FailAtConstructionTask() {
throw new ServiceUnavailableException("Fail at construction");
}
@@ -100,7 +112,57 @@ public final class RequestHandlerTest {
}
}
+ class AuthBase implements Runnable {
+ private final AuthResult authResult;
+
+ AuthBase(AuthResult authResult) {
+ this.authResult = authResult;
+ }
+
+ @Override
+ public void run() {
+ injectedAuthResult = authResult;
+ }
+ }
+
+ @Action(
+ path = "/auth/none",
+ auth = @Auth(
+ methods = {Auth.AuthMethod.INTERNAL},
+ minimumLevel = AuthLevel.NONE,
+ userPolicy = Auth.UserPolicy.IGNORED),
+ method = Action.Method.GET)
+ public class AuthNoneAction extends AuthBase {
+ @Inject AuthNoneAction(AuthResult authResult) {
+ super(authResult);
+ }
+ }
+
+ @Action(
+ path = "/auth/adminUserAnyMethod",
+ auth = @Auth(
+ methods = {Auth.AuthMethod.INTERNAL, Auth.AuthMethod.API, Auth.AuthMethod.LEGACY},
+ minimumLevel = AuthLevel.USER,
+ userPolicy = Auth.UserPolicy.ADMIN),
+ method = Action.Method.GET)
+ public class AuthAdminUserAnyMethodAction extends AuthBase {
+ @Inject AuthAdminUserAnyMethodAction(AuthResult authResult) {
+ super(authResult);
+ }
+ }
+
public class Component {
+
+ private RequestModule requestModule = null;
+
+ public RequestModule getRequestModule() {
+ return requestModule;
+ }
+
+ public void setRequestModule(RequestModule requestModule) {
+ this.requestModule = requestModule;
+ }
+
public BumblebeeTask bumblebeeTask() {
return bumblebeeTask;
}
@@ -124,12 +186,22 @@ public final class RequestHandlerTest {
public FailAtConstructionTask failAtConstructionTask() {
return new FailAtConstructionTask();
}
+
+ public AuthNoneAction authNoneAction() {
+ return new AuthNoneAction(component.getRequestModule().provideAuthResult());
+ }
+
+ public AuthAdminUserAnyMethodAction authAdminUserAnyMethodAction() {
+ return new AuthAdminUserAnyMethodAction(
+ component.getRequestModule().provideAuthResult());
+ }
}
/** Fake Builder for the fake component above to satisfy RequestHandler expectations. */
- public abstract static class Builder implements RequestComponentBuilder {
+ public abstract class Builder implements RequestComponentBuilder {
@Override
public Builder requestModule(RequestModule requestModule) {
+ component.setRequestModule(requestModule);
return this;
}
}
@@ -158,9 +230,22 @@ public final class RequestHandlerTest {
private final Component component = new Component();
private final StringWriter httpOutput = new StringWriter();
private RequestHandler handler;
+ private AuthResult injectedAuthResult = null;
+ private final User testUser = new User("test@example.com", "test@example.com");
+ private RequestAuthenticator requestAuthenticator;
@Before
public void before() throws Exception {
+ requestAuthenticator = new RequestAuthenticator(
+ new AppEngineInternalAuthenticationMechanism(),
+ ImmutableList.of(
+ new OAuthAuthenticationMechanism(
+ OAuthServiceFactory.getOAuthService(),
+ ImmutableSet.of("https://www.googleapis.com/auth/userinfo.email"),
+ ImmutableSet.of("https://www.googleapis.com/auth/userinfo.email"),
+ ImmutableSet.of("proxy-client-id", "regtool-client-id"))),
+ new LegacyAuthenticationMechanism(userService));
+
// Initialize here, not inline, so that we pick up the mocked UserService.
handler = RequestHandler.createForTest(
Component.class,
@@ -171,7 +256,8 @@ public final class RequestHandlerTest {
return component;
}
}),
- userService);
+ userService,
+ requestAuthenticator);
when(rsp.getWriter()).thenReturn(new PrintWriter(httpOutput));
}
@@ -279,6 +365,7 @@ public final class RequestHandlerTest {
@Test
public void testNullness() {
NullPointerTester tester = new NullPointerTester();
+ tester.setDefault(RequestAuthenticator.class, requestAuthenticator);
tester.testAllPublicStaticMethods(RequestHandler.class);
tester.testAllPublicInstanceMethods(handler);
}
@@ -331,9 +418,56 @@ public final class RequestHandlerTest {
@Test
public void testMustBeLoggedIn_loggedIn_runsAction() throws Exception {
when(userService.isUserLoggedIn()).thenReturn(true);
+ when(userService.getCurrentUser()).thenReturn(testUser);
when(req.getMethod()).thenReturn("GET");
when(req.getRequestURI()).thenReturn("/users-only");
handler.handleRequest(req, rsp);
verify(usersOnlyAction).run();
}
+
+ @Test
+ public void testNoAuthNeeded_success() throws Exception {
+ when(req.getMethod()).thenReturn("GET");
+ when(req.getRequestURI()).thenReturn("/auth/none");
+ handler.handleRequest(req, rsp);
+ assertThat(injectedAuthResult).isNotNull();
+ assertThat(injectedAuthResult.authLevel()).isEqualTo(AuthLevel.NONE);
+ assertThat(injectedAuthResult.userAuthInfo()).isAbsent();
+ }
+
+ @Test
+ public void testAuthNeeded_notLoggedIn() throws Exception {
+ when(req.getMethod()).thenReturn("GET");
+ when(req.getRequestURI()).thenReturn("/auth/adminUserAnyMethod");
+ handler.handleRequest(req, rsp);
+ verify(rsp).sendError(403);
+ assertThat(injectedAuthResult).isNull();
+ }
+
+ @Test
+ public void testAuthNeeded_notAuthorized() throws Exception {
+ when(userService.isUserLoggedIn()).thenReturn(true);
+ when(userService.getCurrentUser()).thenReturn(testUser);
+ when(userService.isUserAdmin()).thenReturn(false);
+ when(req.getMethod()).thenReturn("GET");
+ when(req.getRequestURI()).thenReturn("/auth/adminUserAnyMethod");
+ handler.handleRequest(req, rsp);
+ verify(rsp).sendError(403);
+ assertThat(injectedAuthResult).isNull();
+ }
+
+ @Test
+ public void testAuthNeeded_success() throws Exception {
+ when(userService.isUserLoggedIn()).thenReturn(true);
+ when(userService.getCurrentUser()).thenReturn(testUser);
+ when(userService.isUserAdmin()).thenReturn(true);
+ when(req.getMethod()).thenReturn("GET");
+ when(req.getRequestURI()).thenReturn("/auth/adminUserAnyMethod");
+ handler.handleRequest(req, rsp);
+ assertThat(injectedAuthResult).isNotNull();
+ assertThat(injectedAuthResult.authLevel()).isEqualTo(AuthLevel.USER);
+ assertThat(injectedAuthResult.userAuthInfo()).isPresent();
+ assertThat(injectedAuthResult.userAuthInfo().get().user()).isEqualTo(testUser);
+ assertThat(injectedAuthResult.userAuthInfo().get().oauthTokenInfo()).isAbsent();
+ }
}
diff --git a/javatests/google/registry/testing/FakeUserService.java b/javatests/google/registry/testing/FakeUserService.java
new file mode 100644
index 000000000..078e4a218
--- /dev/null
+++ b/javatests/google/registry/testing/FakeUserService.java
@@ -0,0 +1,75 @@
+// Copyright 2017 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.testing;
+
+import com.google.appengine.api.users.User;
+import com.google.appengine.api.users.UserService;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+// TODO: Consider reconciling this with AppEngineRule.withUserService()
+
+/** Fake implementation of {@link UserService} for testing. */
+public class FakeUserService implements UserService {
+
+ @Nullable private User user = null;
+ private boolean isAdmin = false;
+
+ public void setUser(@Nullable User user, boolean isAdmin) {
+ this.user = user;
+ this.isAdmin = isAdmin;
+ }
+
+ @Override
+ public String createLoginURL(String destinationURL) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String createLoginURL(String destinationURL, String authDomain) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String createLoginURL(String destinationURL, String authDomain, String federatedIdentity,
+ Set attributesRequest) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String createLogoutURL(String destinationURL) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String createLogoutURL(String destinationURL, String authDomain) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUserLoggedIn() {
+ return user != null;
+ }
+
+ @Override
+ public boolean isUserAdmin() {
+ return isAdmin;
+ }
+
+ @Override
+ public User getCurrentUser() {
+ return user;
+ }
+}