/* * Copyright (c) 2013, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. 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.wso2.carbon.identity.oauth2; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.wso2.carbon.core.AbstractAdmin; import org.wso2.carbon.identity.base.IdentityException; import org.wso2.carbon.identity.oauth.OAuthUtil; import org.wso2.carbon.identity.oauth.dao.OAuthAppDO; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes; import org.wso2.carbon.identity.oauth.common.OAuthConstants; import org.wso2.carbon.identity.oauth.common.exception.InvalidOAuthClientException; import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; import org.wso2.carbon.identity.oauth.dao.OAuthAppDAO; import org.wso2.carbon.identity.oauth2.authz.AuthorizationHandlerManager; import org.wso2.carbon.identity.oauth2.dao.TokenMgtDAO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeReqDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2AuthorizeRespDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2ClientValidationResponseDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2TokenValidationRequestDTO; import org.wso2.carbon.identity.oauth2.dto.OAuth2TokenValidationResponseDTO; import org.wso2.carbon.identity.oauth2.dto.OAuthRevocationRequestDTO; import org.wso2.carbon.identity.oauth2.dto.OAuthRevocationResponseDTO; import org.wso2.carbon.identity.oauth2.model.AccessTokenDO; import org.wso2.carbon.identity.oauth2.model.RefreshTokenValidationDataDO; import org.wso2.carbon.identity.oauth2.token.AccessTokenIssuer; import org.wso2.carbon.identity.oauth2.util.OAuth2Util; import org.wso2.carbon.user.api.Claim; import org.wso2.carbon.user.core.UserStoreManager; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import java.sql.SQLIntegrityConstraintViolationException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; /** * OAuth2 Service which is used to issue authorization codes or access tokens upon authorizing by the * user and issue/validateGrant access tokens. */ @SuppressWarnings("unused") public class OAuth2Service extends AbstractAdmin { private static Log log = LogFactory.getLog(OAuth2Service.class); /** * Process the authorization request and issue an authorization code or access token depending * on the Response Type available in the request. * * @param oAuth2AuthorizeReqDTO <code>OAuth2AuthorizeReqDTO</code> containing information about the authorization * request. * @return <code>OAuth2AuthorizeRespDTO</code> instance containing the access token/authorization code * or an error code. */ public OAuth2AuthorizeRespDTO authorize(OAuth2AuthorizeReqDTO oAuth2AuthorizeReqDTO) { if (log.isDebugEnabled()) { log.debug("Authorization Request received for user : " + oAuth2AuthorizeReqDTO.getUser() + ", Client ID : " + oAuth2AuthorizeReqDTO.getConsumerKey() + ", Authorization Response Type : " + oAuth2AuthorizeReqDTO.getResponseType() + ", Requested callback URI : " + oAuth2AuthorizeReqDTO.getCallbackUrl() + ", Requested Scope : " + OAuth2Util.buildScopeString( oAuth2AuthorizeReqDTO.getScopes())); } try { AuthorizationHandlerManager authzHandlerManager = AuthorizationHandlerManager.getInstance(); return authzHandlerManager.handleAuthorization(oAuth2AuthorizeReqDTO); } catch (Exception e) { log.error("Error occurred when processing the authorization request. Returning an error back to client.", e); OAuth2AuthorizeRespDTO authorizeRespDTO = new OAuth2AuthorizeRespDTO(); authorizeRespDTO.setErrorCode(OAuth2ErrorCodes.SERVER_ERROR); authorizeRespDTO.setErrorMsg("Error occurred when processing the authorization " + "request. Returning an error back to client."); authorizeRespDTO.setCallbackURI(oAuth2AuthorizeReqDTO.getCallbackUrl()); return authorizeRespDTO; } } /** * Check Whether the provided client_id and the callback URL are valid. * * @param clientId client_id available in the request, Not null parameter. * @param callbackURI callback_uri available in the request, can be null. * @return <code>OAuth2ClientValidationResponseDTO</code> bean with validity information, * callback, App Name, Error Code and Error Message when appropriate. */ public OAuth2ClientValidationResponseDTO validateClientInfo(String clientId, String callbackURI) { OAuth2ClientValidationResponseDTO validationResponseDTO = new OAuth2ClientValidationResponseDTO(); if (log.isDebugEnabled()) { log.debug("Validate Client information request for client_id : " + clientId + " and callback_uri " + callbackURI); } try { OAuthAppDAO oAuthAppDAO = new OAuthAppDAO(); OAuthAppDO appDO = oAuthAppDAO.getAppInformation(clientId); if(StringUtils.isEmpty(appDO.getGrantTypes()) || StringUtils.isEmpty(appDO.getCallbackUrl())){ if(log.isDebugEnabled()) { log.debug("Registered App found for the given Client Id : " + clientId + " ,App Name : " + appDO .getApplicationName() + ", does not support the requested grant type."); } validationResponseDTO.setValidClient(false); validationResponseDTO.setErrorCode(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE); validationResponseDTO.setErrorMsg("Requested Grant type is not supported."); return validationResponseDTO; } OAuth2Util.setClientTenatId(IdentityTenantUtil.getTenantId(appDO.getUser().getTenantDomain())); // Valid Client, No callback has provided. Use the callback provided during the registration. if (callbackURI == null) { validationResponseDTO.setValidClient(true); validationResponseDTO.setCallbackURL(appDO.getCallbackUrl()); validationResponseDTO.setApplicationName(appDO.getApplicationName()); return validationResponseDTO; } if (log.isDebugEnabled()) { log.debug("Registered App found for the given Client Id : " + clientId + " ,App Name : " + appDO .getApplicationName() + ", Callback URL : " + appDO.getCallbackUrl()); } // Valid Client with a callback url in the request. Check whether they are equal. if (appDO.getCallbackUrl().equals(callbackURI)) { validationResponseDTO.setValidClient(true); validationResponseDTO.setApplicationName(appDO.getApplicationName()); validationResponseDTO.setCallbackURL(callbackURI); return validationResponseDTO; } else { // Provided callback URL does not match the registered callback url. log.warn("Provided Callback URL does not match with the provided one."); validationResponseDTO.setValidClient(false); validationResponseDTO.setErrorCode(OAuth2ErrorCodes.INVALID_CALLBACK); validationResponseDTO.setErrorMsg("Registered callback does not match with the provided url."); return validationResponseDTO; } } catch (InvalidOAuthClientException e) { // There is no such Client ID being registered. So it is a request from an invalid client. log.error("Error while retrieving the Application Information", e); validationResponseDTO.setValidClient(false); validationResponseDTO.setErrorCode(OAuth2ErrorCodes.INVALID_CLIENT); validationResponseDTO.setErrorMsg(e.getMessage()); return validationResponseDTO; } catch (IdentityOAuth2Exception e) { log.error("Error when reading the Application Information.", e); validationResponseDTO.setValidClient(false); validationResponseDTO.setErrorCode(OAuth2ErrorCodes.SERVER_ERROR); validationResponseDTO.setErrorMsg("Error when processing the authorization request."); return validationResponseDTO; } } /** * Issue access token in exchange to an Authorization Grant. * * @param tokenReqDTO <Code>OAuth2AccessTokenReqDTO</Code> representing the Access Token request * @return <Code>OAuth2AccessTokenRespDTO</Code> representing the Access Token response */ public OAuth2AccessTokenRespDTO issueAccessToken(OAuth2AccessTokenReqDTO tokenReqDTO) { if (log.isDebugEnabled()) { log.debug("Access Token request received for Client ID " + tokenReqDTO.getClientId() + ", User ID " + tokenReqDTO.getResourceOwnerUsername() + ", Scope : " + Arrays.toString(tokenReqDTO.getScope()) + " and Grant Type : " + tokenReqDTO.getGrantType()); } try { AccessTokenIssuer tokenIssuer = AccessTokenIssuer.getInstance(); return tokenIssuer.issue(tokenReqDTO); } catch (InvalidOAuthClientException e) { if (log.isDebugEnabled()) { log.debug("Error occurred while issuing access token for Client ID : " + tokenReqDTO.getClientId() + ", User ID: " + tokenReqDTO.getResourceOwnerUsername() + ", Scope : " + Arrays.toString(tokenReqDTO.getScope()) + " and Grant Type : " + tokenReqDTO.getGrantType(), e); } OAuth2AccessTokenRespDTO tokenRespDTO = new OAuth2AccessTokenRespDTO(); tokenRespDTO.setError(true); tokenRespDTO.setErrorCode(OAuth2ErrorCodes.INVALID_CLIENT); tokenRespDTO.setErrorMsg("Invalid Client"); return tokenRespDTO; } catch (Exception e) { // in case of an error, consider it as a system error log.error("Error occurred while issuing the access token for Client ID : " + tokenReqDTO.getClientId() + ", User ID " + tokenReqDTO.getResourceOwnerUsername() + ", Scope : " + Arrays.toString(tokenReqDTO.getScope()) + " and Grant Type : " + tokenReqDTO.getGrantType(), e); OAuth2AccessTokenRespDTO tokenRespDTO = new OAuth2AccessTokenRespDTO(); tokenRespDTO.setError(true); if (e.getCause().getCause() instanceof SQLIntegrityConstraintViolationException){ tokenRespDTO.setErrorCode("sql_error"); } else { tokenRespDTO.setErrorCode(OAuth2ErrorCodes.SERVER_ERROR); } tokenRespDTO.setErrorMsg("Server Error"); return tokenRespDTO; } } /** * Revoke tokens issued to OAuth clients * * @param revokeRequestDTO DTO representing consumerKey, consumerSecret and tokens[] * @return revokeRespDTO DTO representing success or failure message */ public OAuthRevocationResponseDTO revokeTokenByOAuthClient(OAuthRevocationRequestDTO revokeRequestDTO) { //fix here remove associated cache entry TokenMgtDAO tokenMgtDAO = new TokenMgtDAO(); OAuthRevocationResponseDTO revokeResponseDTO = new OAuthRevocationResponseDTO(); try { if (StringUtils.isNotEmpty(revokeRequestDTO.getConsumerKey()) && StringUtils.isNotEmpty(revokeRequestDTO.getToken())) { boolean refreshTokenFirst = false; if (StringUtils.equals(GrantType.REFRESH_TOKEN.toString(), revokeRequestDTO.getToken_type())) { refreshTokenFirst = true; } RefreshTokenValidationDataDO refreshTokenDO = null; AccessTokenDO accessTokenDO = null; if (refreshTokenFirst) { refreshTokenDO = tokenMgtDAO .validateRefreshToken(revokeRequestDTO.getConsumerKey(), revokeRequestDTO.getToken()); if (refreshTokenDO == null || StringUtils.isEmpty(refreshTokenDO.getRefreshTokenState()) || !(OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE .equals(refreshTokenDO.getRefreshTokenState()) || OAuthConstants.TokenStates.TOKEN_STATE_EXPIRED .equals(refreshTokenDO.getRefreshTokenState()))) { accessTokenDO = tokenMgtDAO.retrieveAccessToken(revokeRequestDTO.getToken(), true); refreshTokenDO = null; } } else { accessTokenDO = tokenMgtDAO.retrieveAccessToken(revokeRequestDTO.getToken(), true); if (accessTokenDO == null) { refreshTokenDO = tokenMgtDAO .validateRefreshToken(revokeRequestDTO.getConsumerKey(), revokeRequestDTO.getToken()); if (refreshTokenDO == null || StringUtils.isEmpty(refreshTokenDO.getRefreshTokenState()) || !(OAuthConstants.TokenStates.TOKEN_STATE_ACTIVE .equals(refreshTokenDO.getRefreshTokenState()) || OAuthConstants.TokenStates.TOKEN_STATE_EXPIRED .equals(refreshTokenDO.getRefreshTokenState()))) { return revokeResponseDTO; } } } String grantType = StringUtils.EMPTY; if (accessTokenDO != null) { grantType = accessTokenDO.getGrantType(); } else if (refreshTokenDO != null) { grantType = refreshTokenDO.getGrantType(); } if (!StringUtils.equals(OAuthConstants.GrantTypes.IMPLICIT, grantType) && !OAuth2Util.authenticateClient(revokeRequestDTO.getConsumerKey(), revokeRequestDTO.getConsumerSecret())) { OAuthRevocationResponseDTO revokeRespDTO = new OAuthRevocationResponseDTO(); revokeRespDTO.setError(true); revokeRespDTO.setErrorCode(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); revokeRespDTO.setErrorMsg("Unauthorized Client"); return revokeRespDTO; } if (refreshTokenDO != null) { OAuthUtil.clearOAuthCache(revokeRequestDTO.getConsumerKey(), refreshTokenDO.getAuthorizedUser(), OAuth2Util.buildScopeString(refreshTokenDO.getScope())); OAuthUtil.clearOAuthCache(revokeRequestDTO.getConsumerKey(), refreshTokenDO.getAuthorizedUser()); OAuthUtil.clearOAuthCache(refreshTokenDO.getAccessToken()); tokenMgtDAO.revokeTokens(new String[] { refreshTokenDO.getAccessToken() }); addRevokeResponseHeaders(revokeResponseDTO, refreshTokenDO.getAccessToken(), revokeRequestDTO.getToken(), refreshTokenDO.getAuthorizedUser().toString()); } else if (accessTokenDO != null) { OAuthUtil.clearOAuthCache(revokeRequestDTO.getConsumerKey(), accessTokenDO.getAuthzUser(), OAuth2Util.buildScopeString(accessTokenDO.getScope())); OAuthUtil.clearOAuthCache(revokeRequestDTO.getConsumerKey(), accessTokenDO.getAuthzUser()); OAuthUtil.clearOAuthCache(revokeRequestDTO.getToken()); tokenMgtDAO.revokeTokens(new String[] { revokeRequestDTO.getToken() }); addRevokeResponseHeaders(revokeResponseDTO, revokeRequestDTO.getToken(), accessTokenDO.getRefreshToken(), accessTokenDO.getAuthzUser().toString()); } return revokeResponseDTO; } else { revokeResponseDTO.setError(true); revokeResponseDTO.setErrorCode(OAuth2ErrorCodes.INVALID_REQUEST); revokeResponseDTO.setErrorMsg("Invalid revocation request"); return revokeResponseDTO; } } catch (InvalidOAuthClientException e) { log.error("Unauthorized Client", e); OAuthRevocationResponseDTO revokeRespDTO = new OAuthRevocationResponseDTO(); revokeRespDTO.setError(true); revokeRespDTO.setErrorCode(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); revokeRespDTO.setErrorMsg("Unauthorized Client"); return revokeRespDTO; } catch (IdentityException e) { log.error("Error occurred while revoking authorization grant for applications", e); OAuthRevocationResponseDTO revokeRespDTO = new OAuthRevocationResponseDTO(); revokeRespDTO.setError(true); revokeRespDTO.setErrorCode(OAuth2ErrorCodes.SERVER_ERROR); revokeRespDTO.setErrorMsg("Error occurred while revoking authorization grant for applications"); return revokeRespDTO; } } /** * Returns an array of claims of the authorized user. This is for the * OpenIDConnect user-end-point implementation. * <p/> * TODO : 1. Should return the userinfo response instead. * TODO : 2. Should create another service API for userinfo endpoint * * @param accessTokenIdentifier * @return * @throws IdentityException */ public Claim[] getUserClaims(String accessTokenIdentifier) { OAuth2TokenValidationRequestDTO reqDTO = new OAuth2TokenValidationRequestDTO(); OAuth2TokenValidationRequestDTO.OAuth2AccessToken accessToken = reqDTO.new OAuth2AccessToken(); accessToken.setTokenType("bearer"); accessToken.setIdentifier(accessTokenIdentifier); reqDTO.setAccessToken(accessToken); OAuth2TokenValidationResponseDTO respDTO = new OAuth2TokenValidationService().validate(reqDTO); String username = respDTO.getAuthorizedUser(); if (username == null) { // invalid token log.debug(respDTO.getErrorMsg()); return new Claim[0]; } String[] scope = respDTO.getScope(); boolean isOICScope = false; for (String curScope : scope) { if ("openid".equals(curScope)) { isOICScope = true; } } if (!isOICScope) { log.error("AccessToken does not have the openid scope"); return new Claim[0]; } // TODO : this code is ugly String profileName = "default"; // TODO : configurable String tenantDomain = MultitenantUtils.getTenantDomain(username); String tenatUser = MultitenantUtils.getTenantAwareUsername(username); List<Claim> claimsList = new ArrayList<Claim>(); // MUST claim // http://openid.net/specs/openid-connect-basic-1_0-22.html#id_res Claim subClaim = new Claim(); subClaim.setClaimUri("sub"); subClaim.setValue(username); claimsList.add(subClaim); try { UserStoreManager userStore = IdentityTenantUtil.getRealm(tenantDomain, tenatUser) .getUserStoreManager(); // externel configured claims String[] claims = OAuthServerConfiguration.getInstance().getSupportedClaims(); if (claims != null) { Map<String, String> extClaimsMap = userStore.getUserClaimValues(username, claims, profileName); for (Map.Entry<String, String> entry : extClaimsMap.entrySet()){ Claim curClaim = new Claim(); curClaim.setClaimUri(entry.getKey()); curClaim.setValue(entry.getValue()); claimsList.add(curClaim); } } // default claims String[] defaultClaims = new String[3]; defaultClaims[0] = "http://wso2.org/claims/emailaddress"; defaultClaims[1] = "http://wso2.org/claims/givenname"; defaultClaims[2] = "http://wso2.org/claims/lastname"; String emailAddress = null; String firstName = null; String lastName = null; Map<String, String> defClaimsMap = userStore.getUserClaimValues(username, defaultClaims, profileName); if (defClaimsMap.get(defaultClaims[0]) != null) { emailAddress = defClaimsMap.get(defaultClaims[0]); Claim email = new Claim(); email.setClaimUri("email"); email.setValue(emailAddress); claimsList.add(email); Claim prefName = new Claim(); prefName.setClaimUri("preferred_username"); prefName.setValue(emailAddress.split("@")[0]); claimsList.add(prefName); } if (defClaimsMap.get(defaultClaims[1]) != null) { firstName = defClaimsMap.get(defaultClaims[1]); Claim givenName = new Claim(); givenName.setClaimUri("given_name"); givenName.setValue(firstName); claimsList.add(givenName); } if (defClaimsMap.get(defaultClaims[2]) != null) { lastName = defClaimsMap.get(defaultClaims[2]); Claim familyName = new Claim(); familyName.setClaimUri("family_name"); familyName.setValue(lastName); claimsList.add(familyName); } if (firstName != null && lastName != null) { Claim name = new Claim(); name.setClaimUri("name"); name.setValue(firstName + " " + lastName); claimsList.add(name); } } catch (Exception e) { log.error("Error while reading user claims ", e); } Claim[] allClaims = new Claim[claimsList.size()]; for (int i = 0; i < claimsList.size(); i++) { allClaims[i] = claimsList.get(i); } return allClaims; } private void addRevokeResponseHeaders(OAuthRevocationResponseDTO revokeResponseDTP, String accessToken, String refreshToken, String authorizedUser) { List<ResponseHeader> respHeaders = new ArrayList<>(); ResponseHeader header = new ResponseHeader(); header.setKey("RevokedAccessToken"); header.setValue(accessToken); respHeaders.add(header); header = new ResponseHeader(); header.setKey("AuthorizedUser"); header.setValue(authorizedUser); respHeaders.add(header); header = new ResponseHeader(); header.setKey("RevokedRefreshToken"); header.setValue(refreshToken); respHeaders.add(header); revokeResponseDTP.setResponseHeaders(respHeaders.toArray(new ResponseHeader[respHeaders.size()])); } }