OAuth2TokenIntrospectionHandler.java revision 5403f22d4095c9f8a934161f3f64078e5b923510
dc0d8d65d35787d30a275895ccad8d8e1b58a5ednd/*
dc0d8d65d35787d30a275895ccad8d8e1b58a5ednd * The contents of this file are subject to the terms of the Common Development and
dc0d8d65d35787d30a275895ccad8d8e1b58a5ednd * Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2015-2016 ForgeRock AS.
*/
package org.forgerock.oauth2.core;
import static org.forgerock.json.JsonValue.field;
import static org.forgerock.json.JsonValue.json;
import static org.forgerock.json.JsonValue.object;
import static org.forgerock.openam.oauth2.OAuth2Constants.IntrospectionEndpoint.ACCESS_TOKEN_TYPE;
import static org.forgerock.openam.oauth2.OAuth2Constants.IntrospectionEndpoint.ACTIVE;
import static org.forgerock.openam.oauth2.OAuth2Constants.IntrospectionEndpoint.REFRESH_TOKEN_TYPE;
import static org.forgerock.openam.oauth2.OAuth2Constants.IntrospectionEndpoint.TOKEN;
import static org.forgerock.openam.oauth2.OAuth2Constants.IntrospectionEndpoint.TOKEN_TYPE;
import static org.forgerock.openam.oauth2.OAuth2Constants.IntrospectionEndpoint.USER_ID;
import javax.inject.Inject;
import org.forgerock.guava.common.base.Joiner;
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.oauth2.core.exceptions.InvalidGrantException;
import org.forgerock.oauth2.core.exceptions.NotFoundException;
import org.forgerock.oauth2.core.exceptions.ServerException;
import org.forgerock.openam.oauth2.OAuth2Constants;
import org.forgerock.openam.oauth2.OAuth2Constants.ProofOfPossession;
import org.forgerock.openam.oauth2.OAuth2UrisFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OAuth2TokenIntrospectionHandler implements TokenIntrospectionHandler {
private static final Joiner SCOPE_JOINER = Joiner.on(' ');
private static final JsonPointer CNF_POINTER = new JsonPointer(ProofOfPossession.CNF);
private final Logger logger = LoggerFactory.getLogger("OAuth2Provider");
private final TokenStore tokenStore;
private final OAuth2UrisFactory urisFactory;
@Inject
public OAuth2TokenIntrospectionHandler(TokenStore tokenStore, OAuth2UrisFactory urisFactory) {
this.tokenStore = tokenStore;
this.urisFactory = urisFactory;
}
@Override
public JsonValue introspect(OAuth2Request request, String clientId, String tokenType, String tokenId) throws ServerException, NotFoundException {
IntrospectableToken token = getIntrospectableToken(request, tokenType, tokenId);
if (token != null && !token.isExpired()) {
if (token.getClientId().equals(clientId) &&
token.getRealm().equals(request.<String>getParameter(OAuth2Constants.Params.REALM))) {
return renderOAuth2Token(request, token);
} else {
logger.warn("Token {} didn't belong to client {}", request.getParameter(TOKEN), clientId);
}
}
return null;
}
/**
* Render the OAuth 2.0 as a JsonValue according to the specification for introspection of OAuth 2.0 tokens.
* @see <a href="http://tools.ietf.org/html/draft-ietf-oauth-introspection-04">OAuth 2.0 Token Introspection</a>
* @param request The request.
* @param token The token.
* @return A JSON representation of the token attributes.
* @throws ServerException
*/
private JsonValue renderOAuth2Token(OAuth2Request request, IntrospectableToken token)
throws ServerException, NotFoundException {
JsonValue tokenRepresentation = json(object(
field(ACTIVE, true),
field(OAuth2Constants.Params.SCOPE, SCOPE_JOINER.join(token.getScope())),
field(OAuth2Constants.Params.CLIENT_ID, token.getClientId()),
field(USER_ID, token.getResourceOwnerId()),
field(TOKEN_TYPE, token instanceof AccessToken ? ACCESS_TOKEN_TYPE : REFRESH_TOKEN_TYPE),
field(OAuth2Constants.JWTTokenParams.EXP, token.getExpiryTime() == -1 ? null :
(token.getExpiryTime() / 1000)),
field(OAuth2Constants.JWTTokenParams.SUB, token.getResourceOwnerId()),
field(OAuth2Constants.JWTTokenParams.ISS, urisFactory.get(request).getIssuer())
));
if (token instanceof AccessToken) {
JsonValue confirmationKey = ((AccessToken) token).getConfirmationKey();
if (confirmationKey.isNotNull()) {
tokenRepresentation.putPermissive(CNF_POINTER, confirmationKey.getObject());
}
}
return tokenRepresentation;
}
protected IntrospectableToken getIntrospectableToken(OAuth2Request request, String tokenType, String tokenId)
throws ServerException, NotFoundException {
IntrospectableToken token = null;
if (token == null && (tokenType == null || ACCESS_TOKEN_TYPE.equals(tokenType))) {
try {
token = tokenStore.readAccessToken(request, tokenId);
} catch (InvalidGrantException e) {
// OK, try refresh token.
logger.debug("Couldn't find access token with ID {}", tokenId, e);
}
}
if (token == null && (tokenType == null || REFRESH_TOKEN_TYPE.equals(tokenType))) {
try {
token = tokenStore.readRefreshToken(request, tokenId);
} catch (InvalidGrantException e) {
// OK, we'll return not active.
logger.debug("Couldn't find refresh token with ID {}", tokenId, e);
}
}
return token;
}
@Override
public Integer priority() {
return 10;
}
}