From c41f5bb31c6a03d9ddf79e8de9c0f6daaed83add Mon Sep 17 00:00:00 2001 From: mountford Date: Thu, 9 Feb 2017 14:28:23 -0800 Subject: [PATCH] Make first pass at new OAuth-aware server authentication framework ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=147081745 --- .../registry/config/RegistryConfig.java | 39 +++++ java/google/registry/module/backend/BUILD | 1 + .../module/backend/BackendComponent.java | 2 + .../module/backend/BackendRequestHandler.java | 6 +- java/google/registry/module/frontend/BUILD | 1 + .../module/frontend/FrontendComponent.java | 2 + .../frontend/FrontendRequestHandler.java | 6 +- java/google/registry/module/tools/BUILD | 1 + .../registry/module/tools/ToolsComponent.java | 2 + .../module/tools/ToolsRequestHandler.java | 6 +- java/google/registry/request/Action.java | 4 + java/google/registry/request/BUILD | 1 + .../registry/request/RequestHandler.java | 34 +++- .../registry/request/RequestModule.java | 16 +- javatests/google/registry/request/BUILD | 3 +- .../registry/request/RequestHandlerTest.java | 150 +++++++++++++++++- .../registry/testing/FakeUserService.java | 75 +++++++++ 17 files changed, 327 insertions(+), 22 deletions(-) create mode 100644 javatests/google/registry/testing/FakeUserService.java 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: + * + *

+ */ + @Provides + @Config("availableOauthScopes") + public static ImmutableSet 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; + } +}