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;