All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cloudfoundry.identity.uaa.oauth.CheckTokenEndpoint Maven / Gradle / Ivy

/*******************************************************************************
 *     Cloud Foundry
 *     Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
 *
 *     This product is licensed to you under the Apache License, Version 2.0 (the "License").
 *     You may not use this product except in compliance with the License.
 *
 *     This product includes a number of subcomponents with
 *     separate copyright notices and license terms. Your use of these
 *     subcomponents is subject to the terms and conditions of the
 *     subcomponent's license, as noted in the LICENSE file.
 *******************************************************************************/
package org.cloudfoundry.identity.uaa.oauth;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.error.ParameterParsingException;
import org.cloudfoundry.identity.uaa.error.UaaException;
import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper;
import org.cloudfoundry.identity.uaa.oauth.token.Claims;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.TimeService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.Collections.emptyList;
import static org.springframework.util.StringUtils.commaDelimitedListToSet;
import static org.springframework.util.StringUtils.hasText;
import static org.springframework.web.bind.annotation.RequestMethod.POST;

/**
 * Controller which decodes access tokens for clients who are not able to do so
 * (or where opaque token values are used).
 */
@Controller
public class CheckTokenEndpoint implements InitializingBean {

    //Copy of the value from org.apache.Globals.PARAMETER_PARSE_FAILED_ATTR
    private static final String PARAMETER_PARSE_FAILED_ATTR = "org.apache.catalina.parameter_parse_failed";

    private ResourceServerTokenServices resourceServerTokenServices;
    private TimeService timeService;

    protected final Log logger = LogFactory.getLog(getClass());
    private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();

    public void setTokenServices(ResourceServerTokenServices resourceServerTokenServices) {
        this.resourceServerTokenServices = resourceServerTokenServices;
    }
    public void setTimeService(TimeService timeService) {
        this.timeService = timeService;
    }

    private Boolean allowQueryString = null;

    public boolean isAllowQueryString() {
        return (allowQueryString == null) ? true : allowQueryString;
    }

    public void setAllowQueryString(boolean allowQueryString) {
        this.allowQueryString = allowQueryString;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(resourceServerTokenServices, "tokenServices must be set");
    }

    @RequestMapping(value = "/check_token", method = POST)
    @ResponseBody
    public Claims checkToken(@RequestParam("token") String value,
                             @RequestParam(name = "scopes", required = false, defaultValue = "") List scopes,
                             HttpServletRequest request) throws HttpRequestMethodNotSupportedException {

        if (!hadParsedAllArgs(request)) {
            throw new ParameterParsingException();
        }

        if (hasText(request.getQueryString()) && !isAllowQueryString()) {
            logger.debug("Call to /oauth/check_token contains a query string. Aborting.");
            throw new HttpRequestMethodNotSupportedException("POST");
        }

        OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
        if (token == null) {
            throw new InvalidTokenException("Token was not recognised");
        }

        if (token.getExpiration() != null && token.getExpiration().before(timeService.getCurrentDate())) {
            throw new InvalidTokenException("Token has expired");
        }

        try {
            resourceServerTokenServices.loadAuthentication(value);
        } catch (AuthenticationException x) {
            throw new InvalidTokenException((x.getMessage()));
        }

        Claims response = getClaimsForToken(token.getValue());

        List claimScopes = response.getScope().stream().map(String::toLowerCase).collect(Collectors.toList());

        List missingScopes = new ArrayList<>();
        for(String expectedScope : scopes) {
            if (!claimScopes.contains(expectedScope.toLowerCase())) {
                missingScopes.add(expectedScope);
            }
        }

        if (!missingScopes.isEmpty()) {
            throw new InvalidScopeException("Some requested scopes are missing: " + String.join(",", missingScopes));
        }

        return response;
    }

    private boolean hadParsedAllArgs(HttpServletRequest request) {
        return request.getAttribute(PARAMETER_PARSE_FAILED_ATTR) == null;
    }

    @RequestMapping(value = "/check_token")
    @ResponseBody
    public Claims checkToken(HttpServletRequest request) throws HttpRequestMethodNotSupportedException {
        if (isAllowQueryString()) {
            String token = request.getParameter("token");
            String scope = request.getParameter("scope");
            return
                checkToken(
                    token,
                    hasText(scope) ? new LinkedList<>(commaDelimitedListToSet(scope)) : emptyList(),
                    request
                );
        } else {
            throw new HttpRequestMethodNotSupportedException(request.getMethod());
        }
    }

    private Claims getClaimsForToken(String token) {
        Jwt tokenJwt;
        try {
            tokenJwt = JwtHelper.decode(token);
        } catch (Throwable t) {
            throw new InvalidTokenException("Invalid token (could not decode): " + token);
        }

        Claims claims;
        try {
            claims = JsonUtils.readValue(tokenJwt.getClaims(), Claims.class);
        } catch (JsonUtils.JsonUtilException e) {
            throw new InvalidTokenException("Cannot read token claims", e);
        }

        return claims;
    }

    @ExceptionHandler(InvalidTokenException.class)
    public ResponseEntity handleException(Exception e) throws Exception {
        logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
        // This isn't an oauth resource, so we don't want to send an
        // unauthorized code here.
        // The client has already authenticated successfully with basic auth and
        // should just
        // get back the invalid token error.
        InvalidTokenException e400 = new InvalidTokenException(e.getMessage()) {
            @Override
            public int getHttpErrorCode() {
                return 400;
            }
        };
        return exceptionTranslator.translate(e400);
    }

    @ExceptionHandler(InvalidScopeException.class)
    public ResponseEntity handleInvalidScopeException(Exception e) throws Exception {
        logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
        return exceptionTranslator.translate(e);
    }


    @ExceptionHandler(UaaException.class)
    public ResponseEntity handleInvalidScopeSTUFF(UaaException e) throws Exception {
        logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
        return new ResponseEntity<>(e, HttpStatus.valueOf(e.getHttpStatus()));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy