/* * Copyright 2012 SURFnet bv, The Netherlands * * 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 org.surfnet.oaaas.auth; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.surfnet.oaaas.auth.principal.BasicAuthCredentials; import org.surfnet.oaaas.model.AccessTokenRequest; import org.surfnet.oaaas.model.AuthorizationRequest; import org.surfnet.oaaas.model.Client; import org.surfnet.oaaas.repository.ClientRepository; import javax.inject.Inject; import javax.inject.Named; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.surfnet.oaaas.auth.OAuth2Validator.ValidationResponse.*; /** * Implementation of {@link OAuth2Validator} * */ @Named public class OAuth2ValidatorImpl implements OAuth2Validator { private static final Set<String> RESPONSE_TYPES = new HashSet<String>(); private static final Set<String> GRANT_TYPES = new HashSet<String>(); static { RESPONSE_TYPES.add(IMPLICIT_GRANT_RESPONSE_TYPE); RESPONSE_TYPES.add(AUTHORIZATION_CODE_GRANT_RESPONSE_TYPE); GRANT_TYPES.add(GRANT_TYPE_AUTHORIZATION_CODE); GRANT_TYPES.add(GRANT_TYPE_REFRESH_TOKEN); GRANT_TYPES.add(GRANT_TYPE_CLIENT_CREDENTIALS); GRANT_TYPES.add(GRANT_TYPE_PASSWORD); } @Inject private ClientRepository clientRepository; @Override public ValidationResponse validate(AuthorizationRequest authorizationRequest) { try { validateAuthorizationRequest(authorizationRequest); String responseType = validateResponseType(authorizationRequest); Client client = validateClient(authorizationRequest); authorizationRequest.setClient(client); String redirectUri = determineRedirectUri(authorizationRequest, responseType, client); authorizationRequest.setRedirectUri(redirectUri); List<String> scopes = determineScopes(authorizationRequest, client); authorizationRequest.setRequestedScopes(scopes); } catch (ValidationResponseException e) { return e.v; } return VALID; } protected List<String> determineScopes(AuthorizationRequest authorizationRequest, Client client) { if (CollectionUtils.isEmpty(authorizationRequest.getRequestedScopes())) { // TODO add default scopes. return null; } else { List<String> scopes = authorizationRequest.getRequestedScopes(); List<String> clientScopes = client.getScopes(); for (String scope : scopes) { if (!clientScopes.contains(scope)) { throw new ValidationResponseException(SCOPE_NOT_VALID); } } return authorizationRequest.getRequestedScopes(); } } protected String determineRedirectUri(AuthorizationRequest authorizationRequest, String responseType, Client client) { List<String> uris = client.getRedirectUris(); String redirectUri = authorizationRequest.getRedirectUri(); if (StringUtils.isBlank(redirectUri)) { if (responseType.equals(IMPLICIT_GRANT_RESPONSE_TYPE)) { throw new ValidationResponseException(IMPLICIT_GRANT_REDIRECT_URI); } else if (CollectionUtils.isEmpty(uris)) { throw new ValidationResponseException(REDIRECT_URI_REQUIRED); } else { return uris.get(0); } } else if (!AuthenticationFilter.isValidUri(redirectUri)) { throw new ValidationResponseException(REDIRECT_URI_NOT_URI); } else if (redirectUri.contains("#")) { throw new ValidationResponseException(REDIRECT_URI_FRAGMENT_COMPONENT); } else if (CollectionUtils.isNotEmpty(uris)) { boolean match = false; for (String uri : uris) { if (redirectUri.startsWith(uri)) { match = true; break; } } if (!match) { // Reset the redirect uri to first of the registered ones. Otherwise the result error response would be undesired: a (possibly on purpose) redirect to URI that is not acked. authorizationRequest.setRedirectUri(uris.get(0)); throw new ValidationResponseException(REDIRECT_URI_NOT_VALID); } } return redirectUri; } protected Client validateClient(AuthorizationRequest authorizationRequest) { String clientId = authorizationRequest.getClientId(); Client client = StringUtils.isBlank(clientId) ? null : clientRepository.findByClientId(clientId); if (client == null) { throw new ValidationResponseException(UNKNOWN_CLIENT_ID); } if (!client.isAllowedImplicitGrant() && authorizationRequest.getResponseType().equals(IMPLICIT_GRANT_RESPONSE_TYPE)) { throw new ValidationResponseException(IMPLICIT_GRANT_NOT_PERMITTED); } return client; } protected String validateResponseType(AuthorizationRequest authorizationRequest) { String responseType = authorizationRequest.getResponseType(); if (StringUtils.isBlank(responseType) || !RESPONSE_TYPES.contains(responseType)) { throw new ValidationResponseException(UNSUPPORTED_RESPONSE_TYPE); } return responseType; } protected void validateAuthorizationRequest(AuthorizationRequest authorizationRequest) { } /* (non-Javadoc) * @see org.surfnet.oaaas.auth.OAuth2Validator#validate(org.surfnet.oaaas.model.AccessTokenRequest) */ @Override public ValidationResponse validate(AccessTokenRequest request, BasicAuthCredentials clientCredentials) { try { validateGrantType(request); validateAttributes(request); validateClient(request, clientCredentials); validateAccessTokenRequest(request); } catch (ValidationResponseException e) { return e.v; } return VALID; } protected void validateGrantType(AccessTokenRequest request) { String grantType = request.getGrantType(); if (StringUtils.isBlank(grantType) || !GRANT_TYPES.contains(grantType)) { throw new ValidationResponseException(UNSUPPORTED_GRANT_TYPE); } } protected void validateAttributes(AccessTokenRequest request) { String grantType = request.getGrantType(); if (GRANT_TYPE_AUTHORIZATION_CODE.equals(grantType)) { if (StringUtils.isBlank(request.getCode())) { throw new ValidationResponseException(INVALID_GRANT_AUTHORIZATION_CODE); } } else if (GRANT_TYPE_REFRESH_TOKEN.equals(grantType)) { if (StringUtils.isBlank(request.getRefreshToken())) { throw new ValidationResponseException(INVALID_GRANT_REFRESH_TOKEN); } } else if (GRANT_TYPE_PASSWORD.equals(grantType)) { if (StringUtils.isBlank(request.getUsername()) || StringUtils.isBlank(request.getPassword())) { throw new ValidationResponseException(INVALID_GRANT_PASSWORD); } } } protected void validateClient(AccessTokenRequest accessTokenRequest, BasicAuthCredentials clientCredentials) { Client client = null; // Were we given client credentials via basic auth? if (!clientCredentials.isNull()) { // Confirm that the credentials are valid and use them to get the client if (!clientCredentials.isValid()) { throw new ValidationResponseException(UNAUTHORIZED_CLIENT); } client = getClient(clientCredentials.getUsername(), clientCredentials.getPassword(), UNAUTHORIZED_CLIENT); } else if (!StringUtils.isBlank(accessTokenRequest.getClientId())) { // Use the request parameters to obtain the client client = getClient(accessTokenRequest.getClientId(), accessTokenRequest.getClientSecret(), UNKNOWN_CLIENT_ID); } // Record the associated client accessTokenRequest.setClient(client); } private Client getClient(String clientId, String clientSecret, ValidationResponse error) { // Find the indicated client Client client = clientRepository.findByClientId(clientId); if (client == null) { throw new ValidationResponseException(error); } // Confirm that the credentials match those for the client if (!client.verifySecret(clientSecret)) { throw new ValidationResponseException(error); } return client; } protected void validateAccessTokenRequest(AccessTokenRequest accessTokenRequest) { if (accessTokenRequest.getGrantType().equals(GRANT_TYPE_CLIENT_CREDENTIALS)) { // We must have a client Client client = accessTokenRequest.getClient(); if (client == null) { throw new ValidationResponseException(INVALID_GRANT_CLIENT_CREDENTIALS); } // And the client must be allowed to perform this grant type if (!client.isAllowedClientCredentials()) { accessTokenRequest.setClient(null); throw new ValidationResponseException(CLIENT_CREDENTIALS_NOT_PERMITTED); } } } }