From ebcc39a0b2a216218abca42a9f8775a499fa28ba Mon Sep 17 00:00:00 2001 From: Lai Jiang Date: Tue, 16 May 2023 16:43:11 -0400 Subject: [PATCH] Refactor OIDC-based auth mechanism (#2025) IAP and regular OIDC auth mechanisms are unified under a base class that produces either APP or USER level AuthResult based on the principal email found in the OIDC token. Also moved some enum classes to better organize code structure. --- .../registry/request/RequestMetrics.java | 2 +- ...EngineInternalAuthenticationMechanism.java | 4 +- .../google/registry/request/auth/Auth.java | 34 +-- .../registry/request/auth/AuthLevel.java | 52 ---- .../registry/request/auth/AuthModule.java | 67 +++-- .../registry/request/auth/AuthResult.java | 4 +- .../registry/request/auth/AuthSettings.java | 109 +++++++++ .../IapHeaderAuthenticationMechanism.java | 60 ----- .../auth/IdTokenAuthenticationBase.java | 70 ------ .../auth/LegacyAuthenticationMechanism.java | 4 +- .../auth/OAuthAuthenticationMechanism.java | 7 +- .../OidcTokenAuthenticationMechanism.java | 173 +++++++++++++ .../request/auth/RequestAuthenticator.java | 62 +---- ...ServiceAccountAuthenticationMechanism.java | 63 ----- .../registry/tools/RequestFactoryModule.java | 4 +- .../registry/rdap/RdapActionBaseTestCase.java | 2 +- .../registry/request/RequestHandlerTest.java | 2 +- .../AuthenticatedRegistrarAccessorTest.java | 1 + .../IapHeaderAuthenticationMechanismTest.java | 111 --------- .../LegacyAuthenticationMechanismTest.java | 1 + .../OidcTokenAuthenticationMechanismTest.java | 230 ++++++++++++++++++ .../auth/RequestAuthenticatorTest.java | 70 +++--- ...iceAccountAuthenticationMechanismTest.java | 89 ------- .../server/RegistryTestServerMain.java | 8 +- .../console/ConsoleDomainGetActionTest.java | 2 +- .../registrar/ConsoleOteSetupActionTest.java | 2 +- .../ConsoleRegistrarCreatorActionTest.java | 2 +- .../server/registrar/ConsoleUiActionTest.java | 2 +- .../RegistrarSettingsActionTestCase.java | 2 +- .../registrar/RegistryLockGetActionTest.java | 2 +- .../registrar/RegistryLockPostActionTest.java | 2 +- .../RegistryLockVerifyActionTest.java | 2 +- 32 files changed, 644 insertions(+), 601 deletions(-) delete mode 100644 core/src/main/java/google/registry/request/auth/AuthLevel.java create mode 100644 core/src/main/java/google/registry/request/auth/AuthSettings.java delete mode 100644 core/src/main/java/google/registry/request/auth/IapHeaderAuthenticationMechanism.java delete mode 100644 core/src/main/java/google/registry/request/auth/IdTokenAuthenticationBase.java create mode 100644 core/src/main/java/google/registry/request/auth/OidcTokenAuthenticationMechanism.java delete mode 100644 core/src/main/java/google/registry/request/auth/ServiceAccountAuthenticationMechanism.java delete mode 100644 core/src/test/java/google/registry/request/auth/IapHeaderAuthenticationMechanismTest.java create mode 100644 core/src/test/java/google/registry/request/auth/OidcTokenAuthenticationMechanismTest.java delete mode 100644 core/src/test/java/google/registry/request/auth/ServiceAccountAuthenticationMechanismTest.java diff --git a/core/src/main/java/google/registry/request/RequestMetrics.java b/core/src/main/java/google/registry/request/RequestMetrics.java index 78a2f2a2a..9a15e94d2 100644 --- a/core/src/main/java/google/registry/request/RequestMetrics.java +++ b/core/src/main/java/google/registry/request/RequestMetrics.java @@ -24,7 +24,7 @@ import com.google.common.flogger.FluentLogger; import com.google.monitoring.metrics.EventMetric; import com.google.monitoring.metrics.LabelDescriptor; import com.google.monitoring.metrics.MetricRegistryImpl; -import google.registry.request.auth.AuthLevel; +import google.registry.request.auth.AuthSettings.AuthLevel; import java.util.List; import java.util.stream.Collectors; import org.joda.time.Duration; diff --git a/core/src/main/java/google/registry/request/auth/AppEngineInternalAuthenticationMechanism.java b/core/src/main/java/google/registry/request/auth/AppEngineInternalAuthenticationMechanism.java index 35b44e266..5b0421133 100644 --- a/core/src/main/java/google/registry/request/auth/AppEngineInternalAuthenticationMechanism.java +++ b/core/src/main/java/google/registry/request/auth/AppEngineInternalAuthenticationMechanism.java @@ -14,8 +14,8 @@ package google.registry.request.auth; -import static google.registry.request.auth.AuthLevel.APP; -import static google.registry.request.auth.AuthLevel.NONE; +import static google.registry.request.auth.AuthSettings.AuthLevel.APP; +import static google.registry.request.auth.AuthSettings.AuthLevel.NONE; import com.google.appengine.api.users.UserService; import javax.inject.Inject; diff --git a/core/src/main/java/google/registry/request/auth/Auth.java b/core/src/main/java/google/registry/request/auth/Auth.java index 30cf2a062..ead0c7b17 100644 --- a/core/src/main/java/google/registry/request/auth/Auth.java +++ b/core/src/main/java/google/registry/request/auth/Auth.java @@ -15,9 +15,9 @@ package google.registry.request.auth; import com.google.common.collect.ImmutableList; -import google.registry.request.auth.RequestAuthenticator.AuthMethod; -import google.registry.request.auth.RequestAuthenticator.AuthSettings; -import google.registry.request.auth.RequestAuthenticator.UserPolicy; +import google.registry.request.auth.AuthSettings.AuthLevel; +import google.registry.request.auth.AuthSettings.AuthMethod; +import google.registry.request.auth.AuthSettings.UserPolicy; /** Enum used to configure authentication settings for Actions. */ public enum Auth { @@ -25,21 +25,18 @@ public enum Auth { /** * Allows anyone access, doesn't attempt to authenticate user. * - * Will never return absent(), but only authenticates access from App Engine task-queues. For + *

Will never return absent(), but only authenticates access from App Engine task-queues. For * everyone else - returns NOT_AUTHENTICATED. */ - AUTH_PUBLIC_ANONYMOUS( - ImmutableList.of(AuthMethod.INTERNAL), - AuthLevel.NONE, - UserPolicy.PUBLIC), + AUTH_PUBLIC_ANONYMOUS(ImmutableList.of(AuthMethod.INTERNAL), AuthLevel.NONE, UserPolicy.PUBLIC), /** - * Allows anyone access, does attempt to authenticate user. + * Allows anyone to access, does attempt to authenticate user. * - * If a user is logged in, will authenticate (and return) them. Otherwise, access is still + *

If a user is logged in, will authenticate (and return) them. Otherwise, access is still * granted, but NOT_AUTHENTICATED is returned. * - * Will never return absent(). + *

Will never return absent(). */ AUTH_PUBLIC( ImmutableList.of(AuthMethod.INTERNAL, AuthMethod.API, AuthMethod.LEGACY), @@ -47,17 +44,15 @@ public enum Auth { UserPolicy.PUBLIC), /** - * Allows anyone access, as long as they are logged in. + * Allows anyone to access, as long as they are logged in. * - * Does not allow access from App Engine task-queues. + *

Does not allow access from App Engine task-queues. */ AUTH_PUBLIC_LOGGED_IN( - ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), - AuthLevel.USER, - UserPolicy.PUBLIC), + ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), AuthLevel.USER, UserPolicy.PUBLIC), /** - * Allows anyone access, as long as they use OAuth to authenticate. + * Allows anyone to access, as long as they use OAuth to authenticate. * *

Also allows access from App Engine task-queue. Note that OAuth client ID still needs to be * allow-listed in the config file for OAuth-based authentication to succeed. @@ -80,10 +75,7 @@ public enum Auth { private final AuthSettings authSettings; - Auth( - ImmutableList methods, - AuthLevel minimumLevel, - UserPolicy userPolicy) { + Auth(ImmutableList methods, AuthLevel minimumLevel, UserPolicy userPolicy) { authSettings = AuthSettings.create(methods, minimumLevel, userPolicy); } diff --git a/core/src/main/java/google/registry/request/auth/AuthLevel.java b/core/src/main/java/google/registry/request/auth/AuthLevel.java deleted file mode 100644 index 45ddd2457..000000000 --- a/core/src/main/java/google/registry/request/auth/AuthLevel.java +++ /dev/null @@ -1,52 +0,0 @@ -// 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.request.auth; - -/** - * Authentication level. - * - *

Used by {@link Auth} to specify what authentication is required, and by {@link AuthResult}) - * to specify what authentication was found. These are a series of levels, from least to most - * authentication required. The lowest level of requirement, NONE, can be satisfied by any level - * of authentication, while the highest level, USER, can only be satisfied by the authentication of - * a specific user. The level returned may be higher than what was required, if more authentication - * turns out to be possible. For instance, if an authenticated user is found, USER will be returned - * even if no authentication was required. - */ -public enum AuthLevel { - - /** No authentication was required/found. */ - NONE, - - /** - * Authentication required, but user not required. - * - *

In Auth: Authentication is required, but app-internal authentication (which isn't associated - * with a specific user) is permitted. - * - *

In AuthResult: App-internal authentication was successful. - */ - APP, - - /** - * Authentication required, user required. - * - *

In Auth: Authentication is required, and app-internal authentication is forbidden, meaning - * that a valid authentication result will contain specific user information. - * - *

In AuthResult: A valid user was authenticated. - */ - USER -} diff --git a/core/src/main/java/google/registry/request/auth/AuthModule.java b/core/src/main/java/google/registry/request/auth/AuthModule.java index 45f20a037..08586b04c 100644 --- a/core/src/main/java/google/registry/request/auth/AuthModule.java +++ b/core/src/main/java/google/registry/request/auth/AuthModule.java @@ -14,6 +14,8 @@ package google.registry.request.auth; +import static com.google.common.net.HttpHeaders.AUTHORIZATION; + import com.google.appengine.api.oauth.OAuthService; import com.google.appengine.api.oauth.OAuthServiceFactory; import com.google.auth.oauth2.TokenVerifier; @@ -21,35 +23,46 @@ import com.google.common.collect.ImmutableList; import dagger.Module; import dagger.Provides; import google.registry.config.RegistryConfig.Config; +import google.registry.request.auth.OidcTokenAuthenticationMechanism.IapOidcAuthenticationMechanism; +import google.registry.request.auth.OidcTokenAuthenticationMechanism.RegularOidcAuthenticationMechanism; +import google.registry.request.auth.OidcTokenAuthenticationMechanism.TokenExtractor; import javax.inject.Qualifier; import javax.inject.Singleton; -/** - * Dagger module for authentication routines. - */ +/** Dagger module for authentication routines. */ @Module public class AuthModule { + // IAP-signed JWT will be in this header. + // See https://cloud.google.com/iap/docs/signed-headers-howto#securing_iap_headers. + public static final String IAP_HEADER_NAME = "X-Goog-IAP-JWT-Assertion"; + // GAE will put the content in header "proxy-authorization" in this header when it routes the + // request to the app. + public static final String PROXY_HEADER_NAME = "X-Google-Proxy-Authorization"; + public static final String BEARER_PREFIX = "Bearer "; + // TODO: Change the IAP audience format once we are on GKE. + // See: https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload + private static final String IAP_AUDIENCE_FORMAT = "/projects/%d/apps/%s"; private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap"; private static final String SA_ISSUER_URL = "https://accounts.google.com"; - /** Provides the custom authentication mechanisms (including OAuth). */ + /** Provides the custom authentication mechanisms (including OAuth and OIDC). */ @Provides ImmutableList provideApiAuthenticationMechanisms( OAuthAuthenticationMechanism oauthAuthenticationMechanism, - IapHeaderAuthenticationMechanism iapHeaderAuthenticationMechanism, - ServiceAccountAuthenticationMechanism serviceAccountAuthenticationMechanism) { + IapOidcAuthenticationMechanism iapOidcAuthenticationMechanism, + RegularOidcAuthenticationMechanism regularOidcAuthenticationMechanism) { return ImmutableList.of( oauthAuthenticationMechanism, - iapHeaderAuthenticationMechanism, - serviceAccountAuthenticationMechanism); + iapOidcAuthenticationMechanism, + regularOidcAuthenticationMechanism); } @Qualifier - @interface IAP {} + @interface IapOidc {} @Qualifier - @interface ServiceAccount {} + @interface RegularOidc {} /** Provides the OAuthService instance. */ @Provides @@ -58,18 +71,42 @@ public class AuthModule { } @Provides - @IAP + @IapOidc @Singleton - TokenVerifier provideTokenVerifier( + TokenVerifier provideIapTokenVerifier( @Config("projectId") String projectId, @Config("projectIdNumber") long projectIdNumber) { - String audience = String.format("/projects/%d/apps/%s", projectIdNumber, projectId); + String audience = String.format(IAP_AUDIENCE_FORMAT, projectIdNumber, projectId); return TokenVerifier.newBuilder().setAudience(audience).setIssuer(IAP_ISSUER_URL).build(); } @Provides - @ServiceAccount + @RegularOidc @Singleton - TokenVerifier provideServiceAccountTokenVerifier(@Config("projectId") String projectId) { + TokenVerifier provideRegularTokenVerifier(@Config("projectId") String projectId) { return TokenVerifier.newBuilder().setAudience(projectId).setIssuer(SA_ISSUER_URL).build(); } + + @Provides + @IapOidc + @Singleton + TokenExtractor provideIapTokenExtractor() { + return request -> request.getHeader(IAP_HEADER_NAME); + } + + @Provides + @RegularOidc + @Singleton + TokenExtractor provideRegularTokenExtractor() { + return request -> { + // TODO: only check the Authorizaiton header after the migration to OIDC is complete. + String rawToken = request.getHeader(PROXY_HEADER_NAME); + if (rawToken == null) { + rawToken = request.getHeader(AUTHORIZATION); + } + if (rawToken != null && rawToken.startsWith(BEARER_PREFIX)) { + return rawToken.substring(BEARER_PREFIX.length()); + } + return null; + }; + } } diff --git a/core/src/main/java/google/registry/request/auth/AuthResult.java b/core/src/main/java/google/registry/request/auth/AuthResult.java index 41f94900a..1e4432a63 100644 --- a/core/src/main/java/google/registry/request/auth/AuthResult.java +++ b/core/src/main/java/google/registry/request/auth/AuthResult.java @@ -17,6 +17,7 @@ package google.registry.request.auth; import static com.google.common.base.Preconditions.checkNotNull; import com.google.auto.value.AutoValue; +import google.registry.request.auth.AuthSettings.AuthLevel; import java.util.Optional; import javax.annotation.Nullable; @@ -66,6 +67,5 @@ public abstract class AuthResult { * returns NOT_AUTHENTICATED in this case, as opposed to absent() if authentication failed and was * required. So as a return from an authorization check, this can be treated as a success. */ - public static final AuthResult NOT_AUTHENTICATED = - AuthResult.create(AuthLevel.NONE); + public static final AuthResult NOT_AUTHENTICATED = create(AuthLevel.NONE); } diff --git a/core/src/main/java/google/registry/request/auth/AuthSettings.java b/core/src/main/java/google/registry/request/auth/AuthSettings.java new file mode 100644 index 000000000..3b41d4bfa --- /dev/null +++ b/core/src/main/java/google/registry/request/auth/AuthSettings.java @@ -0,0 +1,109 @@ +// Copyright 2023 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.request.auth; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; + +/** + * Parameters used to configure the authenticator. + * + *

AuthSettings shouldn't be used directly, instead - use one of the predefined {@link Auth} enum + * values. + */ +@Immutable +@AutoValue +public abstract class AuthSettings { + + public abstract ImmutableList methods(); + + public abstract AuthLevel minimumLevel(); + + public abstract UserPolicy userPolicy(); + + static AuthSettings create( + ImmutableList methods, AuthLevel minimumLevel, UserPolicy userPolicy) { + return new AutoValue_AuthSettings(methods, minimumLevel, userPolicy); + } + + /** Available methods for authentication. */ + public enum AuthMethod { + + /** App Engine internal authentication. Must always be provided as the first method. */ + INTERNAL, + + /** Authentication methods suitable for API-style access, such as OAuth 2. */ + API, + + /** Legacy authentication using cookie-based App Engine Users API. Must come last if present. */ + LEGACY + } + + /** + * Authentication level. + * + *

Used by {@link Auth} to specify what authentication is required, and by {@link AuthResult}) + * to specify what authentication was found. These are a series of levels, from least to most + * authentication required. The lowest level of requirement, NONE, can be satisfied by any level + * of authentication, while the highest level, USER, can only be satisfied by the authentication + * of a specific user. The level returned may be higher than what was required, if more + * authentication turns out to be possible. For instance, if an authenticated user is found, USER + * will be returned even if no authentication was required. + */ + public enum AuthLevel { + + /** No authentication was required/found. */ + NONE, + + /** + * Authentication required, but user not required. + * + *

In Auth: Authentication is required, but app-internal authentication (which isn't + * associated with a specific user) is permitted. + * + *

In AuthResult: App-internal authentication was successful. + */ + APP, + + /** + * Authentication required, user required. + * + *

In Auth: Authentication is required, and app-internal authentication is forbidden, meaning + * that a valid authentication result will contain specific user information. + * + *

In AuthResult: A valid user was authenticated. + */ + USER + } + + /** User authorization policy options. */ + public enum UserPolicy { + + /** This action ignores end users; the only configured auth method must be INTERNAL. */ + IGNORED, + + /** No user policy is enforced; anyone can access this action. */ + PUBLIC, + + /** + * If there is a user, it must be an admin, as determined by isUserAdmin(). + * + *

Note that, according to App Engine, anybody with access to the app in the GCP Console, + * including editors and viewers, is an admin. + */ + ADMIN + } +} diff --git a/core/src/main/java/google/registry/request/auth/IapHeaderAuthenticationMechanism.java b/core/src/main/java/google/registry/request/auth/IapHeaderAuthenticationMechanism.java deleted file mode 100644 index 76d95408b..000000000 --- a/core/src/main/java/google/registry/request/auth/IapHeaderAuthenticationMechanism.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2022 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.request.auth; - -import com.google.auth.oauth2.TokenVerifier; -import google.registry.model.console.User; -import google.registry.model.console.UserDao; -import google.registry.request.auth.AuthModule.IAP; -import java.util.Optional; -import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; - -/** - * A way to authenticate HTTP requests that have gone through the GCP Identity-Aware Proxy. - * - *

When the user logs in, IAP provides a JWT in the X-Goog-IAP-JWT-Assertion header. - * This header is included on all requests to IAP-enabled services (which should be all of them that - * receive requests from the front end). The token verification libraries ensure that the signed - * token has the proper audience and issuer. - * - * @see the documentation on GCP - * IAP's signed headers for more information. - */ -public class IapHeaderAuthenticationMechanism extends IdTokenAuthenticationBase { - - private static final String ID_TOKEN_HEADER_NAME = "X-Goog-IAP-JWT-Assertion"; - - @Inject - public IapHeaderAuthenticationMechanism(@IAP TokenVerifier tokenVerifier) { - super(tokenVerifier); - } - - @Override - String rawTokenFromRequest(HttpServletRequest request) { - return request.getHeader(ID_TOKEN_HEADER_NAME); - } - - @Override - AuthResult authResultFromEmail(String emailAddress) { - Optional maybeUser = UserDao.loadUser(emailAddress); - if (!maybeUser.isPresent()) { - logger.atInfo().log("No user found for email address %s", emailAddress); - return AuthResult.NOT_AUTHENTICATED; - } - return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(maybeUser.get())); - } - -} diff --git a/core/src/main/java/google/registry/request/auth/IdTokenAuthenticationBase.java b/core/src/main/java/google/registry/request/auth/IdTokenAuthenticationBase.java deleted file mode 100644 index e13ad4ddd..000000000 --- a/core/src/main/java/google/registry/request/auth/IdTokenAuthenticationBase.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2022 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.request.auth; - -import com.google.api.client.json.webtoken.JsonWebSignature; -import com.google.auth.oauth2.TokenVerifier; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.flogger.FluentLogger; -import google.registry.config.RegistryEnvironment; -import google.registry.model.console.User; -import java.util.Optional; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; - -public abstract class IdTokenAuthenticationBase implements AuthenticationMechanism { - - public static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - // A workaround that allows "use" of the IAP-based authenticator when running local testing, i.e. - // the RegistryTestServer - private static Optional userForTesting = Optional.empty(); - - private final TokenVerifier tokenVerifier; - - public IdTokenAuthenticationBase(TokenVerifier tokenVerifier) { - this.tokenVerifier = tokenVerifier; - } - - abstract String rawTokenFromRequest(HttpServletRequest request); - - abstract AuthResult authResultFromEmail(String email); - - @Override - public AuthResult authenticate(HttpServletRequest request) { - if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST) - && userForTesting.isPresent()) { - return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userForTesting.get())); - } - String rawIdToken = rawTokenFromRequest(request); - if (rawIdToken == null) { - return AuthResult.NOT_AUTHENTICATED; - } - JsonWebSignature token; - try { - token = tokenVerifier.verify(rawIdToken); - } catch (Exception e) { - logger.atInfo().withCause(e).log("Error when verifying access token"); - return AuthResult.NOT_AUTHENTICATED; - } - String emailAddress = (String) token.getPayload().get("email"); - return authResultFromEmail(emailAddress); - } - - @VisibleForTesting - public static void setUserAuthInfoForTestServer(@Nullable User user) { - userForTesting = Optional.ofNullable(user); - } -} diff --git a/core/src/main/java/google/registry/request/auth/LegacyAuthenticationMechanism.java b/core/src/main/java/google/registry/request/auth/LegacyAuthenticationMechanism.java index b4f12eab0..b7fc660b7 100644 --- a/core/src/main/java/google/registry/request/auth/LegacyAuthenticationMechanism.java +++ b/core/src/main/java/google/registry/request/auth/LegacyAuthenticationMechanism.java @@ -16,8 +16,8 @@ package google.registry.request.auth; import static com.google.common.base.Strings.emptyToNull; import static com.google.common.base.Strings.nullToEmpty; -import static google.registry.request.auth.AuthLevel.NONE; -import static google.registry.request.auth.AuthLevel.USER; +import static google.registry.request.auth.AuthSettings.AuthLevel.NONE; +import static google.registry.request.auth.AuthSettings.AuthLevel.USER; import static google.registry.security.XsrfTokenManager.P_CSRF_TOKEN; import static google.registry.security.XsrfTokenManager.X_CSRF_TOKEN; diff --git a/core/src/main/java/google/registry/request/auth/OAuthAuthenticationMechanism.java b/core/src/main/java/google/registry/request/auth/OAuthAuthenticationMechanism.java index b81e39bf1..933cdabea 100644 --- a/core/src/main/java/google/registry/request/auth/OAuthAuthenticationMechanism.java +++ b/core/src/main/java/google/registry/request/auth/OAuthAuthenticationMechanism.java @@ -15,8 +15,9 @@ package google.registry.request.auth; import static com.google.common.net.HttpHeaders.AUTHORIZATION; -import static google.registry.request.auth.AuthLevel.NONE; -import static google.registry.request.auth.AuthLevel.USER; +import static google.registry.request.auth.AuthModule.BEARER_PREFIX; +import static google.registry.request.auth.AuthSettings.AuthLevel.NONE; +import static google.registry.request.auth.AuthSettings.AuthLevel.USER; import com.google.appengine.api.oauth.OAuthRequestException; import com.google.appengine.api.oauth.OAuthService; @@ -35,8 +36,6 @@ import javax.servlet.http.HttpServletRequest; */ public class OAuthAuthenticationMechanism implements AuthenticationMechanism { - private static final String BEARER_PREFIX = "Bearer "; - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private final OAuthService oauthService; diff --git a/core/src/main/java/google/registry/request/auth/OidcTokenAuthenticationMechanism.java b/core/src/main/java/google/registry/request/auth/OidcTokenAuthenticationMechanism.java new file mode 100644 index 000000000..de6fa70cc --- /dev/null +++ b/core/src/main/java/google/registry/request/auth/OidcTokenAuthenticationMechanism.java @@ -0,0 +1,173 @@ +// Copyright 2022 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.request.auth; + +import static google.registry.request.auth.AuthSettings.AuthLevel.APP; + +import com.google.api.client.json.webtoken.JsonWebSignature; +import com.google.auth.oauth2.TokenVerifier; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.flogger.FluentLogger; +import google.registry.config.RegistryConfig.Config; +import google.registry.config.RegistryEnvironment; +import google.registry.model.console.User; +import google.registry.model.console.UserDao; +import google.registry.request.auth.AuthModule.IapOidc; +import google.registry.request.auth.AuthModule.RegularOidc; +import google.registry.request.auth.AuthSettings.AuthLevel; +import java.util.Optional; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; + +/** + * An authenticam mechanism that verifies the OIDC token. + * + *

Currently, two flavors are supported: one that checkes for the OIDC token as a regular bearer + * token, and another that checks for the OIDC token passed by IAP. In both cases, the {@link + * AuthResult} with the highest {@link AuthLevel} possible is returned. So, if the email address for + * which the token is minted exists both as a {@link User} and as a service account, the returned + * {@link AuthResult} is at {@link AuthLevel#USER}. + * + * @see OpenID + * Connect + */ +public abstract class OidcTokenAuthenticationMechanism implements AuthenticationMechanism { + + public static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + // A workaround that allows "use" of the OIDC authenticator when running local testing, i.e. + // the RegistryTestServer + private static AuthResult authResultForTesting = null; + + protected final TokenVerifier tokenVerifier; + + protected final TokenExtractor tokenExtractor; + + private final ImmutableList serviceAccountEmails; + + protected OidcTokenAuthenticationMechanism( + ImmutableList serviceAccountEmails, + TokenVerifier tokenVerifier, + TokenExtractor tokenExtractor) { + this.serviceAccountEmails = serviceAccountEmails; + this.tokenVerifier = tokenVerifier; + this.tokenExtractor = tokenExtractor; + } + + @Override + public AuthResult authenticate(HttpServletRequest request) { + if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST) + && authResultForTesting != null) { + logger.atWarning().log("Using AuthResult %s for testing.", authResultForTesting); + return authResultForTesting; + } + String rawIdToken = tokenExtractor.extract(request); + if (rawIdToken == null) { + return AuthResult.NOT_AUTHENTICATED; + } + JsonWebSignature token; + try { + token = tokenVerifier.verify(rawIdToken); + } catch (Exception e) { + logger.atInfo().withCause(e).log("Error when verifying access token"); + return AuthResult.NOT_AUTHENTICATED; + } + String email = (String) token.getPayload().get("email"); + if (email == null) { + logger.atWarning().log("No email address from the OIDC token:\n%s", token.getPayload()); + return AuthResult.NOT_AUTHENTICATED; + } + Optional maybeUser = UserDao.loadUser(email); + if (maybeUser.isPresent()) { + return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(maybeUser.get())); + } + logger.atInfo().log("No end user found for email address %s", email); + if (serviceAccountEmails.stream().anyMatch(e -> e.equals(email))) { + return AuthResult.create(APP); + } + logger.atInfo().log("No service account found for email address %s", email); + logger.atWarning().log( + "The email address %s is not tied to a principal with access to Nomulus", email); + return AuthResult.NOT_AUTHENTICATED; + } + + @VisibleForTesting + public static void setAuthResultForTesting(@Nullable AuthResult authResult) { + authResultForTesting = authResult; + } + + @VisibleForTesting + public static void unsetAuthResultForTesting() { + authResultForTesting = null; + } + + @FunctionalInterface + protected interface TokenExtractor { + @Nullable + String extract(HttpServletRequest request); + } + + /** + * A mechanism to authenticate HTTP requests that have gone through the GCP Identity-Aware Proxy. + * + *

When the user logs in, IAP provides a JWT in the {@code X-Goog-IAP-JWT-Assertion} header. + * This header is included on all requests to IAP-enabled services (which should be all of them + * that receive requests from the front end). The token verification libraries ensure that the + * signed token has the proper audience and issuer. + * + * @see the documentation on GCP + * IAP's signed headers for more information. + */ + static class IapOidcAuthenticationMechanism extends OidcTokenAuthenticationMechanism { + + @Inject + protected IapOidcAuthenticationMechanism( + @Config("serviceAccountEmails") ImmutableList serviceAccountEmails, + @IapOidc TokenVerifier tokenVerifier, + @IapOidc TokenExtractor tokenExtractor) { + super(serviceAccountEmails, tokenVerifier, tokenExtractor); + } + } + + /** + * A mechanism to authenticate HTTP requests with an OIDC token as a bearer token. + * + *

If the endpoint is not behind IAP, we can try to authenticate the OIDC token supplied in the + * request header directly. Ideally we would like all endpoints to be behind IAP, but being able + * to authenticate the token directly provides us with the flexibility to do away with OAuth-based + * {@link OAuthAuthenticationMechanism} that is tied to App Engine runtime without having to turn + * on IAP, which is an all-or-nothing switch for each GAE service (i.e. no way to turn it on only + * for certain GAE endpoints). + * + *

Note that this mechanism will try to first extract the token under the "proxy-authorization" + * header, before trying "authorization". This is because currently the GAE OAuth service always + * uses "authorization", and we would like to provide a way for both auth mechanisms to be working + * at the same time for the same request. + * + * @see Bearer Token Usage + */ + static class RegularOidcAuthenticationMechanism extends OidcTokenAuthenticationMechanism { + + @Inject + protected RegularOidcAuthenticationMechanism( + @Config("serviceAccountEmails") ImmutableList serviceAccountEmails, + @RegularOidc TokenVerifier tokenVerifier, + @RegularOidc TokenExtractor tokenExtractor) { + super(serviceAccountEmails, tokenVerifier, tokenExtractor); + } + } +} diff --git a/core/src/main/java/google/registry/request/auth/RequestAuthenticator.java b/core/src/main/java/google/registry/request/auth/RequestAuthenticator.java index 54b56ef5b..1c02c533c 100644 --- a/core/src/main/java/google/registry/request/auth/RequestAuthenticator.java +++ b/core/src/main/java/google/registry/request/auth/RequestAuthenticator.java @@ -16,11 +16,12 @@ package google.registry.request.auth; import static com.google.common.base.Preconditions.checkArgument; -import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import com.google.common.flogger.FluentLogger; -import com.google.errorprone.annotations.Immutable; +import google.registry.request.auth.AuthSettings.AuthLevel; +import google.registry.request.auth.AuthSettings.AuthMethod; +import google.registry.request.auth.AuthSettings.UserPolicy; import java.util.Optional; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -44,57 +45,6 @@ public class RequestAuthenticator { this.legacyAuthenticationMechanism = legacyAuthenticationMechanism; } - /** - * Parameters used to configure the authenticator. - * - * AuthSettings shouldn't be used directly, instead - use one of the predefined {@link Auth} enum - * values. - */ - @Immutable - @AutoValue - public abstract static class AuthSettings { - - public abstract ImmutableList methods(); - public abstract AuthLevel minimumLevel(); - public abstract UserPolicy userPolicy(); - - static AuthSettings create( - ImmutableList methods, AuthLevel minimumLevel, UserPolicy userPolicy) { - return new AutoValue_RequestAuthenticator_AuthSettings(methods, minimumLevel, userPolicy); - } - } - - /** Available methods for authentication. */ - public enum AuthMethod { - - /** App Engine internal authentication. Must always be provided as the first method. */ - INTERNAL, - - /** Authentication methods suitable for API-style access, such as OAuth 2. */ - API, - - /** Legacy authentication using cookie-based App Engine Users API. Must come last if present. */ - LEGACY - } - - /** User authorization policy options. */ - public enum UserPolicy { - - /** This action ignores end users; the only configured auth method must be INTERNAL. */ - IGNORED, - - /** No user policy is enforced; anyone can access this action. */ - PUBLIC, - - /** - * If there is a user, it must be an admin, as determined by isUserAdmin(). - * - *

Note that, according to App Engine, anybody with access to the app in the GCP Console, - * including editors and viewers, is an admin. - */ - ADMIN - } - /** * Attempts to authenticate and authorize the user, according to the settings of the action. * @@ -169,7 +119,7 @@ public class RequestAuthenticator { return authResult; } break; - // API-based user authentication mechanisms, such as OAuth + // API-based user authentication mechanisms, such as OAuth case API: // checkAuthConfig will have insured that the user policy is not IGNORED. for (AuthenticationMechanism authMechanism : apiAuthenticationMechanisms) { @@ -181,7 +131,7 @@ public class RequestAuthenticator { } } break; - // Legacy authentication via UserService + // Legacy authentication via UserService case LEGACY: // checkAuthConfig will have insured that the user policy is not IGNORED. authResult = legacyAuthenticationMechanism.authenticate(req); @@ -209,7 +159,7 @@ public class RequestAuthenticator { "Actions with INTERNAL auth method may not require USER auth level"); checkArgument( !(auth.userPolicy().equals(UserPolicy.IGNORED) - && !authMethods.equals(ImmutableList.of(AuthMethod.INTERNAL))), + && !authMethods.equals(ImmutableList.of(AuthMethod.INTERNAL))), "Actions with auth methods beyond INTERNAL must not specify the IGNORED user policy"); } } diff --git a/core/src/main/java/google/registry/request/auth/ServiceAccountAuthenticationMechanism.java b/core/src/main/java/google/registry/request/auth/ServiceAccountAuthenticationMechanism.java deleted file mode 100644 index bf20d49c3..000000000 --- a/core/src/main/java/google/registry/request/auth/ServiceAccountAuthenticationMechanism.java +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2023 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.request.auth; - -import static com.google.common.net.HttpHeaders.AUTHORIZATION; -import static google.registry.request.auth.AuthLevel.APP; - -import com.google.auth.oauth2.TokenVerifier; -import com.google.common.collect.ImmutableList; -import google.registry.config.RegistryConfig.Config; -import google.registry.request.auth.AuthModule.ServiceAccount; -import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; - -/** - * A way to authenticate HTTP requests signed by Service Account - * - *

Currently used by cloud scheduler service account - */ -public class ServiceAccountAuthenticationMechanism extends IdTokenAuthenticationBase { - - private static final String BEARER_PREFIX = "Bearer "; - - private final ImmutableList serviceAccountEmails; - - @Inject - public ServiceAccountAuthenticationMechanism( - @ServiceAccount TokenVerifier tokenVerifier, - @Config("serviceAccountEmails") ImmutableList serviceAccountEmails) { - super(tokenVerifier); - this.serviceAccountEmails = serviceAccountEmails; - } - - @Override - String rawTokenFromRequest(HttpServletRequest request) { - String rawToken = request.getHeader(AUTHORIZATION); - if (rawToken != null && rawToken.startsWith(BEARER_PREFIX)) { - return rawToken.substring(BEARER_PREFIX.length()); - } - return null; - } - - @Override - AuthResult authResultFromEmail(String emailAddress) { - if (serviceAccountEmails.stream().anyMatch(e -> e.equals(emailAddress))) { - return AuthResult.create(APP); - } else { - return AuthResult.NOT_AUTHENTICATED; - } - } -} diff --git a/core/src/main/java/google/registry/tools/RequestFactoryModule.java b/core/src/main/java/google/registry/tools/RequestFactoryModule.java index 2c8099228..cf6020584 100644 --- a/core/src/main/java/google/registry/tools/RequestFactoryModule.java +++ b/core/src/main/java/google/registry/tools/RequestFactoryModule.java @@ -48,8 +48,8 @@ class RequestFactoryModule { * *

If we need to have an IAP-enabled audience, we can use the existing refresh token and the * IAP client ID audience to request an IAP-enabled ID token. This token is read and used by - * {@link google.registry.request.auth.IapHeaderAuthenticationMechanism}, and it requires that the - * user have a {@link google.registry.model.console.User} object present in the database. + * {@link IapHeaderAuthenticationMechanismMechanism}, and it requires that the user have a {@link + * google.registry.model.console.User} object present in the database. */ private static final GenericUrl TOKEN_SERVER_URL = new GenericUrl(URI.create("https://oauth2.googleapis.com/token")); diff --git a/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java b/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java index d268b9f12..f8fb34037 100644 --- a/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java +++ b/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java @@ -27,8 +27,8 @@ import com.google.gson.JsonObject; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.request.Actions; -import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.UserAuthInfo; import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; diff --git a/core/src/test/java/google/registry/request/RequestHandlerTest.java b/core/src/test/java/google/registry/request/RequestHandlerTest.java index 05ac35cae..643257832 100644 --- a/core/src/test/java/google/registry/request/RequestHandlerTest.java +++ b/core/src/test/java/google/registry/request/RequestHandlerTest.java @@ -32,8 +32,8 @@ import static org.mockito.Mockito.when; import com.google.appengine.api.users.User; import com.google.common.testing.NullPointerTester; import google.registry.request.HttpException.ServiceUnavailableException; -import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.RequestAuthenticator; import google.registry.request.auth.UserAuthInfo; import java.io.PrintWriter; diff --git a/core/src/test/java/google/registry/request/auth/AuthenticatedRegistrarAccessorTest.java b/core/src/test/java/google/registry/request/auth/AuthenticatedRegistrarAccessorTest.java index 7cf8200e3..c1f594061 100644 --- a/core/src/test/java/google/registry/request/auth/AuthenticatedRegistrarAccessorTest.java +++ b/core/src/test/java/google/registry/request/auth/AuthenticatedRegistrarAccessorTest.java @@ -40,6 +40,7 @@ import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar.State; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException; import google.registry.util.JdkLoggerConfig; import java.util.Optional; diff --git a/core/src/test/java/google/registry/request/auth/IapHeaderAuthenticationMechanismTest.java b/core/src/test/java/google/registry/request/auth/IapHeaderAuthenticationMechanismTest.java deleted file mode 100644 index de1603bb8..000000000 --- a/core/src/test/java/google/registry/request/auth/IapHeaderAuthenticationMechanismTest.java +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2022 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.request.auth; - -import static com.google.common.truth.Truth.assertThat; -import static google.registry.testing.DatabaseHelper.insertInDb; -import static org.mockito.Mockito.when; - -import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; -import com.google.api.client.json.webtoken.JsonWebSignature; -import com.google.api.client.json.webtoken.JsonWebSignature.Header; -import com.google.auth.oauth2.TokenVerifier; -import com.google.common.truth.Truth8; -import google.registry.model.console.GlobalRole; -import google.registry.model.console.User; -import google.registry.model.console.UserRoles; -import google.registry.persistence.transaction.JpaTestExtensions; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -/** Tests for {@link IapHeaderAuthenticationMechanism}. */ -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -public class IapHeaderAuthenticationMechanismTest { - - @RegisterExtension - public final JpaTestExtensions.JpaUnitTestExtension jpaExtension = - new JpaTestExtensions.Builder().withEntityClass(User.class).buildUnitTestExtension(); - - @Mock private TokenVerifier tokenVerifier; - @Mock private HttpServletRequest request; - - private JsonWebSignature token; - private IapHeaderAuthenticationMechanism authenticationMechanism; - - @BeforeEach - void beforeEach() throws Exception { - authenticationMechanism = new IapHeaderAuthenticationMechanism(tokenVerifier); - when(request.getHeader("X-Goog-IAP-JWT-Assertion")).thenReturn("jwtValue"); - Payload payload = new Payload(); - payload.setEmail("email@email.com"); - payload.setSubject("gaiaId"); - token = new JsonWebSignature(new Header(), payload, new byte[0], new byte[0]); - when(tokenVerifier.verify("jwtValue")).thenReturn(token); - } - - @Test - void testSuccess_validUser() throws Exception { - User user = - new User.Builder() - .setEmailAddress("email@email.com") - .setGaiaId("gaiaId") - .setUserRoles( - new UserRoles.Builder().setIsAdmin(true).setGlobalRole(GlobalRole.FTE).build()) - .build(); - insertInDb(user); - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("idToken", "asdf")}); - when(tokenVerifier.verify("asdf")).thenReturn(token); - AuthResult authResult = authenticationMechanism.authenticate(request); - assertThat(authResult.isAuthenticated()).isTrue(); - Truth8.assertThat(authResult.userAuthInfo()).isPresent(); - Truth8.assertThat(authResult.userAuthInfo().get().consoleUser()).hasValue(user); - } - - @Test - void testFailure_noCookie() { - when(request.getCookies()).thenReturn(new Cookie[0]); - assertThat(authenticationMechanism.authenticate(request).isAuthenticated()).isFalse(); - } - - @Test - void testFailure_badToken() throws Exception { - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("idToken", "asdf")}); - when(tokenVerifier.verify("asdf")).thenReturn(null); - assertThat(authenticationMechanism.authenticate(request).isAuthenticated()).isFalse(); - } - - @Test - void testFailure_errorVerifyingToken() throws Exception { - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("idToken", "asdf")}); - when(tokenVerifier.verify("asdf")).thenThrow(new TokenVerifier.VerificationException("hi")); - assertThat(authenticationMechanism.authenticate(request).isAuthenticated()).isFalse(); - } - - @Test - void testFailure_goodTokenButUnknownUser() throws Exception { - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("idToken", "asdf")}); - when(tokenVerifier.verify("asdf")).thenReturn(token); - assertThat(authenticationMechanism.authenticate(request).isAuthenticated()).isFalse(); - } -} diff --git a/core/src/test/java/google/registry/request/auth/LegacyAuthenticationMechanismTest.java b/core/src/test/java/google/registry/request/auth/LegacyAuthenticationMechanismTest.java index 57f428177..0ced9a73d 100644 --- a/core/src/test/java/google/registry/request/auth/LegacyAuthenticationMechanismTest.java +++ b/core/src/test/java/google/registry/request/auth/LegacyAuthenticationMechanismTest.java @@ -26,6 +26,7 @@ import com.google.appengine.api.users.User; import com.google.appengine.api.users.UserService; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.security.XsrfTokenManager; import google.registry.testing.FakeClock; import javax.servlet.http.HttpServletRequest; diff --git a/core/src/test/java/google/registry/request/auth/OidcTokenAuthenticationMechanismTest.java b/core/src/test/java/google/registry/request/auth/OidcTokenAuthenticationMechanismTest.java new file mode 100644 index 000000000..f2d2a65c7 --- /dev/null +++ b/core/src/test/java/google/registry/request/auth/OidcTokenAuthenticationMechanismTest.java @@ -0,0 +1,230 @@ +// Copyright 2023 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.request.auth; + +import static com.google.common.net.HttpHeaders.AUTHORIZATION; +import static com.google.common.truth.Truth.assertThat; +import static google.registry.request.auth.AuthModule.BEARER_PREFIX; +import static google.registry.request.auth.AuthModule.IAP_HEADER_NAME; +import static google.registry.request.auth.AuthModule.PROXY_HEADER_NAME; +import static google.registry.testing.DatabaseHelper.insertInDb; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; +import com.google.api.client.json.webtoken.JsonWebSignature; +import com.google.api.client.json.webtoken.JsonWebSignature.Header; +import com.google.auth.oauth2.TokenVerifier; +import com.google.auth.oauth2.TokenVerifier.VerificationException; +import com.google.common.collect.ImmutableList; +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import google.registry.config.RegistryConfig.Config; +import google.registry.model.console.GlobalRole; +import google.registry.model.console.User; +import google.registry.model.console.UserRoles; +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.request.auth.AuthSettings.AuthLevel; +import google.registry.request.auth.OidcTokenAuthenticationMechanism.IapOidcAuthenticationMechanism; +import google.registry.request.auth.OidcTokenAuthenticationMechanism.RegularOidcAuthenticationMechanism; +import javax.inject.Singleton; +import javax.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Unit tests for {@link OidcTokenAuthenticationMechanism}. */ +public class OidcTokenAuthenticationMechanismTest { + + private static final String rawToken = "this-token"; + private static final String email = "user@email.test"; + private static final String gaiaId = "gaia-id"; + private static final ImmutableList serviceAccounts = + ImmutableList.of("service@email.test", "email@service.goog"); + + private final Payload payload = new Payload(); + private final User user = + new User.Builder() + .setEmailAddress(email) + .setGaiaId(gaiaId) + .setUserRoles( + new UserRoles.Builder().setIsAdmin(true).setGlobalRole(GlobalRole.FTE).build()) + .build(); + private final JsonWebSignature jwt = + new JsonWebSignature(new Header(), payload, new byte[0], new byte[0]); + private final TokenVerifier tokenVerifier = mock(TokenVerifier.class); + private final HttpServletRequest request = mock(HttpServletRequest.class); + + private AuthResult authResult; + private OidcTokenAuthenticationMechanism authenticationMechanism = + new OidcTokenAuthenticationMechanism(serviceAccounts, tokenVerifier, e -> rawToken) {}; + + @RegisterExtension + public final JpaTestExtensions.JpaUnitTestExtension jpaExtension = + new JpaTestExtensions.Builder().withEntityClass(User.class).buildUnitTestExtension(); + + @BeforeEach + void beforeEach() throws Exception { + when(tokenVerifier.verify(eq(rawToken))).thenReturn(jwt); + payload.setEmail(email); + payload.setSubject(gaiaId); + insertInDb(user); + } + + @AfterEach + void afterEach() { + OidcTokenAuthenticationMechanism.unsetAuthResultForTesting(); + } + + @Test + void testAuthResultBypass() { + OidcTokenAuthenticationMechanism.setAuthResultForTesting(AuthResult.create(AuthLevel.APP)); + assertThat(authenticationMechanism.authenticate(null)) + .isEqualTo(AuthResult.create(AuthLevel.APP)); + } + + @Test + void testAuthenticate_noTokenFromRequest() { + authenticationMechanism = + new OidcTokenAuthenticationMechanism(serviceAccounts, tokenVerifier, e -> null) {}; + authResult = authenticationMechanism.authenticate(request); + assertThat(authResult).isEqualTo(AuthResult.NOT_AUTHENTICATED); + } + + @Test + void testAuthenticate_invalidToken() throws Exception { + when(tokenVerifier.verify(eq(rawToken))).thenThrow(new VerificationException("Bad token")); + authResult = authenticationMechanism.authenticate(request); + assertThat(authResult).isEqualTo(AuthResult.NOT_AUTHENTICATED); + } + + @Test + void testAuthenticate_noEmailAddress() throws Exception { + payload.setEmail(null); + authResult = authenticationMechanism.authenticate(request); + assertThat(authResult).isEqualTo(AuthResult.NOT_AUTHENTICATED); + } + + @Test + void testAuthenticate_user() throws Exception { + authResult = authenticationMechanism.authenticate(request); + assertThat(authResult.isAuthenticated()).isTrue(); + assertThat(authResult.authLevel()).isEqualTo(AuthLevel.USER); + assertThat(authResult.userAuthInfo().get().consoleUser().get()).isEqualTo(user); + } + + @Test + void testAuthenticate_serviceAccount() throws Exception { + payload.setEmail("service@email.test"); + authResult = authenticationMechanism.authenticate(request); + assertThat(authResult.isAuthenticated()).isTrue(); + assertThat(authResult.authLevel()).isEqualTo(AuthLevel.APP); + } + + @Test + void testAuthenticate_bothUserAndServiceAccount() throws Exception { + User serviceUser = + new User.Builder() + .setEmailAddress("service@email.test") + .setGaiaId("service-gaia-id") + .setUserRoles( + new UserRoles.Builder().setIsAdmin(true).setGlobalRole(GlobalRole.FTE).build()) + .build(); + insertInDb(serviceUser); + payload.setEmail("service@email.test"); + authResult = authenticationMechanism.authenticate(request); + assertThat(authResult.isAuthenticated()).isTrue(); + assertThat(authResult.authLevel()).isEqualTo(AuthLevel.USER); + assertThat(authResult.userAuthInfo().get().consoleUser().get()).isEqualTo(serviceUser); + } + + @Test + void testAuthenticate_unknownEmailAddress() throws Exception { + payload.setEmail("bad-guy@evil.real"); + authResult = authenticationMechanism.authenticate(request); + assertThat(authResult).isEqualTo(AuthResult.NOT_AUTHENTICATED); + } + + @Test + void testIap_tokenExtractor() throws Exception { + useIapOidcMechanism(); + when(request.getHeader(IAP_HEADER_NAME)).thenReturn(rawToken); + assertThat(authenticationMechanism.tokenExtractor.extract(request)).isEqualTo(rawToken); + } + + @Test + void testRegular_tokenExtractor() throws Exception { + useRegularOidcMechanism(); + // The token does not have the "Bearer " prefix. + when(request.getHeader(PROXY_HEADER_NAME)).thenReturn(rawToken); + assertThat(authenticationMechanism.tokenExtractor.extract(request)).isNull(); + + // The token is in the correct format. + when(request.getHeader(PROXY_HEADER_NAME)) + .thenReturn(String.format("%s%s", BEARER_PREFIX, rawToken)); + assertThat(authenticationMechanism.tokenExtractor.extract(request)).isEqualTo(rawToken); + + // The token is in the correct format, and under the alternative header. + when(request.getHeader(PROXY_HEADER_NAME)).thenReturn(null); + when(request.getHeader(AUTHORIZATION)) + .thenReturn(String.format("%s%s", BEARER_PREFIX, rawToken)); + assertThat(authenticationMechanism.tokenExtractor.extract(request)).isEqualTo(rawToken); + } + + private void useIapOidcMechanism() { + TestComponent component = DaggerOidcTokenAuthenticationMechanismTest_TestComponent.create(); + authenticationMechanism = component.iapOidcAuthenticationMechanism(); + } + + private void useRegularOidcMechanism() { + TestComponent component = DaggerOidcTokenAuthenticationMechanismTest_TestComponent.create(); + authenticationMechanism = component.regularOidcAuthenticationMechanism(); + } + + @Singleton + @Component(modules = {AuthModule.class, TestModule.class}) + interface TestComponent { + IapOidcAuthenticationMechanism iapOidcAuthenticationMechanism(); + + RegularOidcAuthenticationMechanism regularOidcAuthenticationMechanism(); + } + + @Module + static class TestModule { + @Provides + @Singleton + @Config("projectIdNumber") + long provideProjectIdNumber() { + return 12345; + } + + @Provides + @Singleton + @Config("projectId") + String provideProjectId() { + return "my-project"; + } + + @Provides + @Singleton + @Config("serviceAccountEmails") + ImmutableList provideServiceAccountEmails() { + return serviceAccounts; + } + } +} diff --git a/core/src/test/java/google/registry/request/auth/RequestAuthenticatorTest.java b/core/src/test/java/google/registry/request/auth/RequestAuthenticatorTest.java index 4757766ff..7b9940809 100644 --- a/core/src/test/java/google/registry/request/auth/RequestAuthenticatorTest.java +++ b/core/src/test/java/google/registry/request/auth/RequestAuthenticatorTest.java @@ -28,9 +28,9 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; -import google.registry.request.auth.RequestAuthenticator.AuthMethod; -import google.registry.request.auth.RequestAuthenticator.AuthSettings; -import google.registry.request.auth.RequestAuthenticator.UserPolicy; +import google.registry.request.auth.AuthSettings.AuthLevel; +import google.registry.request.auth.AuthSettings.AuthMethod; +import google.registry.request.auth.AuthSettings.UserPolicy; import google.registry.security.XsrfTokenManager; import google.registry.testing.FakeClock; import google.registry.testing.FakeOAuthService; @@ -52,50 +52,42 @@ class RequestAuthenticatorTest { AuthSettings.create( ImmutableList.of(AuthMethod.INTERNAL), AuthLevel.NONE, UserPolicy.IGNORED); - private static final AuthSettings AUTH_INTERNAL_OR_ADMIN = AuthSettings.create( - ImmutableList.of(AuthMethod.INTERNAL), - AuthLevel.APP, - UserPolicy.IGNORED); + private static final AuthSettings AUTH_INTERNAL_OR_ADMIN = + AuthSettings.create(ImmutableList.of(AuthMethod.INTERNAL), AuthLevel.APP, UserPolicy.IGNORED); - private static final AuthSettings AUTH_ANY_USER_ANY_METHOD = AuthSettings.create( - ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), - AuthLevel.USER, - UserPolicy.PUBLIC); + private static final AuthSettings AUTH_ANY_USER_ANY_METHOD = + AuthSettings.create( + ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), AuthLevel.USER, UserPolicy.PUBLIC); - private static final AuthSettings AUTH_ANY_USER_NO_LEGACY = AuthSettings.create( - ImmutableList.of(AuthMethod.API), - AuthLevel.USER, - UserPolicy.PUBLIC); + private static final AuthSettings AUTH_ANY_USER_NO_LEGACY = + AuthSettings.create(ImmutableList.of(AuthMethod.API), AuthLevel.USER, UserPolicy.PUBLIC); - private static final AuthSettings AUTH_ADMIN_USER_ANY_METHOD = AuthSettings.create( - ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), - AuthLevel.USER, - UserPolicy.ADMIN); + private static final AuthSettings AUTH_ADMIN_USER_ANY_METHOD = + AuthSettings.create( + ImmutableList.of(AuthMethod.API, AuthMethod.LEGACY), AuthLevel.USER, UserPolicy.ADMIN); - private static final AuthSettings AUTH_NO_METHODS = AuthSettings.create( - ImmutableList.of(), - AuthLevel.APP, - UserPolicy.IGNORED); + private static final AuthSettings AUTH_NO_METHODS = + AuthSettings.create(ImmutableList.of(), AuthLevel.APP, UserPolicy.IGNORED); - private static final AuthSettings AUTH_WRONG_METHOD_ORDERING = AuthSettings.create( - ImmutableList.of(AuthMethod.API, AuthMethod.INTERNAL), - AuthLevel.APP, - UserPolicy.IGNORED); + private static final AuthSettings AUTH_WRONG_METHOD_ORDERING = + AuthSettings.create( + ImmutableList.of(AuthMethod.API, AuthMethod.INTERNAL), AuthLevel.APP, UserPolicy.IGNORED); - private static final AuthSettings AUTH_DUPLICATE_METHODS = AuthSettings.create( - ImmutableList.of(AuthMethod.INTERNAL, AuthMethod.API, AuthMethod.API), - AuthLevel.APP, - UserPolicy.IGNORED); + private static final AuthSettings AUTH_DUPLICATE_METHODS = + AuthSettings.create( + ImmutableList.of(AuthMethod.INTERNAL, AuthMethod.API, AuthMethod.API), + AuthLevel.APP, + UserPolicy.IGNORED); - private static final AuthSettings AUTH_INTERNAL_WITH_USER = AuthSettings.create( - ImmutableList.of(AuthMethod.INTERNAL, AuthMethod.API), - AuthLevel.USER, - UserPolicy.IGNORED); + private static final AuthSettings AUTH_INTERNAL_WITH_USER = + AuthSettings.create( + ImmutableList.of(AuthMethod.INTERNAL, AuthMethod.API), + AuthLevel.USER, + UserPolicy.IGNORED); - private static final AuthSettings AUTH_WRONGLY_IGNORING_USER = AuthSettings.create( - ImmutableList.of(AuthMethod.INTERNAL, AuthMethod.API), - AuthLevel.APP, - UserPolicy.IGNORED); + private static final AuthSettings AUTH_WRONGLY_IGNORING_USER = + AuthSettings.create( + ImmutableList.of(AuthMethod.INTERNAL, AuthMethod.API), AuthLevel.APP, UserPolicy.IGNORED); private final UserService mockUserService = mock(UserService.class); private final HttpServletRequest req = mock(HttpServletRequest.class); diff --git a/core/src/test/java/google/registry/request/auth/ServiceAccountAuthenticationMechanismTest.java b/core/src/test/java/google/registry/request/auth/ServiceAccountAuthenticationMechanismTest.java deleted file mode 100644 index ef0aa925e..000000000 --- a/core/src/test/java/google/registry/request/auth/ServiceAccountAuthenticationMechanismTest.java +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2023 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.request.auth; - -import static com.google.common.net.HttpHeaders.AUTHORIZATION; -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.when; - -import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; -import com.google.api.client.json.webtoken.JsonWebSignature; -import com.google.api.client.json.webtoken.JsonWebSignature.Header; -import com.google.auth.oauth2.TokenVerifier; -import com.google.common.collect.ImmutableList; -import javax.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class ServiceAccountAuthenticationMechanismTest { - - @Mock private TokenVerifier tokenVerifier; - @Mock private HttpServletRequest request; - - private JsonWebSignature token; - private ServiceAccountAuthenticationMechanism serviceAccountAuthenticationMechanism; - - @BeforeEach - void beforeEach() throws Exception { - serviceAccountAuthenticationMechanism = - new ServiceAccountAuthenticationMechanism( - tokenVerifier, ImmutableList.of("sa-prefix@email.com", "cloud-tasks@email.com")); - when(request.getHeader(AUTHORIZATION)).thenReturn("Bearer jwtValue"); - Payload payload = new Payload(); - payload.setEmail("sa-prefix@email.com"); - token = new JsonWebSignature(new Header(), payload, new byte[0], new byte[0]); - when(tokenVerifier.verify("jwtValue")).thenReturn(token); - } - - @Test - void testSuccess_authenticates() throws Exception { - AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request); - assertThat(authResult.isAuthenticated()).isTrue(); - assertThat(authResult.authLevel()).isEqualTo(AuthLevel.APP); - } - - @Test - void testSuccess_secondEmail() throws Exception { - Payload payload = new Payload(); - payload.setEmail("cloud-tasks@email.com"); - token = new JsonWebSignature(new Header(), payload, new byte[0], new byte[0]); - when(tokenVerifier.verify("jwtValue")).thenReturn(token); - - AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request); - assertThat(authResult.isAuthenticated()).isTrue(); - assertThat(authResult.authLevel()).isEqualTo(AuthLevel.APP); - } - - @Test - void testFails_authenticateWrongEmail() throws Exception { - token.getPayload().set("email", "not-service-account-email@email.com"); - AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request); - assertThat(authResult.isAuthenticated()).isFalse(); - } - - @Test - void testFails_authenticateWrongHeader() throws Exception { - when(request.getHeader(AUTHORIZATION)).thenReturn("BEARER asd"); - AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request); - assertThat(authResult.isAuthenticated()).isFalse(); - } -} diff --git a/core/src/test/java/google/registry/server/RegistryTestServerMain.java b/core/src/test/java/google/registry/server/RegistryTestServerMain.java index 1d5a125b1..cf525a6e5 100644 --- a/core/src/test/java/google/registry/server/RegistryTestServerMain.java +++ b/core/src/test/java/google/registry/server/RegistryTestServerMain.java @@ -24,7 +24,10 @@ import google.registry.model.console.User; import google.registry.model.console.UserRoles; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTransactionManagerExtension; -import google.registry.request.auth.IapHeaderAuthenticationMechanism; +import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; +import google.registry.request.auth.OidcTokenAuthenticationMechanism; +import google.registry.request.auth.UserAuthInfo; import google.registry.testing.UserInfo; import google.registry.testing.UserServiceExtension; import google.registry.tools.params.HostAndPortParameter; @@ -145,7 +148,8 @@ public final class RegistryTestServerMain { .setUserRoles(userRoles) .setRegistryLockPassword("registryLockPassword") .build(); - IapHeaderAuthenticationMechanism.setUserAuthInfoForTestServer(user); + OidcTokenAuthenticationMechanism.setAuthResultForTesting( + AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user))); new JpaTestExtensions.Builder().buildIntegrationTestExtension().beforeEach(null); JpaTransactionManagerExtension.loadInitialData(); System.out.printf("%sLoading fixtures...%s\n", BLUE, RESET); diff --git a/core/src/test/java/google/registry/ui/server/console/ConsoleDomainGetActionTest.java b/core/src/test/java/google/registry/ui/server/console/ConsoleDomainGetActionTest.java index 0fc7957e0..9a64f69e1 100644 --- a/core/src/test/java/google/registry/ui/server/console/ConsoleDomainGetActionTest.java +++ b/core/src/test/java/google/registry/ui/server/console/ConsoleDomainGetActionTest.java @@ -25,8 +25,8 @@ import google.registry.model.console.RegistrarRole; import google.registry.model.console.User; import google.registry.model.console.UserRoles; import google.registry.persistence.transaction.JpaTestExtensions; -import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.UserAuthInfo; import google.registry.testing.DatabaseHelper; import google.registry.testing.FakeResponse; diff --git a/core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java index 27e7195d6..bd7da1aaf 100644 --- a/core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java +++ b/core/src/test/java/google/registry/ui/server/registrar/ConsoleOteSetupActionTest.java @@ -35,8 +35,8 @@ import google.registry.model.tld.Tld; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.request.Action.Method; -import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.UserAuthInfo; import google.registry.security.XsrfTokenManager; diff --git a/core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java index 55fa141a0..ab072c419 100644 --- a/core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java +++ b/core/src/test/java/google/registry/ui/server/registrar/ConsoleRegistrarCreatorActionTest.java @@ -35,8 +35,8 @@ import google.registry.model.registrar.RegistrarPoc; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.request.Action.Method; -import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.UserAuthInfo; import google.registry.security.XsrfTokenManager; diff --git a/core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java index b3eae498d..a851c5a99 100644 --- a/core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java +++ b/core/src/test/java/google/registry/ui/server/registrar/ConsoleUiActionTest.java @@ -31,8 +31,8 @@ import com.google.common.net.MediaType; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.request.Action.Method; -import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.UserAuthInfo; import google.registry.security.XsrfTokenManager; diff --git a/core/src/test/java/google/registry/ui/server/registrar/RegistrarSettingsActionTestCase.java b/core/src/test/java/google/registry/ui/server/registrar/RegistrarSettingsActionTestCase.java index bc94bef13..809af2b5f 100644 --- a/core/src/test/java/google/registry/ui/server/registrar/RegistrarSettingsActionTestCase.java +++ b/core/src/test/java/google/registry/ui/server/registrar/RegistrarSettingsActionTestCase.java @@ -42,8 +42,8 @@ import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationT import google.registry.request.JsonActionRunner; import google.registry.request.JsonResponse; import google.registry.request.ResponseImpl; -import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.UserAuthInfo; import google.registry.testing.CloudTasksHelper; diff --git a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockGetActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockGetActionTest.java index c78b2edd2..01db1c326 100644 --- a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockGetActionTest.java +++ b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockGetActionTest.java @@ -39,8 +39,8 @@ import google.registry.model.registrar.RegistrarPoc; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; import google.registry.request.Action.Method; -import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.UserAuthInfo; import google.registry.testing.FakeClock; diff --git a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockPostActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockPostActionTest.java index 2449c98de..239464bf3 100644 --- a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockPostActionTest.java +++ b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockPostActionTest.java @@ -43,8 +43,8 @@ import google.registry.persistence.transaction.JpaTransactionManagerExtension; import google.registry.request.JsonActionRunner; import google.registry.request.JsonResponse; import google.registry.request.ResponseImpl; -import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role; import google.registry.request.auth.UserAuthInfo; diff --git a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java index c24e4166b..1ff6a1868 100644 --- a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java +++ b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java @@ -42,8 +42,8 @@ import google.registry.model.reporting.HistoryEntry; import google.registry.model.tld.Tld; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; -import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; +import google.registry.request.auth.AuthSettings.AuthLevel; import google.registry.request.auth.UserAuthInfo; import google.registry.security.XsrfTokenManager; import google.registry.testing.CloudTasksHelper;