/*
* 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.application.authentication.framework.handler.request.impl;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.identity.application.authentication.framework.cache.AuthenticationRequestCacheEntry;
import org.wso2.carbon.identity.application.authentication.framework.config.ConfigurationFacade;
import org.wso2.carbon.identity.application.authentication.framework.config.model.SequenceConfig;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.context.SessionContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.FrameworkException;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.RequestCoordinator;
import org.wso2.carbon.identity.application.authentication.framework.internal.FrameworkServiceComponent;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.registry.core.utils.UUIDGenerator;
import org.wso2.carbon.user.api.Tenant;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
/**
* Request Coordinator
*/
public class DefaultRequestCoordinator implements RequestCoordinator {
private static final Log log = LogFactory.getLog(DefaultRequestCoordinator.class);
private static volatile DefaultRequestCoordinator instance;
public static DefaultRequestCoordinator getInstance() {
if (instance == null) {
synchronized (DefaultRequestCoordinator.class) {
if (instance == null) {
instance = new DefaultRequestCoordinator();
}
}
}
return instance;
}
/**
* Get authentication request cache entry
* @param request Http servlet request
* @return Authentication request cache entry
*/
private AuthenticationRequestCacheEntry getAuthenticationRequestFromRequest(HttpServletRequest request) {
return (AuthenticationRequestCacheEntry) request.getAttribute(FrameworkConstants.RequestAttribute.AUTH_REQUEST);
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
AuthenticationContext context;
AuthenticationRequestCacheEntry authRequest = null;
String sessionDataKey = request.getParameter("sessionDataKey");
boolean returning = false;
// Check whether this is the start of the authentication flow.
// 'type' parameter should be present if so. This parameter contains
// the request type (e.g. samlsso) set by the calling servlet.
// TODO: use a different mechanism to determine the flow start.
if (request.getParameter("type") != null) {
// Retrieve AuthenticationRequestCache Entry which is stored stored from servlet.
if (sessionDataKey != null) {
if (log.isDebugEnabled()) {
log.debug("retrieving authentication request from cache..");
}
authRequest = getAuthenticationRequest(request, sessionDataKey);
if (authRequest == null) {
// authRequest cannot be retrieved from cache. Cache
throw new FrameworkException("Invalid authentication request. Session data key : " + sessionDataKey);
}
} else if (!Boolean.parseBoolean(request.getParameter(FrameworkConstants.LOGOUT))) {
// sessionDataKey is null and not a logout request
if (log.isDebugEnabled()) {
log.debug("Session data key is null in the request and not a logout request.");
}
FrameworkUtils.sendToRetryPage(request, response);
}
// if there is a cache entry, wrap the original request with params in cache entry
if (authRequest != null) {
request = FrameworkUtils.getCommonAuthReqWithParams(request, authRequest);
FrameworkUtils.removeAuthenticationRequestFromCache(sessionDataKey);
}
context = initializeFlow(request, response);
} else {
returning = true;
context = FrameworkUtils.getContextData(request);
}
if (context != null) {
context.setReturning(returning);
// if this is the flow start, store the original request in the context
if (!context.isReturning() && authRequest != null) {
context.setAuthenticationRequest(authRequest.getAuthenticationRequest());
}
if (!context.isLogoutRequest()) {
FrameworkUtils.getAuthenticationRequestHandler().handle(request, response,
context);
} else {
FrameworkUtils.getLogoutRequestHandler().handle(request, response, context);
}
} else {
if (log.isDebugEnabled()) {
String key = request.getParameter("sessionDataKey");
if (key == null) {
log.debug("Session data key is null in the request");
} else {
log.debug("Session data key : " + key);
}
}
log.error("Context does not exist. Probably due to invalidated cache");
FrameworkUtils.sendToRetryPage(request, response);
}
} catch (Throwable e) {
log.error("Exception in Authentication Framework", e);
FrameworkUtils.sendToRetryPage(request, response);
}
}
/**
* When cache removed authentication request stored as request attribute, then taking request from request or
* otherwise getting authentication request from cache
*
* @param request
* @param sessionDataKey
* @return
*/
private AuthenticationRequestCacheEntry getAuthenticationRequest(HttpServletRequest request,
String sessionDataKey) {
AuthenticationRequestCacheEntry authRequest = getAuthenticationRequestFromRequest(request);
if (authRequest == null) {
authRequest = FrameworkUtils.getAuthenticationRequestFromCache(sessionDataKey);
}
return authRequest;
}
/**
* Handles the initial request (from the calling servlet)
*
* @param request
* @param response
* @throws ServletException
* @throws IOException
* @throws
*/
protected AuthenticationContext initializeFlow(HttpServletRequest request,
HttpServletResponse response) throws FrameworkException {
if (log.isDebugEnabled()) {
log.debug("Initializing the flow");
}
// "sessionDataKey" - calling servlet maintains its state information
// using this
String callerSessionDataKey = request.getParameter(FrameworkConstants.SESSION_DATA_KEY);
// "commonAuthCallerPath" - path of the calling servlet. This is the url
// response should be sent to
String callerPath = getCallerPath(request);
// "type" - type of the request. e.g. samlsso, openid, oauth, passivests
String requestType = request.getParameter(FrameworkConstants.RequestParams.TYPE);
// "relyingParty"
String relyingParty = request.getParameter(FrameworkConstants.RequestParams.ISSUER);
// tenant domain
String tenantDomain = getTenantDomain(request);
// Store the request data sent by the caller
AuthenticationContext context = new AuthenticationContext();
context.setCallerSessionKey(callerSessionDataKey);
context.setCallerPath(callerPath);
context.setRequestType(requestType);
context.setRelyingParty(relyingParty);
context.setTenantDomain(tenantDomain);
// generate a new key to hold the context data object
String contextId = UUIDGenerator.generateUUID();
context.setContextIdentifier(contextId);
if (log.isDebugEnabled()) {
log.debug("Framework contextId: " + contextId);
}
// if this a logout request from the calling servlet
if (request.getParameter(FrameworkConstants.RequestParams.LOGOUT) != null) {
if (log.isDebugEnabled()) {
log.debug("Starting a logout flow");
}
context.setLogoutRequest(true);
if (context.getRelyingParty() == null || context.getRelyingParty().trim().length() == 0) {
if (log.isDebugEnabled()) {
log.debug("relyingParty param is null. This is a possible logout scenario.");
}
Cookie cookie = FrameworkUtils.getAuthCookie(request);
if (cookie != null) {
context.setSessionIdentifier(cookie.getValue());
}
return context;
}
} else {
if (log.isDebugEnabled()) {
log.debug("Starting an authentication flow");
}
}
findPreviousAuthenticatedSession(request, context);
buildOutboundQueryString(request, context);
return context;
}
private String getCallerPath(HttpServletRequest request) throws FrameworkException {
String callerPath = request.getParameter(FrameworkConstants.RequestParams.CALLER_PATH);
try {
if (callerPath != null) {
callerPath = URLDecoder.decode(callerPath, "UTF-8");
}
} catch (UnsupportedEncodingException e) {
throw new FrameworkException(e.getMessage(), e);
}
return callerPath;
}
private String getTenantDomain(HttpServletRequest request) throws FrameworkException {
String tenantDomain = request.getParameter(FrameworkConstants.RequestParams.TENANT_DOMAIN);
if (tenantDomain == null || tenantDomain.isEmpty() || "null".equals(tenantDomain)) {
String tenantId = request.getParameter(FrameworkConstants.RequestParams.TENANT_ID);
if (tenantId != null && !"-1234".equals(tenantId)) {
try {
Tenant tenant = FrameworkServiceComponent.getRealmService().getTenantManager()
.getTenant(Integer.parseInt(tenantId));
if (tenant != null) {
tenantDomain = tenant.getDomain();
}
} catch (Exception e) {
throw new FrameworkException(e.getMessage(), e);
}
} else {
tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;
}
}
return tenantDomain;
}
protected void findPreviousAuthenticatedSession(HttpServletRequest request,
AuthenticationContext context) throws FrameworkException {
// Get service provider chain
SequenceConfig sequenceConfig = ConfigurationFacade.getInstance().getSequenceConfig(
context.getRequestType(),
request.getParameter(FrameworkConstants.RequestParams.ISSUER),
context.getTenantDomain());
Cookie cookie = FrameworkUtils.getAuthCookie(request);
// if cookie exists user has previously authenticated
if (cookie != null) {
if (log.isDebugEnabled()) {
log.debug(FrameworkConstants.COMMONAUTH_COOKIE
+ " cookie is available with the value: " + cookie.getValue());
}
// get the authentication details from the cache
SessionContext sessionContext = FrameworkUtils.getSessionContextFromCache(cookie
.getValue());
if (sessionContext != null) {
context.setSessionIdentifier(cookie.getValue());
String appName = sequenceConfig.getApplicationConfig().getApplicationName();
if (log.isDebugEnabled()) {
log.debug("Service Provider is: " + appName);
}
SequenceConfig previousAuthenticatedSeq = sessionContext
.getAuthenticatedSequences().get(appName);
if (previousAuthenticatedSeq != null) {
if (log.isDebugEnabled()) {
log.debug("A previously authenticated sequence found for the SP: "
+ appName);
}
context.setPreviousSessionFound(true);
sequenceConfig = previousAuthenticatedSeq;
AuthenticatedUser authenticatedUser = sequenceConfig.getAuthenticatedUser();
String authenticatedUserTenantDomain = sequenceConfig.getAuthenticatedUser().getTenantDomain();
if (authenticatedUser != null) {
// set the user for the current authentication/logout flow
context.setSubject(authenticatedUser);
if (log.isDebugEnabled()) {
log.debug("Already authenticated by username: " +
authenticatedUser.getAuthenticatedSubjectIdentifier());
}
if (authenticatedUserTenantDomain != null) {
// set the user tenant domain for the current authentication/logout flow
context.setProperty("user-tenant-domain", authenticatedUserTenantDomain);
if (log.isDebugEnabled()) {
log.debug("Authenticated user tenant domain: " + authenticatedUserTenantDomain);
}
}
}
}
context.setPreviousAuthenticatedIdPs(sessionContext.getAuthenticatedIdPs());
} else {
if (log.isDebugEnabled()) {
log.debug("Failed to find the SessionContext from the cache. Possible cache timeout.");
}
}
}
context.setServiceProviderName(sequenceConfig.getApplicationConfig().getApplicationName());
// set the sequence for the current authentication/logout flow
context.setSequenceConfig(sequenceConfig);
}
private void buildOutboundQueryString(HttpServletRequest request, AuthenticationContext context)
throws FrameworkException {
// Build the outbound query string that will be sent to the authentication endpoint and
// federated IdPs
StringBuilder outboundQueryStringBuilder = new StringBuilder();
outboundQueryStringBuilder.append(FrameworkUtils.getQueryStringWithConfiguredParams(request));
if (StringUtils.isNotEmpty(outboundQueryStringBuilder.toString())) {
outboundQueryStringBuilder.append("&");
}
try {
outboundQueryStringBuilder.append("sessionDataKey=").append(context.getContextIdentifier())
.append("&relyingParty=").append(URLEncoder.encode(context.getRelyingParty(), "UTF-8")).append("&type=")
.append(context.getRequestType()).append("&sp=")
.append(URLEncoder.encode(context.getServiceProviderName(), "UTF-8")).append("&isSaaSApp=")
.append(context.getSequenceConfig().getApplicationConfig().isSaaSApp());
} catch (UnsupportedEncodingException e) {
throw new FrameworkException("Error while URL Encoding", e);
}
if (log.isDebugEnabled()) {
log.debug("Outbound Query String: " + outboundQueryStringBuilder.toString());
}
context.setContextIdIncludedQueryParams(outboundQueryStringBuilder.toString());
context.setOrignalRequestQueryParams(outboundQueryStringBuilder.toString());
}
}