/*
* 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.oauth.endpoint.revoke;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes;
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
import org.wso2.carbon.identity.oauth.common.exception.OAuthClientException;
import org.wso2.carbon.identity.oauth.endpoint.OAuthRequestWrapper;
import org.wso2.carbon.identity.oauth.endpoint.util.EndpointUtil;
import org.wso2.carbon.identity.oauth2.ResponseHeader;
import org.wso2.carbon.identity.oauth2.dto.OAuthRevocationRequestDTO;
import org.wso2.carbon.identity.oauth2.dto.OAuthRevocationResponseDTO;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import java.util.Enumeration;
@Path("/revoke")
public class OAuthRevocationEndpoint {
private static final Log log = LogFactory.getLog(OAuthRevocationEndpoint.class);
private static final String TOKEN_PARAM = "token";
private static final String TOKEN_TYPE_HINT_PARAM = "token_type_hint";
private static final String CALLBACK_PARAM = "callback";
@POST
@Path("/")
@Consumes("application/x-www-form-urlencoded")
public Response revokeAccessToken(@Context HttpServletRequest request,
MultivaluedMap<String, String> paramMap) throws OAuthSystemException {
try {
PrivilegedCarbonContext.startTenantFlow();
PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext
.getThreadLocalCarbonContext();
carbonContext.setTenantId(MultitenantConstants.SUPER_TENANT_ID);
carbonContext.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);
HttpServletRequestWrapper httpRequest = new OAuthRequestWrapper(request, paramMap);
String token = httpRequest.getParameter(TOKEN_PARAM);
if (StringUtils.isBlank(token) && paramMap.get(TOKEN_PARAM) != null &&
!paramMap.get(TOKEN_PARAM).isEmpty()) {
token = paramMap.get(TOKEN_PARAM).get(0);
}
String tokenType = httpRequest.getParameter(TOKEN_TYPE_HINT_PARAM);
if (StringUtils.isBlank(tokenType) && paramMap.get(TOKEN_TYPE_HINT_PARAM) != null && !paramMap
.get(TOKEN_TYPE_HINT_PARAM).isEmpty()) {
tokenType = paramMap.get(TOKEN_TYPE_HINT_PARAM).get(0);
}
String callback = httpRequest.getParameter(CALLBACK_PARAM);
if (StringUtils.isBlank(callback) && paramMap.get(CALLBACK_PARAM) != null && !paramMap.get
(CALLBACK_PARAM).isEmpty()) {
callback = paramMap.get(CALLBACK_PARAM).get(0);
}
// extract the basic auth credentials if present in the request and use for
// authentication.
if (request.getHeader(OAuthConstants.HTTP_REQ_HEADER_AUTHZ) != null) {
try {
String[] clientCredentials = EndpointUtil.extractCredentialsFromAuthzHeader(
request.getHeader(OAuthConstants.HTTP_REQ_HEADER_AUTHZ));
// The client MUST NOT use more than one authentication method in each request
if (paramMap.containsKey(OAuth.OAUTH_CLIENT_ID) &&
paramMap.containsKey(OAuth.OAUTH_CLIENT_SECRET)) {
return handleBasicAuthFailure(callback);
}
if(clientCredentials.length != 2){
handleBasicAuthFailure(callback);
}
// add the credentials available in Authorization to the parameter map
paramMap.add(OAuth.OAUTH_CLIENT_ID, clientCredentials[0]);
paramMap.add(OAuth.OAUTH_CLIENT_SECRET, clientCredentials[1]);
} catch (OAuthClientException e) {
// malformed credential string is considered as an auth failure.
log.error("Error while extracting credentials from authorization header", e);
return handleBasicAuthFailure(callback);
}
}
try {
OAuthRevocationRequestDTO revokeRequest = new OAuthRevocationRequestDTO();
if (paramMap.get(OAuth.OAUTH_CLIENT_ID) != null) {
revokeRequest.setConsumerKey(paramMap.get(OAuth.OAUTH_CLIENT_ID).get(0));
}
if (paramMap.get(OAuth.OAUTH_CLIENT_SECRET) != null) {
revokeRequest.setConsumerSecret(paramMap.get(OAuth.OAUTH_CLIENT_SECRET).get(0));
}
if (StringUtils.isNotEmpty(token)) {
revokeRequest.setToken(token);
} else {
handleClientFailure(callback);
}
if (StringUtils.isNotEmpty(tokenType)) {
revokeRequest.setToken_type(tokenType);
}
OAuthRevocationResponseDTO oauthRevokeResp = revokeTokens(revokeRequest);
// if there BE has returned an error
if (oauthRevokeResp.getErrorMsg() != null) {
// if there is an auth failure, HTTP 401 Status Code should be sent back to the client.
if (OAuth2ErrorCodes.INVALID_CLIENT.equals(oauthRevokeResp.getErrorCode())) {
return handleBasicAuthFailure(callback);
} else if (OAuth2ErrorCodes.UNAUTHORIZED_CLIENT.equals(oauthRevokeResp.getErrorCode())) {
return handleAuthorizationFailure(callback);
}
// Otherwise send back HTTP 400 Status Code
return handleClientFailure(callback, oauthRevokeResp);
} else {
OAuthResponse response;
if (StringUtils.isNotEmpty(callback)) {
response = CarbonOAuthASResponse.revokeResponse(HttpServletResponse.SC_OK).buildBodyMessage();
response.setBody(callback + "();");
} else {
response = CarbonOAuthASResponse.revokeResponse(HttpServletResponse.SC_OK).buildBodyMessage();
}
ResponseHeader[] headers = oauthRevokeResp.getResponseHeaders();
ResponseBuilder respBuilder = Response
.status(response.getResponseStatus())
.header(OAuthConstants.HTTP_RESP_HEADER_CACHE_CONTROL,
OAuthConstants.HTTP_RESP_HEADER_VAL_CACHE_CONTROL_NO_STORE)
.header(HTTPConstants.HEADER_CONTENT_LENGTH,
"0")
.header(OAuthConstants.HTTP_RESP_HEADER_PRAGMA,
OAuthConstants.HTTP_RESP_HEADER_VAL_PRAGMA_NO_CACHE);
if (headers != null && headers.length > 0) {
for (int i = 0; i < headers.length; i++) {
if (headers[i] != null) {
respBuilder.header(headers[i].getKey(), headers[i].getValue());
}
}
}
if (StringUtils.isNotEmpty(callback)) {
respBuilder.header("Content-Type", "application/javascript");
} else {
respBuilder.header("Content-Type", "text/html");
}
return respBuilder.entity(response.getBody()).build();
}
} catch (OAuthClientException e) {
return handleServerFailure(callback, e);
}
} finally {
PrivilegedCarbonContext.endTenantFlow();
}
}
private Response handleBasicAuthFailure(String callback)
throws OAuthSystemException {
if (callback == null || "".equals(callback)) {
OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setError(OAuth2ErrorCodes.INVALID_CLIENT)
.setErrorDescription("Client Authentication failed.").buildJSONMessage();
return Response.status(response.getResponseStatus())
.header(OAuthConstants.HTTP_RESP_HEADER_AUTHENTICATE, EndpointUtil.getRealmInfo())
.header("Content-Type", "text/html")
.entity(response.getBody()).build();
} else {
OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setError(OAuth2ErrorCodes.INVALID_CLIENT).buildJSONMessage();
return Response.status(response.getResponseStatus())
.header(OAuthConstants.HTTP_RESP_HEADER_AUTHENTICATE, EndpointUtil.getRealmInfo())
.header("Content-Type", "application/javascript")
.entity(callback + "(" + response.getBody() + ");").build();
}
}
private Response handleAuthorizationFailure(String callback)
throws OAuthSystemException {
if (callback == null || "".equals(callback)) {
OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT)
.setErrorDescription("Client Authentication failed.").buildJSONMessage();
return Response.status(response.getResponseStatus())
.header(OAuthConstants.HTTP_RESP_HEADER_AUTHENTICATE, EndpointUtil.getRealmInfo())
.header("Content-Type", "text/html")
.entity(response.getBody()).build();
} else {
OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
.setError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT).buildJSONMessage();
return Response.status(response.getResponseStatus())
.header(OAuthConstants.HTTP_RESP_HEADER_AUTHENTICATE, EndpointUtil.getRealmInfo())
.header("Content-Type", "application/javascript")
.entity(callback + "(" + response.getBody() + ");").build();
}
}
private Response handleServerFailure(String callback, Exception e)
throws OAuthSystemException {
if (callback == null || "".equals(callback)) {
OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
.setError(OAuth2ErrorCodes.SERVER_ERROR)
.setErrorDescription(e.getMessage()).buildJSONMessage();
return Response.status(response.getResponseStatus())
.header("Content-Type", "text/html")
.entity(response.getBody()).build();
} else {
OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
.setError(OAuth2ErrorCodes.SERVER_ERROR).buildJSONMessage();
return Response.status(response.getResponseStatus())
.header("Content-Type", "application/javascript")
.entity(callback + "(" + response.getBody() + ");").build();
}
}
private Response handleClientFailure(String callback)
throws OAuthSystemException {
if (callback == null || "".equals(callback)) {
OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuth2ErrorCodes.INVALID_REQUEST)
.setErrorDescription("Invalid revocation request").buildJSONMessage();
return Response.status(response.getResponseStatus())
.header("Content-Type", "text/html")
.entity(response.getBody()).build();
} else {
OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuth2ErrorCodes.INVALID_REQUEST).buildJSONMessage();
return Response.status(response.getResponseStatus())
.header("Content-Type", "application/javascript")
.entity(callback + "(" + response.getBody() + ");").build();
}
}
private Response handleClientFailure(String callback, OAuthRevocationResponseDTO dto)
throws OAuthSystemException {
if (callback == null || "".equals(callback)) {
OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(dto.getErrorCode())
.setErrorDescription(dto.getErrorMsg()).buildJSONMessage();
return Response.status(response.getResponseStatus())
.header("Content-Type", "text/html")
.entity(response.getBody()).build();
} else {
OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(dto.getErrorCode()).buildJSONMessage();
return Response.status(response.getResponseStatus())
.header("Content-Type", "application/javascript")
.entity(callback + "(" + response.getBody() + ");").build();
}
}
private OAuthRevocationResponseDTO revokeTokens(OAuthRevocationRequestDTO oauthRequest)
throws OAuthClientException {
OAuthRevocationRequestDTO revokeReqDTO = new OAuthRevocationRequestDTO();
revokeReqDTO.setConsumerKey(oauthRequest.getConsumerKey());
revokeReqDTO.setConsumerSecret(oauthRequest.getConsumerSecret());
revokeReqDTO.setToken(oauthRequest.getToken());
revokeReqDTO.setToken_type(oauthRequest.getToken_type());
return EndpointUtil.getOAuth2Service().revokeTokenByOAuthClient(revokeReqDTO);
}
}