Package org.surfnet.oaaas.resource

Source Code of org.surfnet.oaaas.resource.TokenResource

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.resource;

import com.sun.jersey.api.client.ClientResponse.Status;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.surfnet.oaaas.auth.AbstractAuthenticator;
import org.surfnet.oaaas.auth.AbstractUserConsentHandler;
import org.surfnet.oaaas.auth.AuthenticationFilter;
import org.surfnet.oaaas.auth.OAuth2Validator;
import org.surfnet.oaaas.auth.ResourceOwnerAuthenticator;
import org.surfnet.oaaas.auth.OAuth2Validator.*;
import org.surfnet.oaaas.auth.ValidationResponseException;
import org.surfnet.oaaas.auth.principal.AuthenticatedPrincipal;
import org.surfnet.oaaas.auth.principal.BasicAuthCredentials;
import org.surfnet.oaaas.model.*;
import org.surfnet.oaaas.repository.AccessTokenRepository;
import org.surfnet.oaaas.repository.AuthorizationRequestRepository;

import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.*;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.UUID;

import static org.surfnet.oaaas.auth.OAuth2Validator.*;

/**
* Resource for handling all calls related to tokens. It adheres to <a
* href="http://tools.ietf.org/html/draft-ietf-oauth-v2"> the OAuth spec</a>.
*
*/
@Named
@Path("/")
public class TokenResource {

  public static final String BASIC_REALM = "Basic realm=\"OAuth2 Secure\"";

  public static final String WWW_AUTHENTICATE = "WWW-Authenticate";

  @Inject
  private AuthorizationRequestRepository authorizationRequestRepository;

  @Inject
  private AccessTokenRepository accessTokenRepository;

  @Inject
  private OAuth2Validator oAuth2Validator;
 
  @Inject
  private ResourceOwnerAuthenticator resourceOwnerAuthenticator;

  private static final Logger LOG = LoggerFactory.getLogger(TokenResource.class);

  /**
   * The "authorization endpoint" as described in <a
   * href="http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-3.1">Section 3.1</a> of
   * the OAuth spec.  This provides the optional GET support.  Access to this endpoint requires
   * authentication of the requestor (the resource owner) which must be accomplished via a
   * configured {@link AuthenticationFilter}.
   *
   * @param request
   *          the {@link HttpServletRequest}
   * @return the response
   */
  @GET
  @Path("/authorize")
  public Response authorizeCallbackGet(@Context HttpServletRequest request) {
    return authorizeCallback(request);
  }

  /**
   * Entry point for the authorize call which needs to return an authorization
   * code or (implicit grant) an access token.    Access to this endpoint requires
   * authentication of the requestor (the resource owner) which must be accomplished via a
   * configured {@link AuthenticationFilter}.
   *
   * @param request
   *          the {@link HttpServletRequest}
   * @return Response the response
   */
  @POST
  @Produces(MediaType.TEXT_HTML)
  @Path("/authorize")
  public Response authorizeCallback(@Context HttpServletRequest request) {
    return doProcess(request);
  }

  /**
   * Called after the user has given consent
   *
   * @param request
   *          the {@link HttpServletRequest}
   * @return Response the response
   */
  @POST
  @Produces(MediaType.TEXT_HTML)
  @Path("/consent")
  public Response consentCallback(@Context HttpServletRequest request) {
    return doProcess(request);
  }

  private Response doProcess(HttpServletRequest request) {
    AuthorizationRequest authReq = findAuthorizationRequest(request);
    if (authReq == null) {
      return serverError("Not a valid AbstractAuthenticator.AUTH_STATE on the Request");
    }
    processScopes(authReq, request);
    if (authReq.getResponseType().equals(OAuth2Validator.IMPLICIT_GRANT_RESPONSE_TYPE)) {
      AccessToken token = createAccessToken(authReq, true);
      return sendImplicitGrantResponse(authReq, token);
    } else {
      return sendAuthorizationCodeResponse(authReq);
    }
  }

  /*
   * In the user consent filter the scopes are (possible) set on the Request
   */
  private void processScopes(AuthorizationRequest authReq, HttpServletRequest request) {
    if (authReq.getClient().isSkipConsent()) {
      // return the scopes in the authentication request since the requested scopes are stored in the
      // authorizationRequest.
      authReq.setGrantedScopes(authReq.getRequestedScopes());
    } else {
      String[] scopes = (String[]) request.getAttribute(AbstractUserConsentHandler.GRANTED_SCOPES);
      if (!ArrayUtils.isEmpty(scopes)) {
        authReq.setGrantedScopes(Arrays.asList(scopes));
      } else {
        authReq.setGrantedScopes(null);
      }
    }
  }

  private AccessToken createAccessToken(AuthorizationRequest request, boolean isImplicitGrant) {
    Client client = request.getClient();
    long expireDuration = client.getExpireDuration();
    long expires = (expireDuration == 0L ? 0L : (System.currentTimeMillis() + (1000 * expireDuration)));
    String refreshToken = (client.isUseRefreshTokens() && !isImplicitGrant) ? getTokenValue(true) : null;
    AuthenticatedPrincipal principal = request.getPrincipal();
    AccessToken token = new AccessToken(getTokenValue(false), principal, client, expires, request.getGrantedScopes(), refreshToken);
    return accessTokenRepository.save(token);
  }

  private AuthorizationRequest findAuthorizationRequest(HttpServletRequest request) {
    String authState = (String) request.getAttribute(AbstractAuthenticator.AUTH_STATE);
    return authorizationRequestRepository.findByAuthState(authState);
  }

  /**
   * The "token endpoint" as described in <a
   * href="http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-3.2">Section 3.2</a> of
   * the OAuth spec.
   *
   * @param authorization the HTTP Basic auth header.
   * @param formParameters the request parameters
   * @return the response
   */
  @POST
  @Path("/token")
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes("application/x-www-form-urlencoded")
  public Response token(@HeaderParam("Authorization") String authorization,
          final MultivaluedMap<String, String> formParameters) {
    // Convert incoming parameters into internal form and validate them
    AccessTokenRequest accessTokenRequest =
            AccessTokenRequest.fromMultiValuedFormParameters(formParameters);
    BasicAuthCredentials credentials =
        BasicAuthCredentials.createCredentialsFromHeader(authorization);

    ValidationResponse vr = oAuth2Validator.validate(accessTokenRequest, credentials);
    if (!vr.valid()) {
      return sendErrorResponse(vr);
    }
   
    // The request looks valid, attempt to process
    String grantType = accessTokenRequest.getGrantType();
    AuthorizationRequest request;
    try {
      if (GRANT_TYPE_AUTHORIZATION_CODE.equals(grantType)) {
        request = authorizationCodeToken(accessTokenRequest);
      } else if (GRANT_TYPE_REFRESH_TOKEN.equals(grantType)) {
        request = refreshTokenToken(accessTokenRequest);
      } else if (GRANT_TYPE_CLIENT_CREDENTIALS.equals(grantType)) {
        request = clientCredentialToken(accessTokenRequest);
      } else if (GRANT_TYPE_PASSWORD.equals(grantType)) {
        request = passwordToken(accessTokenRequest);
      } else {
        return sendErrorResponse(ValidationResponse.UNSUPPORTED_GRANT_TYPE);
      }
    } catch (ValidationResponseException e) {
      return sendErrorResponse(e.v);
    }
    AccessToken token = createAccessToken(request, false);

    AccessTokenResponse response = new AccessTokenResponse(token.getToken(), BEARER, token.getExpiresIn(), token.getRefreshToken(), StringUtils.join(token.getScopes(), ' '));

    return Response
            .ok()
            .entity(response)
            .cacheControl(cacheControlNoStore())
            .header("Pragma", "no-cache")
            .build();

  }

  private CacheControl cacheControlNoStore() {
    CacheControl cacheControl = new CacheControl();
    cacheControl.setNoStore(true);
    return cacheControl;
  }

  private AuthorizationRequest authorizationCodeToken(AccessTokenRequest accessTokenRequest) {
    AuthorizationRequest authReq = authorizationRequestRepository.findByAuthorizationCode(accessTokenRequest.getCode());
    if (authReq == null) {
      throw new ValidationResponseException(ValidationResponse.INVALID_GRANT_AUTHORIZATION_CODE);
    }
    String uri = accessTokenRequest.getRedirectUri();
    if (!authReq.getRedirectUri().equalsIgnoreCase(uri)) {
      throw new ValidationResponseException(ValidationResponse.REDIRECT_URI_DIFFERENT);
    }
    authorizationRequestRepository.delete(authReq);
    return authReq;
  }

  private AuthorizationRequest refreshTokenToken(AccessTokenRequest accessTokenRequest) {
    AccessToken accessToken = accessTokenRepository.findByRefreshToken(accessTokenRequest.getRefreshToken());
    if (accessToken == null) {
      throw new ValidationResponseException(ValidationResponse.INVALID_GRANT_REFRESH_TOKEN);
    }
    AuthorizationRequest request = new AuthorizationRequest();
    request.setClient(accessToken.getClient());
    request.setPrincipal(accessToken.getPrincipal());
    request.setGrantedScopes(accessToken.getScopes());
    accessTokenRepository.delete(accessToken);
    return request;

  }
 
  private AuthorizationRequest clientCredentialToken(AccessTokenRequest accessTokenRequest) {
    AuthorizationRequest request =  new AuthorizationRequest();
    request.setClient(accessTokenRequest.getClient());
    // We have to construct a AuthenticatedPrincipal on-the-fly as there is only key-secret authentication
    request.setPrincipal(new AuthenticatedPrincipal(request.getClient().getClientId()));
    // Get scopes (either from request or the client's default set)
    request.setGrantedScopes(accessTokenRequest.getScopeList());
    return request;
  }
 
  private AuthorizationRequest passwordToken(AccessTokenRequest accessTokenRequest) {
    // Authenticate the resource owner
    AuthenticatedPrincipal principal =
        resourceOwnerAuthenticator.authenticate(accessTokenRequest.getUsername(),
            accessTokenRequest.getPassword());
    if (principal == null) {
      throw new ValidationResponseException(ValidationResponse.INVALID_GRANT_PASSWORD);
    }
   
    AuthorizationRequest request = new AuthorizationRequest();
    request.setClient(accessTokenRequest.getClient());
    request.setPrincipal(principal);
    request.setGrantedScopes(accessTokenRequest.getScopeList());
    return request;
  }


  private Response sendAuthorizationCodeResponse(AuthorizationRequest authReq) {
    String uri = authReq.getRedirectUri();
    String authorizationCode = getAuthorizationCodeValue();
    authReq.setAuthorizationCode(authorizationCode);
    authorizationRequestRepository.save(authReq);
    uri = uri + appendQueryMark(uri) + "code=" + authorizationCode + appendStateParameter(authReq);
    return Response
            .seeOther(UriBuilder.fromUri(uri).build())
            .cacheControl(cacheControlNoStore())
            .header("Pragma", "no-cache")
            .build();
  }

  protected String getTokenValue(boolean isRefreshToken) {
    return UUID.randomUUID().toString();
  }

  protected String getAuthorizationCodeValue() {
    return getTokenValue(false);
  }

  private Response sendErrorResponse(String error, String description, Status status) {
    if (status == Status.UNAUTHORIZED) {
      return Response.status(Status.UNAUTHORIZED).header(WWW_AUTHENTICATE, BASIC_REALM).build();
    }
    return Response.status(status).entity(new ErrorResponse(error, description)).build();
  }

  private Response sendErrorResponse(ValidationResponse response) {
    return sendErrorResponse(response.getValue(), response.getDescription(), response.getStatus());
  }

  private Response sendImplicitGrantResponse(AuthorizationRequest authReq, AccessToken accessToken) {
    String uri = authReq.getRedirectUri();
    String fragment = String.format("access_token=%s&token_type=bearer&expires_in=%s&scope=%s"
        + appendStateParameter(authReq), accessToken.getToken(), accessToken.getExpiresIn(), StringUtils.join(authReq.getGrantedScopes(), ','));
    if (authReq.getClient().isIncludePrincipal()) {
      fragment += String.format("&principal=%s", authReq.getPrincipal().getDisplayName()) ;
    }
    return Response
            .seeOther(UriBuilder.fromUri(uri)
            .fragment(fragment).build())
            .cacheControl(cacheControlNoStore())
            .header("Pragma", "no-cache")
            .build();


  }

  private String appendQueryMark(String uri) {
    return uri.contains("?") ? "&" : "?";
  }

  private String appendStateParameter(AuthorizationRequest authReq) {
    String state = authReq.getState();
    try {
      return StringUtils.isBlank(state) ? "" : "&state=".concat(URLEncoder.encode(state, "UTF-8"));
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }

  private Response serverError(String msg) {
    LOG.warn(msg);
    return Response.serverError().build();
  }

  /**
   * @param authorizationRequestRepository
   *          the authorizationRequestRepository to set
   */
  public void setAuthorizationRequestRepository(AuthorizationRequestRepository authorizationRequestRepository) {
    this.authorizationRequestRepository = authorizationRequestRepository;
  }

  /**
   * @param accessTokenRepository
   *          the accessTokenRepository to set
   */
  public void setAccessTokenRepository(AccessTokenRepository accessTokenRepository) {
    this.accessTokenRepository = accessTokenRepository;
  }

  /**
   * @param oAuth2Validator
   *          the oAuth2Validator to set
   */
  public void setoAuth2Validator(OAuth2Validator oAuth2Validator) {
    this.oAuth2Validator = oAuth2Validator;
  }

}
TOP

Related Classes of org.surfnet.oaaas.resource.TokenResource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.