/*
* 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.CarbonConstants;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.application.authentication.framework.config.model.ApplicationConfig;
import org.wso2.carbon.identity.application.authentication.framework.config.model.AuthenticatorConfig;
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.AuthenticationRequestHandler;
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.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.idp.mgt.util.IdPManagementUtil;
import org.wso2.carbon.registry.core.utils.UUIDGenerator;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class DefaultAuthenticationRequestHandler implements AuthenticationRequestHandler {
private static final Log log = LogFactory.getLog(DefaultAuthenticationRequestHandler.class);
private static final Log AUDIT_LOG = CarbonConstants.AUDIT_LOG;
private static volatile DefaultAuthenticationRequestHandler instance;
public static DefaultAuthenticationRequestHandler getInstance() {
if (instance == null) {
synchronized (DefaultAuthenticationRequestHandler.class) {
if (instance == null) {
instance = new DefaultAuthenticationRequestHandler();
}
}
}
return instance;
}
/**
* Executes the authentication flow
*
* @param request
* @param response
* @throws FrameworkException
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AuthenticationContext context) throws FrameworkException {
if (log.isDebugEnabled()) {
log.debug("In authentication flow");
}
if (context.isReturning()) {
// if "Deny" or "Cancel" pressed on the login page.
if (request.getParameter(FrameworkConstants.RequestParams.DENY) != null) {
handleDenyFromLoginPage(request, response, context);
return;
}
// handle remember-me option from the login page
handleRememberMeOptionFromLoginPage(request, context);
}
int currentStep = context.getCurrentStep();
// if this is the start of the authentication flow
if (currentStep == 0) {
handleSequenceStart(request, response, context);
}
SequenceConfig seqConfig = context.getSequenceConfig();
List<AuthenticatorConfig> reqPathAuthenticators = seqConfig.getReqPathAuthenticators();
// if SP has request path authenticators configured and this is start of
// the flow
if (reqPathAuthenticators != null && !reqPathAuthenticators.isEmpty() && currentStep == 0) {
// call request path sequence handler
FrameworkUtils.getRequestPathBasedSequenceHandler().handle(request, response, context);
}
// if no request path authenticators or handler returned cannot handle
if (!context.getSequenceConfig().isCompleted()
|| (reqPathAuthenticators == null || reqPathAuthenticators.isEmpty())) {
// call step based sequence handler
FrameworkUtils.getStepBasedSequenceHandler().handle(request, response, context);
}
// if flow completed, send response back
if (context.getSequenceConfig().isCompleted()) {
concludeFlow(request, response, context);
} else { // redirecting outside
FrameworkUtils.addAuthenticationContextToCache(context.getContextIdentifier(), context);
}
}
private void handleDenyFromLoginPage(HttpServletRequest request, HttpServletResponse response,
AuthenticationContext context) throws FrameworkException {
if (log.isDebugEnabled()) {
log.debug("User has pressed Deny or Cancel in the login page. Terminating the authentication flow");
}
context.getSequenceConfig().setCompleted(true);
context.setRequestAuthenticated(false);
concludeFlow(request, response, context);
}
private void handleRememberMeOptionFromLoginPage(HttpServletRequest request, AuthenticationContext context) {
String rememberMe = request.getParameter("chkRemember");
if (rememberMe != null && "on".equalsIgnoreCase(rememberMe)) {
context.setRememberMe(true);
} else {
context.setRememberMe(false);
}
}
/**
* Handle the start of a Sequence
*
* @param request
* @param response
* @param context
* @return
* @throws ServletException
* @throws IOException
* @throws FrameworkException
*/
protected boolean handleSequenceStart(HttpServletRequest request, HttpServletResponse response,
AuthenticationContext context) throws FrameworkException {
if (log.isDebugEnabled()) {
log.debug("Starting the sequence");
}
// "forceAuthenticate" - go in the full authentication flow even if user
// is already logged in.
boolean forceAuthenticate = request
.getParameter(FrameworkConstants.RequestParams.FORCE_AUTHENTICATE) != null ? Boolean
.valueOf(request.getParameter(FrameworkConstants.RequestParams.FORCE_AUTHENTICATE))
: false;
context.setForceAuthenticate(forceAuthenticate);
if (log.isDebugEnabled()) {
log.debug("Force Authenticate : " + forceAuthenticate);
}
// "reAuthenticate" - authenticate again with the same IdPs as before.
boolean reAuthenticate = request
.getParameter(FrameworkConstants.RequestParams.RE_AUTHENTICATE) != null ? Boolean
.valueOf(request.getParameter(FrameworkConstants.RequestParams.RE_AUTHENTICATE))
: false;
if (log.isDebugEnabled()) {
log.debug("Re-Authenticate : " + reAuthenticate);
}
context.setReAuthenticate(reAuthenticate);
// "checkAuthentication" - passive mode. just send back whether user is
// *already* authenticated or not.
boolean passiveAuthenticate = request
.getParameter(FrameworkConstants.RequestParams.PASSIVE_AUTHENTICATION) != null ? Boolean
.valueOf(request
.getParameter(FrameworkConstants.RequestParams.PASSIVE_AUTHENTICATION))
: false;
if (log.isDebugEnabled()) {
log.debug("Passive Authenticate : " + passiveAuthenticate);
}
context.setPassiveAuthenticate(passiveAuthenticate);
return false;
}
/**
* Sends the response to the servlet that initiated the authentication flow
*
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void concludeFlow(HttpServletRequest request, HttpServletResponse response,
AuthenticationContext context) throws FrameworkException {
if (log.isDebugEnabled()) {
log.debug("Concluding the Authentication Flow");
}
SequenceConfig sequenceConfig = context.getSequenceConfig();
sequenceConfig.setCompleted(false);
AuthenticationResult authenticationResult = new AuthenticationResult();
boolean isAuthenticated = context.isRequestAuthenticated();
authenticationResult.setAuthenticated(isAuthenticated);
String authenticatedUserTenantDomain = getAuthenticatedUserTenantDomain(context, authenticationResult);
authenticationResult.setSaaSApp(sequenceConfig.getApplicationConfig().isSaaSApp());
if (isAuthenticated) {
if (!sequenceConfig.getApplicationConfig().isSaaSApp()) {
String spTenantDomain = context.getTenantDomain();
String userTenantDomain = sequenceConfig.getAuthenticatedUser().getTenantDomain();
if (StringUtils.isNotEmpty(userTenantDomain)) {
if (StringUtils.isNotEmpty(spTenantDomain) && !spTenantDomain.equals
(userTenantDomain)) {
throw new FrameworkException("Service Provider tenant domain must be equal to user tenant " +
"domain for non-SaaS applications");
}
}
}
authenticationResult.setSubject(new AuthenticatedUser(sequenceConfig.getAuthenticatedUser()));
ApplicationConfig appConfig = sequenceConfig.getApplicationConfig();
if (appConfig.getServiceProvider().getLocalAndOutBoundAuthenticationConfig()
.isAlwaysSendBackAuthenticatedListOfIdPs()) {
authenticationResult.setAuthenticatedIdPs(sequenceConfig.getAuthenticatedIdPs());
}
// SessionContext is retained across different SP requests in the same browser session.
// it is tracked by a cookie
SessionContext sessionContext = null;
String commonAuthCookie = null;
if (FrameworkUtils.getAuthCookie(request) != null) {
commonAuthCookie = FrameworkUtils.getAuthCookie(request).getValue();
if (commonAuthCookie != null) {
sessionContext = FrameworkUtils.getSessionContextFromCache(commonAuthCookie);
}
}
// session context may be null when cache expires therefore creating new cookie as well.
if (sessionContext != null) {
sessionContext.getAuthenticatedSequences().put(appConfig.getApplicationName(),
sequenceConfig);
sessionContext.getAuthenticatedIdPs().putAll(context.getCurrentAuthenticatedIdPs());
// TODO add to cache?
// store again. when replicate cache is used. this may be needed.
FrameworkUtils.addSessionContextToCache(commonAuthCookie, sessionContext);
} else {
sessionContext = new SessionContext();
sessionContext.getAuthenticatedSequences().put(appConfig.getApplicationName(),
sequenceConfig);
sessionContext.setAuthenticatedIdPs(context.getCurrentAuthenticatedIdPs());
sessionContext.setRememberMe(context.isRememberMe());
String sessionKey = UUIDGenerator.generateUUID();
FrameworkUtils.addSessionContextToCache(sessionKey, sessionContext);
setAuthCookie(request, response, context, sessionKey, authenticatedUserTenantDomain);
}
if (authenticatedUserTenantDomain == null) {
PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
}
String auditData = "\"" + "ContextIdentifier" + "\" : \"" + context.getContextIdentifier()
+ "\",\"" + "AuthenticatedUser" + "\" : \"" + sequenceConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier()
+ "\",\"" + "AuthenticatedUserTenantDomain" + "\" : \"" + authenticatedUserTenantDomain
+ "\",\"" + "ServiceProviderName" + "\" : \"" + context.getServiceProviderName()
+ "\",\"" + "RequestType" + "\" : \"" + context.getRequestType()
+ "\",\"" + "RelyingParty" + "\" : \"" + context.getRelyingParty()
+ "\",\"" + "AuthenticatedIdPs" + "\" : \"" + sequenceConfig.getAuthenticatedIdPs()
+ "\"";
AUDIT_LOG.info(String.format(
FrameworkConstants.AUDIT_MESSAGE,
sequenceConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier(),
"Login",
"ApplicationAuthenticationFramework", auditData, FrameworkConstants.AUDIT_SUCCESS));
}
// Checking weather inbound protocol is an already cache removed one, request come from federated or other
// authenticator in multi steps scenario. Ex. Fido
if (FrameworkUtils.getCacheDisabledAuthenticators().contains(context.getRequestType())
&& (response instanceof CommonAuthResponseWrapper)) {
//Set the result as request attribute
request.setAttribute("sessionDataKey", context.getCallerSessionKey());
addAuthenticationResultToRequest(request, authenticationResult);
}else{
FrameworkUtils.addAuthenticationResultToCache(context.getCallerSessionKey(), authenticationResult);
}
/*
* 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) {
FrameworkUtils.removeAuthenticationContextFromCache(context.getContextIdentifier());
}
sendResponse(request, response, context);
}
/**
* Add authentication request as request attribute
* @param request
* @param authenticationResult
*/
private void addAuthenticationResultToRequest(HttpServletRequest request,
AuthenticationResult authenticationResult) {
request.setAttribute(FrameworkConstants.RequestAttribute.AUTH_RESULT, authenticationResult);
}
private void setAuthCookie(HttpServletRequest request, HttpServletResponse response, AuthenticationContext context,
String sessionKey, String tenantDomain) throws FrameworkException {
Integer authCookieAge = null;
if (context.isRememberMe()) {
authCookieAge = IdPManagementUtil.getRememberMeTimeout(tenantDomain);
}
FrameworkUtils.storeAuthCookie(request, response, sessionKey, authCookieAge);
}
private String getAuthenticatedUserTenantDomain(AuthenticationContext context,
AuthenticationResult authenticationResult) {
String authenticatedUserTenantDomain = null;
if (context.getProperties() != null) {
authenticatedUserTenantDomain = (String) context.getProperties()
.get("user-tenant-domain");
}
return authenticatedUserTenantDomain;
}
protected void sendResponse(HttpServletRequest request, HttpServletResponse response,
AuthenticationContext context) throws FrameworkException {
if (log.isDebugEnabled()) {
StringBuilder debugMessage = new StringBuilder();
debugMessage.append("Sending response back to: ");
debugMessage.append(context.getCallerPath()).append("...\n");
debugMessage.append(FrameworkConstants.ResponseParams.AUTHENTICATED).append(": ");
debugMessage.append(String.valueOf(context.isRequestAuthenticated())).append("\n");
debugMessage.append(FrameworkConstants.ResponseParams.AUTHENTICATED_USER).append(": ");
if (context.getSequenceConfig().getAuthenticatedUser() != null) {
debugMessage.append(context.getSequenceConfig().getAuthenticatedUser().getAuthenticatedSubjectIdentifier()).append("\n");
} else {
debugMessage.append("No Authenticated User").append("\n");
}
debugMessage.append(FrameworkConstants.ResponseParams.AUTHENTICATED_IDPS).append(": ");
debugMessage.append(context.getSequenceConfig().getAuthenticatedIdPs()).append("\n");
debugMessage.append(FrameworkConstants.SESSION_DATA_KEY).append(": ");
debugMessage.append(context.getCallerSessionKey());
log.debug(debugMessage);
}
// TODO rememberMe should be handled by a cookie authenticator. For now rememberMe flag that
// was set in the login page will be sent as a query param to the calling servlet so it will
// handle rememberMe as usual.
String rememberMeParam = "";
if (context.isRequestAuthenticated() && context.isRememberMe()) {
rememberMeParam = rememberMeParam + "&chkRemember=on";
}
// redirect to the caller
String redirectURL = context.getCallerPath() + "?sessionDataKey="
+ context.getCallerSessionKey() + rememberMeParam;
try {
response.sendRedirect(redirectURL);
} catch (IOException e) {
throw new FrameworkException(e.getMessage(), e);
}
}
}