google-nomulus/java/google/registry/request/auth/OAuthAuthenticationMechanism.java
mountford f623d53e73 Remove invalid comment and add temp variable
It was not a problem after all to handle multiple scopes. Also added a temp variable to avoid making the same array conversion over and over.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=164002903
2017-08-29 15:34:49 -04:00

134 lines
5.8 KiB
Java

// 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;
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 com.google.appengine.api.oauth.OAuthRequestException;
import com.google.appengine.api.oauth.OAuthService;
import com.google.appengine.api.oauth.OAuthServiceFailureException;
import com.google.appengine.api.users.User;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import google.registry.config.RegistryConfig.Config;
import google.registry.util.FormattingLogger;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
/**
* OAuth authentication mechanism, using the OAuthService interface.
*
* Only OAuth version 2 is supported.
*/
public class OAuthAuthenticationMechanism implements AuthenticationMechanism {
private static final String BEARER_PREFIX = "Bearer ";
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
private final OAuthService oauthService;
/** The available OAuth scopes for which {@link OAuthService} should check. */
private final ImmutableSet<String> availableOauthScopes;
/** The OAuth scopes which must all be present for authentication to succeed. */
private final ImmutableSet<String> requiredOauthScopes;
private final ImmutableSet<String> allowedOauthClientIds;
@VisibleForTesting
@Inject
public OAuthAuthenticationMechanism(
OAuthService oauthService,
@Config("availableOauthScopes") ImmutableSet<String> availableOauthScopes,
@Config("requiredOauthScopes") ImmutableSet<String> requiredOauthScopes,
@Config("allowedOauthClientIds") ImmutableSet<String> allowedOauthClientIds) {
this.oauthService = oauthService;
this.availableOauthScopes = availableOauthScopes;
this.requiredOauthScopes = requiredOauthScopes;
this.allowedOauthClientIds = allowedOauthClientIds;
}
@Override
public AuthResult authenticate(HttpServletRequest request) {
// Make sure that there is an Authorization header in Bearer form. OAuthService also accepts
// tokens in the request body and URL string, but we should not use those, since they are more
// likely to be logged than the Authorization header. Checking to make sure there's a token also
// avoids unnecessary RPCs, since OAuthService itself does not check whether the header is
// present. In theory, there could be more than one Authorization header, but we only check the
// first one, because there's not a legitimate use case for having more than one, and
// OAuthService itself only looks at the first one anyway.
String header = request.getHeader(AUTHORIZATION);
if ((header == null) || !header.startsWith(BEARER_PREFIX)) {
logger.infofmt("missing or invalid authorization header");
return AuthResult.create(NONE);
}
// Assume that, if a bearer token is found, it's what OAuthService will use to attempt
// authentication. This is not technically guaranteed by the contract of OAuthService; see
// OAuthTokenInfo for more information.
String rawAccessToken = header.substring(BEARER_PREFIX.length());
// Get the OAuth information. The various oauthService method calls use a single cached
// authentication result, so we can call them one by one.
User currentUser;
boolean isUserAdmin;
String clientId;
ImmutableSet<String> authorizedScopes;
try {
String[] availableOauthScopeArray = availableOauthScopes.toArray(new String[0]);
currentUser = oauthService.getCurrentUser(availableOauthScopeArray);
isUserAdmin = oauthService.isUserAdmin(availableOauthScopeArray);
logger.infofmt("current user: %s (%s)", currentUser, isUserAdmin ? "admin" : "not admin");
clientId = oauthService.getClientId(availableOauthScopeArray);
logger.infofmt("client ID: %s", clientId);
authorizedScopes =
ImmutableSet.copyOf(oauthService.getAuthorizedScopes(availableOauthScopeArray));
logger.infofmt("authorized scope(s): %s", authorizedScopes);
} catch (OAuthRequestException | OAuthServiceFailureException e) {
logger.infofmt(e, "unable to get OAuth information");
return AuthResult.create(NONE);
}
if ((currentUser == null) || (clientId == null) || (authorizedScopes == null)) {
return AuthResult.create(NONE);
}
// Make sure that the client ID matches, to avoid a confused deputy attack; see:
// http://stackoverflow.com/a/17439317/1179226
if (!allowedOauthClientIds.contains(clientId)) {
logger.info("client ID is not allowed");
return AuthResult.create(NONE);
}
// Make sure that all required scopes are present.
if (!authorizedScopes.containsAll(requiredOauthScopes)) {
logger.info("required scope(s) missing");
return AuthResult.create(NONE);
}
// Create the {@link AuthResult}, including the OAuth token info.
return AuthResult.create(
USER,
UserAuthInfo.create(
currentUser,
isUserAdmin,
OAuthTokenInfo.create(
ImmutableSet.copyOf(authorizedScopes),
clientId,
rawAccessToken)));
}
}