/*
* Copyright (c) 2005-2010, 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.authenticator.saml2.sso.ui;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.LogoutRequest;
import org.opensaml.saml2.core.LogoutResponse;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.SessionIndex;
import org.opensaml.saml2.core.Subject;
import org.opensaml.xml.XMLObject;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.identity.authenticator.saml2.sso.common.FederatedSSOToken;
import org.wso2.carbon.identity.authenticator.saml2.sso.common.SAML2SSOAuthenticatorConstants;
import org.wso2.carbon.identity.authenticator.saml2.sso.common.SAML2SSOUIAuthenticatorException;
import org.wso2.carbon.identity.authenticator.saml2.sso.common.SAMLConstants;
import org.wso2.carbon.identity.authenticator.saml2.sso.common.Util;
import org.wso2.carbon.identity.authenticator.saml2.sso.ui.authenticator.SAML2SSOUIAuthenticator;
import org.wso2.carbon.identity.authenticator.saml2.sso.ui.client.SAMLSSOServiceClient;
import org.wso2.carbon.identity.authenticator.saml2.sso.ui.session.SSOSessionManager;
import org.wso2.carbon.identity.sso.saml.stub.types.SAMLSSOAuthnReqDTO;
import org.wso2.carbon.identity.sso.saml.stub.types.SAMLSSOReqValidationResponseDTO;
import org.wso2.carbon.identity.sso.saml.stub.types.SAMLSSORespDTO;
import org.wso2.carbon.ui.CarbonSecuredHttpContext;
import org.wso2.carbon.ui.CarbonUIUtil;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.List;
/**
*
*/
public class SSOAssertionConsumerService extends HttpServlet {
public static final Log log = LogFactory.getLog(SSOAssertionConsumerService.class);
public static final String SSO_TOKEN_ID = "ssoTokenId";
/**
*
*/
private static final long serialVersionUID = 5451353570561170887L;
/**
* session timeout happens in 10 hours
*/
private static final int SSO_SESSION_EXPIRE = 36000;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String samlRespString = req.getParameter(
SAML2SSOAuthenticatorConstants.HTTP_POST_PARAM_SAML2_RESP);
if (log.isDebugEnabled()) {
log.debug("Processing SAML Response");
Enumeration headerNames = req.getHeaderNames();
log.debug("[Request Headers] :");
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
log.debug(">> " + headerName + ":" + req.getHeader(headerName));
}
Enumeration params = req.getParameterNames();
log.debug("[Request Parameters] :");
while (params.hasMoreElements()) {
String paramName = (String) params.nextElement();
log.debug(">> " + paramName + ":" + req.getParameter(paramName));
}
}
// Handle single logout requests
if (req.getParameter(SAML2SSOAuthenticatorConstants.HTTP_POST_PARAM_SAML2_AUTH_REQ) != null) {
handleSingleLogoutRequest(req, resp);
return;
}
// If SAML Response is not present in the redirected req, send the user to an error page.
if (samlRespString == null) {
log.error("SAML Response is not present in the request.");
handleMalformedResponses(req, resp,
SAML2SSOAuthenticatorConstants.ErrorMessageConstants.RESPONSE_NOT_PRESENT);
return;
}
// // If RELAY-STATE is invalid, redirect the users to an error page.
// if (!SSOSessionManager.isValidResponse(relayState)) {
// handleMalformedResponses(req, resp,
// SAML2SSOAuthenticatorConstants.ErrorMessageConstants.RESPONSE_INVALID);
// return;
// }
// Handle valid messages, either SAML Responses or LogoutRequests
try {
XMLObject samlObject = Util.unmarshall(Util.decode(samlRespString));
if (samlObject instanceof LogoutResponse) { // if it is a logout response, redirect it to login page.
String externalLogoutPage = Util.getExternalLogoutPage();
if(externalLogoutPage != null && !externalLogoutPage.isEmpty()){
handleExternalLogout(req, resp, externalLogoutPage);
} else {
resp.sendRedirect(getAdminConsoleURL(req) + "admin/logout_action.jsp?logoutcomplete=true");
}
} else if (samlObject instanceof Response) { // if it is a SAML Response
handleSAMLResponses(req, resp, samlObject);
}
} catch (SAML2SSOUIAuthenticatorException e) {
log.error("Error when processing the SAML Assertion in the request.", e);
handleMalformedResponses(req, resp, SAML2SSOAuthenticatorConstants.ErrorMessageConstants.RESPONSE_MALFORMED);
}
}
/**
* Handle SAML Responses and authenticate.
*
* @param req HttpServletRequest
* @param resp HttpServletResponse
* @param samlObject SAML Response object
* @throws ServletException Error when redirecting
* @throws IOException Error when redirecting
*/
private void handleSAMLResponses(HttpServletRequest req, HttpServletResponse resp,
XMLObject samlObject)
throws ServletException, IOException, SAML2SSOUIAuthenticatorException {
Response samlResponse;
samlResponse = (Response) samlObject;
List<Assertion> assertions = samlResponse.getAssertions();
Assertion assertion = null;
if (assertions != null && assertions.size() > 0) {
assertion = assertions.get(0);
}
if (assertion == null) {
// This condition would succeed if Passive Login Request was sent because SP session has timed out
if (samlResponse.getStatus() != null &&
samlResponse.getStatus().getStatusCode() != null &&
samlResponse.getStatus().getStatusCode().getValue().equals("urn:oasis:names:tc:SAML:2.0:status:Responder") &&
samlResponse.getStatus().getStatusCode().getStatusCode() != null &&
samlResponse.getStatus().getStatusCode().getStatusCode().getValue().equals("urn:oasis:names:tc:SAML:2.0:status:NoPassive")) {
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/carbon/admin/login.jsp");
requestDispatcher.forward(req, resp);
return;
}
if (samlResponse.getStatus() != null &&
samlResponse.getStatus().getStatusMessage() != null) {
log.error(samlResponse.getStatus().getStatusMessage().getMessage());
} else {
log.error("SAML Assertion not found in the Response.");
}
throw new SAML2SSOUIAuthenticatorException("SAML Authentication Failed.");
}
// Get the subject name from the Response Object and forward it to login_action.jsp
String username = null;
if (assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
username = Util.getUsernameFromResponse(samlResponse);
}
if (log.isDebugEnabled()) {
log.debug("A username is extracted from the response. : " + username);
}
if (username == null) {
log.error("SAMLResponse does not contain the name of the subject");
throw new SAML2SSOUIAuthenticatorException("SAMLResponse does not contain the name of the subject");
}
String relayState = req.getParameter(SAMLConstants.RELAY_STATE);
boolean isFederated = false;
if (relayState != null) {
FederatedSSOToken federatedSSOToken = org.wso2.carbon.identity.authenticator.saml2.sso.common.SSOSessionManager
.getFederatedToken(relayState);
if (federatedSSOToken != null) {
isFederated = true;
HttpServletRequest fedRequest = federatedSSOToken.getHttpServletRequest();
String samlRequest = fedRequest.getParameter("SAMLRequest");
String authMode = SAMLConstants.AuthnModes.USERNAME_PASSWORD;
String fedRelayState = fedRequest.getParameter(SAMLConstants.RELAY_STATE);
String rpSessionId = fedRequest.getParameter(MultitenantConstants.SSO_AUTH_SESSION_ID);
Enumeration<String> e = fedRequest.getAttributeNames();
while (e.hasMoreElements()) {
String name = e.nextElement();
req.setAttribute(name, fedRequest.getAttribute(name));
}
Cookie[] cookies = fedRequest.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
resp.addCookie(cookies[i]);
}
}
HttpSession session = fedRequest.getSession();
// Use sessionID as the tokenID, if cookie is not set.
String ssoTokenID = session.getId();
Cookie tokenCookie = getSSOTokenCookie(fedRequest);
if (tokenCookie != null) {
ssoTokenID = tokenCookie.getValue();
}
handleFederatedSAMLRequest(req, resp, ssoTokenID, samlRequest, fedRelayState, authMode, assertion.getSubject(), rpSessionId);
}
}
if (!isFederated) {
// Set the SAML2 Response as a HTTP Attribute, so it is not required to build the
// assertion again.
req.setAttribute(SAML2SSOAuthenticatorConstants.HTTP_ATTR_SAML2_RESP_TOKEN,
samlResponse);
String sessionIndex = null;
List<AuthnStatement> authnStatements = assertion.getAuthnStatements();
if (authnStatements != null && authnStatements.size() > 0) {
// There can be only one authentication stmt inside the SAML assertion of a SAML Response
AuthnStatement authStmt = authnStatements.get(0);
sessionIndex = authStmt.getSessionIndex();
}
String url = req.getRequestURI();
url = url.replace("acs","carbon/admin/login_action.jsp?username=" + URLEncoder.encode(username, "UTF-8"));
if(sessionIndex != null) {
url += "&" + SAML2SSOAuthenticatorConstants.IDP_SESSION_INDEX + "=" + URLEncoder.encode(sessionIndex, "UTF-8");
}
if(log.isDebugEnabled()) {
log.debug("Forwarding to path : " + url);
}
RequestDispatcher reqDispatcher = req.getRequestDispatcher(url);
req.getSession().setAttribute("CarbonAuthenticator", new SAML2SSOUIAuthenticator());
reqDispatcher.forward(req, resp);
}
}
/**
* Handle malformed Responses.
*
* @param req HttpServletRequest
* @param resp HttpServletResponse
* @param errorMsg Error message to be displayed in HttpServletResponse.jsp
* @throws IOException Error when redirecting
*/
private void handleMalformedResponses(HttpServletRequest req, HttpServletResponse resp,
String errorMsg) throws IOException {
HttpSession session = req.getSession();
session.setAttribute(SAML2SSOAuthenticatorConstants.NOTIFICATIONS_ERROR_MSG, errorMsg);
resp.sendRedirect(getAdminConsoleURL(req) + "sso-acs/notifications.jsp");
return;
}
/**
* This method is used to handle the single logout requests sent by the Identity Provider
*
* @param req Corresponding HttpServletRequest
* @param resp Corresponding HttpServletResponse
*/
private void handleSingleLogoutRequest(HttpServletRequest req, HttpServletResponse resp) {
String logoutReqStr = req.getParameter(SAML2SSOAuthenticatorConstants.HTTP_POST_PARAM_SAML2_AUTH_REQ);
XMLObject samlObject = null;
try {
samlObject = Util.unmarshall(Util.decode(logoutReqStr));
} catch (SAML2SSOUIAuthenticatorException e) {
log.error("Error handling the single logout request", e);
}
if (samlObject instanceof LogoutRequest) {
LogoutRequest logoutRequest = (LogoutRequest) samlObject;
// There can be only one session index entry.
List<SessionIndex> sessionIndexList = logoutRequest.getSessionIndexes();
if (sessionIndexList.size() > 0) {
SSOSessionManager.getInstance().handleLogout(
sessionIndexList.get(0).getSessionIndex());
}
}
}
/**
* Get the admin console url from the request.
*
* @param request httpServletReq that hits the ACS Servlet
* @return Admin Console URL https://10.100.1.221:8443/acs/carbon/
*/
private String getAdminConsoleURL(HttpServletRequest request) {
String url = CarbonUIUtil.getAdminConsoleURL(request);
if (!url.endsWith("/")) {
url = url + "/";
}
if (url.indexOf("/acs") != -1) {
url = url.replace("/acs", "");
}
return url;
}
private void handleFederatedSAMLRequest(HttpServletRequest req, HttpServletResponse resp,
String ssoTokenID, String samlRequest,
String relayState, String authMode, Subject subject,
String rpSessionId)
throws IOException, ServletException, SAML2SSOUIAuthenticatorException {
// Instantiate the service client.
HttpSession session = req.getSession();
String serverURL = CarbonUIUtil.getServerURL(session.getServletContext(), session);
ConfigurationContext configContext =
(ConfigurationContext) session.getServletContext()
.getAttribute(CarbonConstants.CONFIGURATION_CONTEXT);
SAMLSSOServiceClient ssoServiceClient = new SAMLSSOServiceClient(serverURL, configContext);
String method = req.getMethod();
boolean isPost = false;
if ("post".equalsIgnoreCase(method)) {
isPost = true;
}
SAMLSSOReqValidationResponseDTO signInRespDTO =
ssoServiceClient.validate(samlRequest,
null, ssoTokenID,
rpSessionId,
authMode, isPost);
if (signInRespDTO.getValid()) {
handleRequestFromLoginPage(req, resp, ssoTokenID,
signInRespDTO.getAssertionConsumerURL(),
signInRespDTO.getId(), signInRespDTO.getIssuer(),
subject.getNameID().getValue(), subject.getNameID()
.getValue(),
signInRespDTO.getRpSessionId(),
signInRespDTO.getRequestMessageString(), relayState);
}
}
private void handleRequestFromLoginPage(HttpServletRequest req, HttpServletResponse resp,
String ssoTokenID, String assertionConsumerUrl, String id, String issuer, String userName, String subject,
String rpSession, String requestMsgString, String relayState)
throws IOException, ServletException, SAML2SSOUIAuthenticatorException {
HttpSession session = req.getSession();
// instantiate the service client
String serverURL = CarbonUIUtil.getServerURL(session.getServletContext(), session);
ConfigurationContext configContext = (ConfigurationContext) session.getServletContext()
.getAttribute(CarbonConstants.CONFIGURATION_CONTEXT);
SAMLSSOServiceClient ssoServiceClient = new SAMLSSOServiceClient(serverURL, configContext);
// Create SAMLSSOAuthnReqDTO using the request Parameters
SAMLSSOAuthnReqDTO authnReqDTO = new SAMLSSOAuthnReqDTO();
authnReqDTO.setAssertionConsumerURL(assertionConsumerUrl);
authnReqDTO.setId(id);
authnReqDTO.setIssuer(issuer);
//TODO FIX NEED TO BE DONE
authnReqDTO.setUser(null);
authnReqDTO.setPassword("federated_idp_login");
authnReqDTO.setSubject(subject);
authnReqDTO.setRpSessionId(rpSession);
authnReqDTO.setRequestMessageString(requestMsgString);
// authenticate the user
SAMLSSORespDTO authRespDTO = ssoServiceClient.authenticate(authnReqDTO, ssoTokenID);
if (authRespDTO.getSessionEstablished()) { // authentication is SUCCESSFUL
// Store the cookie
storeSSOTokenCookie(ssoTokenID, req, resp);
// add relay state, assertion string and ACS URL as request parameters.
req.setAttribute(SAMLConstants.RELAY_STATE, relayState);
req.setAttribute(SAMLConstants.ASSERTION_STR, authRespDTO.getRespString());
req.setAttribute(SAMLConstants.ASSRTN_CONSUMER_URL, authRespDTO.getAssertionConsumerURL());
req.setAttribute(SAMLConstants.SUBJECT, authRespDTO.getSubject());
RequestDispatcher reqDispatcher = req.getRequestDispatcher("/carbon/sso-acs/federation_ajaxprocessor.jsp");
reqDispatcher.forward(req, resp);
return;
}
}
private void storeSSOTokenCookie(String ssoTokenID, HttpServletRequest req,
HttpServletResponse resp) {
Cookie ssoTokenCookie = getSSOTokenCookie(req);
if (ssoTokenCookie == null) {
ssoTokenCookie = new Cookie(SSO_TOKEN_ID, ssoTokenID);
ssoTokenCookie.setSecure(true);
ssoTokenCookie.setHttpOnly(true);
}
ssoTokenCookie.setMaxAge(SSO_SESSION_EXPIRE);
resp.addCookie(ssoTokenCookie);
}
private Cookie getSSOTokenCookie(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("ssoTokenId".equals(cookie.getName())) {
return cookie;
}
}
}
return null;
}
private void handleExternalLogout(HttpServletRequest req, HttpServletResponse resp, String externalLogoutPage) throws IOException {
HttpSession currentSession = req.getSession(false);
if (currentSession != null) {
// check if current session has expired
currentSession.removeAttribute(CarbonSecuredHttpContext.LOGGED_USER);
currentSession.getServletContext().removeAttribute(CarbonSecuredHttpContext.LOGGED_USER);
try {
currentSession.invalidate();
if(log.isDebugEnabled()) {
log.debug("Frontend session invalidated");
}
} catch (Exception ignored) {
// Ignore exception when invalidating and invalidated session
}
}
clearCookies(req, resp);
if (log.isDebugEnabled()) {
log.debug("Sending to " + externalLogoutPage);
}
resp.sendRedirect(externalLogoutPage);
}
private void clearCookies(HttpServletRequest req, HttpServletResponse resp) {
Cookie[] cookies = req.getCookies();
for (Cookie curCookie : cookies) {
if (curCookie.getName().equals("requestedURI")) {
Cookie cookie = new Cookie("requestedURI", null);
cookie.setPath("/");
cookie.setMaxAge(0);
resp.addCookie(cookie);
} else if (curCookie.getName().equals(CarbonConstants.REMEMBER_ME_COOKE_NAME)) {
Cookie cookie = new Cookie(CarbonConstants.REMEMBER_ME_COOKE_NAME, null);
cookie.setPath("/");
cookie.setMaxAge(0);
resp.addCookie(cookie);
}
}
}
}