/*
* 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.authz;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
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.application.authentication.framework.AuthenticatorFlowStatus;
import org.wso2.carbon.identity.application.authentication.framework.CommonAuthenticationHandler;
import org.wso2.carbon.identity.application.authentication.framework.cache.AuthenticationResultCacheEntry;
import org.wso2.carbon.identity.application.authentication.framework.config.ConfigurationFacade;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticationResult;
import org.wso2.carbon.identity.application.authentication.framework.model.CommonAuthRequestWrapper;
import org.wso2.carbon.identity.application.authentication.framework.model.CommonAuthResponseWrapper;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.common.model.Claim;
import org.wso2.carbon.identity.application.common.model.ClaimMapping;
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCache;
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCacheEntry;
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCacheKey;
import org.wso2.carbon.identity.oauth.cache.SessionDataCache;
import org.wso2.carbon.identity.oauth.cache.SessionDataCacheEntry;
import org.wso2.carbon.identity.oauth.cache.SessionDataCacheKey;
import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes;
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
import org.wso2.carbon.identity.oauth.endpoint.OAuthRequestWrapper;
import org.wso2.carbon.identity.oauth.endpoint.util.EndpointUtil;
import org.wso2.carbon.identity.oauth.endpoint.util.OpenIDConnectUserRPStore;
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
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.model.CarbonOAuthAuthzRequest;
import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
import org.wso2.carbon.registry.core.utils.UUIDGenerator;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Path("/authorize")
public class OAuth2AuthzEndpoint {
private static final Log log = LogFactory.getLog(OAuth2AuthzEndpoint.class);
public static final String APPROVE = "approve";
private boolean isCacheAvailable = false;
@GET
@Path("/")
@Consumes("application/x-www-form-urlencoded")
@Produces("text/html")
public Response authorize(@Context HttpServletRequest request, @Context HttpServletResponse response)
throws URISyntaxException {
// Setting super-tenant carbon context
PrivilegedCarbonContext.startTenantFlow();
PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
carbonContext.setTenantId(MultitenantConstants.SUPER_TENANT_ID);
carbonContext.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);
String clientId = request.getParameter("client_id");
String sessionDataKeyFromLogin = getSessionDataKey(request);
String sessionDataKeyFromConsent = request.getParameter(OAuthConstants.SESSION_DATA_KEY_CONSENT);
SessionDataCacheKey cacheKey = null;
SessionDataCacheEntry resultFromLogin = null;
SessionDataCacheEntry resultFromConsent = null;
Object flowStatus = request.getAttribute(FrameworkConstants.RequestParams.FLOW_STATUS);
String isToCommonOauth = request.getParameter(FrameworkConstants.RequestParams.TO_COMMONAUTH);
if ("true".equals(isToCommonOauth) && flowStatus == null) {
try {
return sendRequestToFramework(request, response);
} catch (ServletException | IOException e) {
log.error("Error occurred while sending request to authentication framework.");
return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).build();
}
}
if (StringUtils.isNotEmpty(sessionDataKeyFromLogin)) {
cacheKey = new SessionDataCacheKey(sessionDataKeyFromLogin);
resultFromLogin = SessionDataCache.getInstance().getValueFromCache(cacheKey);
}
if (StringUtils.isNotEmpty(sessionDataKeyFromConsent)) {
cacheKey = new SessionDataCacheKey(sessionDataKeyFromConsent);
resultFromConsent = SessionDataCache.getInstance().getValueFromCache(cacheKey);
SessionDataCache.getInstance().clearCacheEntry(cacheKey);
}
if (resultFromLogin != null && resultFromConsent != null) {
if (log.isDebugEnabled()) {
log.debug("Invalid authorization request.\'SessionDataKey\' found in request as parameter and " +
"attribute, and both have non NULL objects in cache");
}
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(
EndpointUtil.getErrorPageURL(OAuth2ErrorCodes.INVALID_REQUEST, "Invalid authorization request",
null, null))).build();
} else if (clientId == null && resultFromLogin == null && resultFromConsent == null) {
if (log.isDebugEnabled()) {
log.debug("Invalid authorization request.\'SessionDataKey\' not found in request as parameter or " +
"attribute, and client_id parameter cannot be found in request");
}
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(
EndpointUtil.getErrorPageURL(OAuth2ErrorCodes.INVALID_REQUEST, "Invalid authorization request",
null, null))).build();
} else if (sessionDataKeyFromLogin != null && resultFromLogin == null) {
if (log.isDebugEnabled()) {
log.debug("Session data not found in SessionDataCache for " + sessionDataKeyFromLogin);
}
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(
EndpointUtil.getErrorPageURL(OAuth2ErrorCodes.ACCESS_DENIED, "Session Timed Out", null, null)))
.build();
} else if (sessionDataKeyFromConsent != null && resultFromConsent == null) {
if (resultFromLogin == null) {
if (log.isDebugEnabled()) {
log.debug("Session data not found in SessionDataCache for " + sessionDataKeyFromConsent);
}
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(
EndpointUtil.getErrorPageURL(OAuth2ErrorCodes.ACCESS_DENIED, "Session Timed Out", null, null)))
.build();
} else {
sessionDataKeyFromConsent = null;
}
}
SessionDataCacheEntry sessionDataCacheEntry = null;
try {
if (clientId != null && sessionDataKeyFromLogin == null && sessionDataKeyFromConsent == null) {
// Authz request from client
String redirectURL = handleOAuthAuthorizationRequest(clientId, request);
String type = OAuthConstants.Scope.OAUTH2;
String scopes = request.getParameter(OAuthConstants.OAuth10AParams.SCOPE);
if (scopes != null && scopes.contains(OAuthConstants.Scope.OPENID)) {
type = OAuthConstants.Scope.OIDC;
}
Object attribute = request.getAttribute(FrameworkConstants.RequestParams.FLOW_STATUS);
if (attribute != null && attribute == AuthenticatorFlowStatus.SUCCESS_COMPLETED) {
try {
return sendRequestToFramework(request, response,
(String) request.getAttribute(FrameworkConstants.SESSION_DATA_KEY),
type);
} catch (ServletException | IOException e ) {
log.error("Error occurred while sending request to authentication framework.");
}
return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).build();
} else {
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(redirectURL)).build();
}
} else if (resultFromLogin != null) { // Authentication response
sessionDataCacheEntry = resultFromLogin;
OAuth2Parameters oauth2Params = sessionDataCacheEntry.getoAuth2Parameters();
AuthenticationResult authnResult = getAuthenticationResult(request, sessionDataKeyFromLogin);
if (authnResult != null) {
removeAuthenticationResult(request, sessionDataKeyFromLogin);
String redirectURL = null;
if (authnResult.isAuthenticated()) {
AuthenticatedUser authenticatedUser = authnResult.getSubject();
if (authenticatedUser.getUserAttributes() != null) {
authenticatedUser.setUserAttributes(new ConcurrentHashMap<ClaimMapping, String>(
authenticatedUser.getUserAttributes()));
}
sessionDataCacheEntry.setLoggedInUser(authenticatedUser);
sessionDataCacheEntry.setAuthenticatedIdPs(authnResult.getAuthenticatedIdPs());
SessionDataCache.getInstance().addToCache(cacheKey, sessionDataCacheEntry);
redirectURL = doUserAuthz(request, sessionDataKeyFromLogin, sessionDataCacheEntry);
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(redirectURL)).build();
} else {
OAuthProblemException oauthException = OAuthProblemException.error(
OAuth2ErrorCodes.ACCESS_DENIED, "Authentication required");
redirectURL = OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
.error(oauthException).location(oauth2Params.getRedirectURI())
.setState(oauth2Params.getState()).buildQueryMessage()
.getLocationUri();
}
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(redirectURL)).build();
} else {
String appName = sessionDataCacheEntry.getoAuth2Parameters().getApplicationName();
if (log.isDebugEnabled()) {
log.debug("Invalid authorization request. \'sessionDataKey\' attribute found but " +
"corresponding AuthenticationResult does not exist in the cache.");
}
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(EndpointUtil
.getErrorPageURL(OAuth2ErrorCodes.INVALID_REQUEST, "Invalid authorization request",
appName, null))).build();
}
} else if (resultFromConsent != null) { // Consent submission
sessionDataCacheEntry = ((SessionDataCacheEntry) resultFromConsent);
OAuth2Parameters oauth2Params = sessionDataCacheEntry.getoAuth2Parameters();
String consent = request.getParameter("consent");
if (consent != null) {
if (OAuthConstants.Consent.DENY.equals(consent)) {
OpenIDConnectUserRPStore.getInstance().putUserRPToStore(resultFromConsent.getLoggedInUser(),
resultFromConsent.getoAuth2Parameters().getApplicationName(), false, oauth2Params.getClientId());
// return an error if user denied
String denyResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
.setError(OAuth2ErrorCodes.ACCESS_DENIED)
.location(oauth2Params.getRedirectURI()).setState(oauth2Params.getState())
.buildQueryMessage().getLocationUri();
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(denyResponse)).build();
}
String redirectURL = handleUserConsent(request, consent, oauth2Params, sessionDataCacheEntry);
String authenticatedIdPs = sessionDataCacheEntry.getAuthenticatedIdPs();
if (authenticatedIdPs != null && !authenticatedIdPs.isEmpty()) {
try {
redirectURL = redirectURL + "&AuthenticatedIdPs=" + URLEncoder.encode(authenticatedIdPs
, "UTF-8");
} catch (UnsupportedEncodingException e) {
//this exception should not occur
log.error("Error while encoding the url", e);
}
}
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(redirectURL)).build();
} else {
String appName = sessionDataCacheEntry.getoAuth2Parameters().getApplicationName();
if (log.isDebugEnabled()) {
log.debug("Invalid authorization request. \'sessionDataKey\' parameter found but \'consent\' " +
"parameter could not be found in request");
}
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(
EndpointUtil.getErrorPageURL(OAuth2ErrorCodes.INVALID_REQUEST, "Invalid authorization " +
"request", appName, null)))
.build();
}
} else { // Invalid request
if (log.isDebugEnabled()) {
log.debug("Invalid authorization request");
}
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(EndpointUtil.getErrorPageURL
(OAuth2ErrorCodes.INVALID_REQUEST, "Invalid authorization request", null, null))).build();
}
} catch (OAuthProblemException e) {
if (log.isDebugEnabled()) {
log.debug(e.getError(), e);
}
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(
EndpointUtil.getErrorPageURL(OAuth2ErrorCodes.INVALID_REQUEST, e.getMessage(), null, null)))
.build();
} catch (OAuthSystemException e) {
String redirectUri = null;
if (sessionDataCacheEntry != null) {
redirectUri = sessionDataCacheEntry.getoAuth2Parameters().getRedirectURI();
}
if (log.isDebugEnabled()) {
log.debug("Server error occurred while performing authorization", e);
}
if (StringUtils.isNotEmpty(redirectUri)) {
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(EndpointUtil.getErrorPageURL
(OAuth2ErrorCodes.SERVER_ERROR, "Server error occurred while performing authorization", null,
redirectUri))).build();
} else {
return Response.status(HttpServletResponse.SC_FOUND).location(new URI(EndpointUtil.getErrorPageURL
(OAuth2ErrorCodes.SERVER_ERROR, "Server error occurred while performing authorization", null,
null))).build();
}
} finally {
if (sessionDataKeyFromConsent != null) {
/*
* TODO Cache retaining is a temporary fix. Remove after Google fixes
* http://code.google.com/p/gdata-issues/issues/detail?id=6628
*/
String retainCache = System.getProperty("retainCache");
if (retainCache == null) {
clearCacheEntry(sessionDataKeyFromConsent);
}
}
PrivilegedCarbonContext.endTenantFlow();
}
}
/**
* Remove authentication result from request
* @param req
*/
private void removeAuthenticationResult(HttpServletRequest req, String sessionDataKey) {
if(isCacheAvailable){
FrameworkUtils.removeAuthenticationResultFromCache(sessionDataKey);
}else {
req.removeAttribute(FrameworkConstants.RequestAttribute.AUTH_RESULT);
}
}
/**
* In federated and multi steps scenario there is a redirection from commonauth to samlsso so have to get
* session data key from query parameter
*
* @param req Http servlet request
* @return Session data key
*/
private String getSessionDataKey(HttpServletRequest req) {
String sessionDataKey = (String) req.getAttribute(OAuthConstants.SESSION_DATA_KEY);
if (sessionDataKey == null) {
sessionDataKey = req.getParameter(OAuthConstants.SESSION_DATA_KEY);
}
return sessionDataKey;
}
@POST
@Path("/")
@Consumes("application/x-www-form-urlencoded")
@Produces("text/html")
public Response authorizePost(@Context HttpServletRequest request,@Context HttpServletResponse response, MultivaluedMap paramMap)
throws URISyntaxException {
HttpServletRequestWrapper httpRequest = new OAuthRequestWrapper(request, paramMap);
return authorize(httpRequest, response);
}
/**
* @param consent
* @param sessionDataCacheEntry
* @return
* @throws OAuthSystemException
*/
private String handleUserConsent(HttpServletRequest request, String consent, OAuth2Parameters oauth2Params,
SessionDataCacheEntry sessionDataCacheEntry) throws OAuthSystemException {
String applicationName = sessionDataCacheEntry.getoAuth2Parameters().getApplicationName();
AuthenticatedUser loggedInUser = sessionDataCacheEntry.getLoggedInUser();
String clientId = sessionDataCacheEntry.getoAuth2Parameters().getClientId();
boolean skipConsent = EndpointUtil.getOAuthServerConfiguration().getOpenIDConnectSkipeUserConsentConfig();
if (!skipConsent) {
boolean approvedAlways =
OAuthConstants.Consent.APPROVE_ALWAYS.equals(consent) ? true : false;
if (approvedAlways) {
OpenIDConnectUserRPStore.getInstance().putUserRPToStore(loggedInUser, applicationName,
approvedAlways, clientId);
}
}
OAuthResponse oauthResponse = null;
String responseType = oauth2Params.getResponseType();
// authorizing the request
OAuth2AuthorizeRespDTO authzRespDTO = authorize(oauth2Params, sessionDataCacheEntry);
if (authzRespDTO != null && authzRespDTO.getErrorCode() == null) {
OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse
.authorizationResponse(request, HttpServletResponse.SC_FOUND);
// all went okay
if (StringUtils.isNotBlank(authzRespDTO.getAuthorizationCode())){
builder.setCode(authzRespDTO.getAuthorizationCode());
addUserAttributesToCache(sessionDataCacheEntry, authzRespDTO.getAuthorizationCode(), authzRespDTO.getCodeId());
}
if (StringUtils.isNotBlank(authzRespDTO.getAccessToken()) &&
!OAuthConstants.ID_TOKEN.equalsIgnoreCase(responseType) &&
!OAuthConstants.NONE.equalsIgnoreCase(responseType)){
builder.setAccessToken(authzRespDTO.getAccessToken());
builder.setExpiresIn(authzRespDTO.getValidityPeriod());
builder.setParam(OAuth.OAUTH_TOKEN_TYPE, "Bearer");
}
if (StringUtils.isNotBlank(authzRespDTO.getIdToken())){
builder.setParam("id_token", authzRespDTO.getIdToken());
}
if (StringUtils.isNotBlank(oauth2Params.getState())) {
builder.setParam(OAuth.OAUTH_STATE, oauth2Params.getState());
}
String redirectURL = authzRespDTO.getCallbackURI();
oauthResponse = builder.location(redirectURL).buildQueryMessage();
} else if (authzRespDTO != null && authzRespDTO.getErrorCode() != null) {
// Authorization failure due to various reasons
String errorMsg;
if (authzRespDTO.getErrorMsg() != null) {
errorMsg = authzRespDTO.getErrorMsg();
} else {
errorMsg = "Error occurred while processing the request";
}
OAuthProblemException oauthProblemException = OAuthProblemException.error(
authzRespDTO.getErrorCode(), errorMsg);
oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND).error(oauthProblemException)
.location(oauth2Params.getRedirectURI()).setState(oauth2Params.getState())
.buildQueryMessage();
} else {
// Authorization failure due to various reasons
String errorCode = OAuth2ErrorCodes.SERVER_ERROR;
String errorMsg = "Error occurred while processing the request";
OAuthProblemException oauthProblemException = OAuthProblemException.error(
errorCode, errorMsg);
oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND).error(oauthProblemException)
.location(oauth2Params.getRedirectURI()).setState(oauth2Params.getState())
.buildQueryMessage();
}
return oauthResponse.getLocationUri();
}
private void addUserAttributesToCache(SessionDataCacheEntry sessionDataCacheEntry, String code, String codeId) {
AuthorizationGrantCacheKey authorizationGrantCacheKey = new AuthorizationGrantCacheKey(code);
AuthorizationGrantCacheEntry authorizationGrantCacheEntry = new AuthorizationGrantCacheEntry(
sessionDataCacheEntry.getLoggedInUser().getUserAttributes());
String sub = sessionDataCacheEntry.getLoggedInUser().getUserAttributes().get("sub");
if(StringUtils.isBlank(sub)){
sub = sessionDataCacheEntry.getLoggedInUser().getAuthenticatedSubjectIdentifier();
}
if(StringUtils.isNotBlank(sub)){
ClaimMapping claimMapping = new ClaimMapping();
Claim claim = new Claim();
claim.setClaimUri("sub");
claimMapping.setRemoteClaim(claim);
sessionDataCacheEntry.getLoggedInUser().getUserAttributes().put(claimMapping, sub);
}
authorizationGrantCacheEntry.setNonceValue(sessionDataCacheEntry.getoAuth2Parameters().getNonce());
authorizationGrantCacheEntry.setCodeId(codeId);
AuthorizationGrantCache.getInstance().addToCacheByCode(authorizationGrantCacheKey, authorizationGrantCacheEntry);
}
/**
* http://tools.ietf.org/html/rfc6749#section-4.1.2
* <p/>
* 4.1.2.1. Error Response
* <p/>
* If the request fails due to a missing, invalid, or mismatching
* redirection URI, or if the client identifier is missing or invalid,
* the authorization server SHOULD inform the resource owner of the
* error and MUST NOT automatically redirect the user-agent to the
* invalid redirection URI.
* <p/>
* If the resource owner denies the access request or if the request
* fails for reasons other than a missing or invalid redirection URI,
* the authorization server informs the client by adding the following
* parameters to the query component of the redirection URI using the
* "application/x-www-form-urlencoded" format
*
* @param clientId
* @param req
* @return
* @throws OAuthSystemException
* @throws OAuthProblemException
*/
private String handleOAuthAuthorizationRequest(String clientId, HttpServletRequest req)
throws OAuthSystemException, OAuthProblemException {
OAuth2ClientValidationResponseDTO clientDTO = null;
String redirectUri = req.getParameter("redirect_uri");
if (StringUtils.isBlank(clientId)) {
if (log.isDebugEnabled()) {
log.debug("Client Id is not present in the authorization request");
}
return EndpointUtil.getErrorPageURL(OAuth2ErrorCodes.INVALID_REQUEST, "Client Id is not present in the " +
"authorization request", null, null);
} else if (StringUtils.isBlank(redirectUri)) {
if (log.isDebugEnabled()) {
log.debug("Redirect URI is not present in the authorization request");
}
return EndpointUtil.getErrorPageURL(OAuth2ErrorCodes.INVALID_REQUEST, "Redirect URI is not present in the" +
" authorization request", null, null);
} else {
clientDTO = validateClient(clientId, redirectUri);
}
if (!clientDTO.isValidClient()) {
return EndpointUtil.getErrorPageURL(clientDTO.getErrorCode(), clientDTO.getErrorMsg(), null, null);
}
// Now the client is valid, redirect him to the authorization page.
OAuthAuthzRequest oauthRequest = new CarbonOAuthAuthzRequest(req);
OAuth2Parameters params = new OAuth2Parameters();
params.setClientId(clientId);
params.setRedirectURI(clientDTO.getCallbackURL());
params.setResponseType(oauthRequest.getResponseType());
params.setScopes(oauthRequest.getScopes());
if (params.getScopes() == null) { // to avoid null pointers
Set<String> scopeSet = new HashSet<String>();
scopeSet.add("");
params.setScopes(scopeSet);
}
params.setState(oauthRequest.getState());
params.setApplicationName(clientDTO.getApplicationName());
// OpenID Connect specific request parameters
params.setNonce(oauthRequest.getParam(OAuthConstants.OAuth20Params.NONCE));
params.setDisplay(oauthRequest.getParam(OAuthConstants.OAuth20Params.DISPLAY));
params.setIDTokenHint(oauthRequest.getParam(OAuthConstants.OAuth20Params.ID_TOKEN_HINT));
params.setLoginHint(oauthRequest.getParam(OAuthConstants.OAuth20Params.LOGIN_HINT));
if (StringUtils.isNotBlank(oauthRequest.getParam("acr_values")) && !"null".equals(oauthRequest.getParam
("acr_values"))) {
String[] acrValues = oauthRequest.getParam("acr_values").split(" ");
LinkedHashSet list = new LinkedHashSet();
for (String acrValue : acrValues) {
list.add(acrValue);
}
params.setACRValues(list);
}
String prompt = oauthRequest.getParam(OAuthConstants.OAuth20Params.PROMPT);
params.setPrompt(prompt);
/**
* The prompt parameter can be used by the Client to make sure
* that the End-User is still present for the current session or
* to bring attention to the request. If this parameter contains
* none with any other value, an error is returned
*
* http://openid.net/specs/openid-connect-messages-
* 1_0-14.html#anchor6
*
* prompt : none
* The Authorization Server MUST NOT display any authentication or
* consent user interface pages. An error is returned if the
* End-User is not already authenticated or the Client does not have
* pre-configured consent for the requested scopes. This can be used
* as a method to check for existing authentication and/or consent.
*
* prompt : login
* The Authorization Server MUST prompt the End-User for
* reauthentication.
*
* Error : login_required
* The Authorization Server requires End-User authentication. This
* error MAY be returned when the prompt parameter in the
* Authorization Request is set to none to request that the
* Authorization Server should not display any user interfaces to
* the End-User, but the Authorization Request cannot be completed
* without displaying a user interface for user authentication.
*
*/
boolean forceAuthenticate = false;
boolean checkAuthentication = false;
// values {none, login, consent, select_profile}
boolean contains_none = (OAuthConstants.Prompt.NONE).equals(prompt);
String[] prompts = null;
if (StringUtils.isNotBlank(prompt)) {
prompts = prompt.trim().split("\\s");
contains_none = (OAuthConstants.Prompt.NONE).equals(prompt);
if (prompts.length > 1 && contains_none) {
if (log.isDebugEnabled()) {
log.debug("Invalid prompt variable combination. The value 'none' cannot be used with others " +
"prompts. Prompt: " + prompt);
}
return OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
.setError(OAuth2ErrorCodes.INVALID_REQUEST)
.setErrorDescription("Invalid prompt variable combination. The value \'none\' cannot be used " +
"with others prompts.").location(params.getRedirectURI())
.setState(params.getState()).buildQueryMessage().getLocationUri();
}
}
if ((OAuthConstants.Prompt.LOGIN).equals(prompt)) { // prompt for authentication
checkAuthentication = false;
forceAuthenticate = true;
} else if (contains_none) {
checkAuthentication = true;
forceAuthenticate = false;
} else if ((OAuthConstants.Prompt.CONSENT).equals(prompt)) {
checkAuthentication = false;
forceAuthenticate = false;
}
String sessionDataKey = UUIDGenerator.generateUUID();
SessionDataCacheKey cacheKey = new SessionDataCacheKey(sessionDataKey);
SessionDataCacheEntry sessionDataCacheEntryNew = new SessionDataCacheEntry();
sessionDataCacheEntryNew.setoAuth2Parameters(params);
sessionDataCacheEntryNew.setQueryString(req.getQueryString());
if (req.getParameterMap() != null) {
sessionDataCacheEntryNew.setParamMap(new ConcurrentHashMap<String, String[]>(req.getParameterMap()));
}
SessionDataCache.getInstance().addToCache(cacheKey, sessionDataCacheEntryNew);
try {
req.setAttribute(FrameworkConstants.RequestParams.FLOW_STATUS, AuthenticatorFlowStatus.SUCCESS_COMPLETED);
req.setAttribute(FrameworkConstants.SESSION_DATA_KEY, sessionDataKey);
return EndpointUtil.getLoginPageURL(clientId, sessionDataKey, forceAuthenticate,
checkAuthentication, oauthRequest.getScopes(), req.getParameterMap());
} catch (IdentityOAuth2Exception e) {
if (log.isDebugEnabled()) {
log.debug("Error while retrieving the login page url.", e);
}
throw new OAuthSystemException("Error when encoding login page URL");
}
}
/**
* Validates the client using the oauth2 service
*
* @param clientId
* @param callbackURL
* @return
*/
private OAuth2ClientValidationResponseDTO validateClient(String clientId, String callbackURL) {
return EndpointUtil.getOAuth2Service().validateClientInfo(clientId, callbackURL);
}
/**
* prompt : none
* The Authorization Server MUST NOT display any authentication
* or consent user interface pages. An error is returned if the
* End-User is not already authenticated or the Client does not
* have pre-configured consent for the requested scopes. This
* can be used as a method to check for existing authentication
* and/or consent.
* <p/>
* prompt : consent
* The Authorization Server MUST prompt the End-User for consent before
* returning information to the Client.
* <p/>
* prompt Error : consent_required
* The Authorization Server requires End-User consent. This
* error MAY be returned when the prompt parameter in the
* Authorization Request is set to none to request that the
* Authorization Server should not display any user
* interfaces to the End-User, but the Authorization Request
* cannot be completed without displaying a user interface
* for End-User consent.
*
* @param sessionDataCacheEntry
* @return
* @throws OAuthSystemException
*/
private String doUserAuthz(HttpServletRequest request, String sessionDataKey,
SessionDataCacheEntry sessionDataCacheEntry)
throws OAuthSystemException {
OAuth2Parameters oauth2Params = sessionDataCacheEntry.getoAuth2Parameters();
AuthenticatedUser user = sessionDataCacheEntry.getLoggedInUser();
String loggedInUser = user.getAuthenticatedSubjectIdentifier();
boolean skipConsent = EndpointUtil.getOAuthServerConfiguration().getOpenIDConnectSkipeUserConsentConfig();
// load the users approved applications to skip consent
String appName = oauth2Params.getApplicationName();
boolean hasUserApproved = OpenIDConnectUserRPStore.getInstance().hasUserApproved(user, appName,
oauth2Params.getClientId());
String consentUrl;
String errorResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
.setError(OAuth2ErrorCodes.ACCESS_DENIED)
.location(oauth2Params.getRedirectURI())
.setState(oauth2Params.getState()).buildQueryMessage()
.getLocationUri();
consentUrl = EndpointUtil.getUserConsentURL(oauth2Params, loggedInUser, sessionDataKey,
OAuth2Util.isOIDCAuthzRequest(oauth2Params.getScopes()) ? true : false);
//Skip the consent page if User has provided approve always or skip consent from file
if ((OAuthConstants.Prompt.CONSENT).equals(oauth2Params.getPrompt())) {
return consentUrl;
} else if ((OAuthConstants.Prompt.NONE).equals(oauth2Params.getPrompt())) {
//Returning error if the user has not previous session
if (sessionDataCacheEntry.getLoggedInUser() == null) {
return errorResponse;
} else {
if (skipConsent || hasUserApproved) {
return handleUserConsent(request, APPROVE, oauth2Params, sessionDataCacheEntry);
} else {
return errorResponse;
}
}
} else if (((OAuthConstants.Prompt.LOGIN).equals(oauth2Params.getPrompt()) || StringUtils.isBlank(oauth2Params.getPrompt()))) {
if (skipConsent || hasUserApproved) {
return handleUserConsent(request, APPROVE, oauth2Params, sessionDataCacheEntry);
} else {
return consentUrl;
}
} else {
return StringUtils.EMPTY;
}
}
/**
* Here we set the authenticated user to the session data
*
* @param oauth2Params
* @return
*/
private OAuth2AuthorizeRespDTO authorize(OAuth2Parameters oauth2Params
, SessionDataCacheEntry sessionDataCacheEntry) {
OAuth2AuthorizeReqDTO authzReqDTO = new OAuth2AuthorizeReqDTO();
authzReqDTO.setCallbackUrl(oauth2Params.getRedirectURI());
authzReqDTO.setConsumerKey(oauth2Params.getClientId());
authzReqDTO.setResponseType(oauth2Params.getResponseType());
authzReqDTO.setScopes(oauth2Params.getScopes().toArray(new String[oauth2Params.getScopes().size()]));
authzReqDTO.setUser(sessionDataCacheEntry.getLoggedInUser());
authzReqDTO.setACRValues(oauth2Params.getACRValues());
authzReqDTO.setNonce(oauth2Params.getNonce());
return EndpointUtil.getOAuth2Service().authorize(authzReqDTO);
}
private void clearCacheEntry(String sessionDataKey) {
if (sessionDataKey != null) {
SessionDataCacheKey cacheKey = new SessionDataCacheKey(sessionDataKey);
SessionDataCacheEntry result = SessionDataCache.getInstance().getValueFromCache(cacheKey);
if (result != null) {
SessionDataCache.getInstance().clearCacheEntry(cacheKey);
}
}
}
/**
* Get authentication result
* When using federated or multiple steps authenticators, there is a redirection from commonauth to samlsso,
* So in that case we cannot use request attribute and have to get the result from cache
*
* @param req Http servlet request
* @param sessionDataKey Session data key
* @return
*/
private AuthenticationResult getAuthenticationResult(HttpServletRequest req, String sessionDataKey) {
AuthenticationResult result = getAuthenticationResultFromRequest(req);
if (result == null) {
isCacheAvailable = true;
result = getAuthenticationResultFromCache(sessionDataKey);
}
return result;
}
private AuthenticationResult getAuthenticationResultFromCache(String sessionDataKey) {
AuthenticationResult authResult = null;
AuthenticationResultCacheEntry authResultCacheEntry = FrameworkUtils
.getAuthenticationResultFromCache(sessionDataKey);
if (authResultCacheEntry != null) {
authResult = authResultCacheEntry.getResult();
} else {
log.error("Cannot find AuthenticationResult from the cache");
}
return authResult;
}
/**
* Get authentication result from request
*
* @param request Http servlet request
* @return
*/
private AuthenticationResult getAuthenticationResultFromRequest(HttpServletRequest request) {
return (AuthenticationResult) request.getAttribute(FrameworkConstants.RequestAttribute.AUTH_RESULT);
}
/**
* In SAML there is no redirection from authentication endpoint to commonauth and it send a post request to samlsso
* servlet and sending the request to authentication framework from here, this overload method not sending
* sessionDataKey and type to commonauth that's why overloaded the method here
*
* @param request Http servlet request
* @param response Http servlet response
* @throws ServletException
* @throws IOException
*/
private Response sendRequestToFramework(HttpServletRequest request,
HttpServletResponse response) throws ServletException,IOException,URISyntaxException {
CommonAuthenticationHandler commonAuthenticationHandler = new CommonAuthenticationHandler();
CommonAuthResponseWrapper responseWrapper = new CommonAuthResponseWrapper(response);
commonAuthenticationHandler.doGet(request, responseWrapper);
Object attribute = request.getAttribute(FrameworkConstants.RequestParams.FLOW_STATUS);
if (attribute != null) {
if (attribute == AuthenticatorFlowStatus.INCOMPLETE) {
if (responseWrapper.isRedirect()) {
response.sendRedirect(responseWrapper.getRedirectURL());
} else {
return Response.status(HttpServletResponse.SC_OK).entity(responseWrapper.getResponseBody()).build();
}
} else {
return authorize(request, response);
}
} else {
request.setAttribute(FrameworkConstants.RequestParams.FLOW_STATUS, AuthenticatorFlowStatus.UNKNOWN);
return authorize(request, response);
}
return null;
}
/**
* This method use to call authentication framework directly via API other than using HTTP redirects.
* Sending wrapper request object to doGet method since other original request doesn't exist required parameters
* Doesn't check SUCCESS_COMPLETED since taking decision with INCOMPLETE status
*
*
* @param request Http Request
* @param response Http Response
* @param sessionDataKey Session data key
* @param type authenticator type
* @throws ServletException
* @throws IOException
*/
private Response sendRequestToFramework(HttpServletRequest request, HttpServletResponse response,
String sessionDataKey, String type) throws ServletException, IOException, URISyntaxException {
CommonAuthenticationHandler commonAuthenticationHandler = new CommonAuthenticationHandler();
CommonAuthRequestWrapper requestWrapper = new CommonAuthRequestWrapper(request);
requestWrapper.setParameter(FrameworkConstants.SESSION_DATA_KEY, sessionDataKey);
requestWrapper.setParameter(FrameworkConstants.RequestParams.TYPE, type);
CommonAuthResponseWrapper responseWrapper = new CommonAuthResponseWrapper(response);
commonAuthenticationHandler.doGet(requestWrapper, responseWrapper);
Object attribute = request.getAttribute(FrameworkConstants.RequestParams.FLOW_STATUS);
if (attribute != null) {
if (attribute == AuthenticatorFlowStatus.INCOMPLETE) {
if (responseWrapper.isRedirect()) {
response.sendRedirect(responseWrapper.getRedirectURL());
} else {
return Response.status(HttpServletResponse.SC_OK).entity(responseWrapper.getResponseBody()).build();
}
} else {
return authorize(requestWrapper, responseWrapper);
}
} else {
requestWrapper.setAttribute(FrameworkConstants.RequestParams.FLOW_STATUS, AuthenticatorFlowStatus.UNKNOWN);
return authorize(requestWrapper, responseWrapper);
}
return null;
}
}