/*
* Copyright (c) 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.sts.passive.ui;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.owasp.encoder.Encode;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.identity.application.authentication.framework.cache.AuthenticationRequestCacheEntry;
import org.wso2.carbon.identity.application.authentication.framework.cache.AuthenticationResultCache;
import org.wso2.carbon.identity.application.authentication.framework.cache.AuthenticationResultCacheEntry;
import org.wso2.carbon.identity.application.authentication.framework.cache.AuthenticationResultCacheKey;
import org.wso2.carbon.identity.application.authentication.framework.config.ConfigurationFacade;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticationRequest;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticationResult;
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.cache.CacheEntry;
import org.wso2.carbon.identity.application.common.model.ClaimMapping;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.sts.passive.stub.types.RequestToken;
import org.wso2.carbon.identity.sts.passive.stub.types.ResponseToken;
import org.wso2.carbon.identity.sts.passive.ui.cache.SessionDataCache;
import org.wso2.carbon.identity.sts.passive.ui.cache.SessionDataCacheEntry;
import org.wso2.carbon.identity.sts.passive.ui.cache.SessionDataCacheKey;
import org.wso2.carbon.identity.sts.passive.ui.client.IdentityPassiveSTSClient;
import org.wso2.carbon.identity.sts.passive.ui.dto.SessionDTO;
import org.wso2.carbon.identity.sts.passive.ui.util.PassiveSTSUtil;
import org.wso2.carbon.idp.mgt.util.IdPManagementUtil;
import org.wso2.carbon.registry.core.utils.UUIDGenerator;
import org.wso2.carbon.ui.CarbonUIUtil;
import org.wso2.carbon.utils.CarbonUtils;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;
public class PassiveSTS extends HttpServlet {
private static final Log log = LogFactory.getLog(PassiveSTS.class);
/**
*
*/
private static final long serialVersionUID = 1927253892844132565L;
private static final String SESSION_DATA_KEY = "sessionDataKey";
private String stsRedirectPage = null;
private String redirectHtmlFilePath = CarbonUtils.getCarbonHome() + File.separator + "repository"
+ File.separator + "resources" + File.separator + "identity" + File.separator + "pages" + File.separator +
"sts_response.html";
/**
* This method reads Passive STS Html Redirect file content.
* This should have been implemented in the backend but done in the front end to avoid API changes
*
* @return Passive STS Html Redirect Page File Content
*/
private String readPassiveSTSHtmlRedirectPage() {
FileInputStream fileInputStream = null;
String fileContent = null;
try {
fileInputStream = new FileInputStream(new File(redirectHtmlFilePath));
fileContent = new Scanner(fileInputStream, "UTF-8").useDelimiter("\\A").next();
if (log.isDebugEnabled()) {
log.debug("sts_response.html : " + fileContent);
}
} catch (FileNotFoundException e) {
// The Passive STS Redirect HTML file is optional. When the file is not found, use the default page content.
if (log.isDebugEnabled()) {
log.debug("Passive STS Redirect HTML file not found in : " + redirectHtmlFilePath +
". Default Redirect is used.");
}
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
log.error("Error occurred when closing file input stream for sts_response.html", e);
}
}
}
if (StringUtils.isBlank(fileContent)) {
fileContent = "<html>" +
" <body>" +
" <p>You are now redirected to $url ." +
" If the redirection fails, please click the post button." +
" </p>" +
" <form method='post' action='$url'>" +
" <p>" +
" <!--$params-->" +
" <!--$additionalParams-->" +
" <button type='submit'>POST</button>" +
" </p>" +
" </form>" +
" <script type='text/javascript'>" +
" document.forms[0].submit();" +
" </script>" +
" </body>" +
"</html>";
}
// Adding parameters to the Passive STS HTML redirect page
String parameters = "<input type=\"hidden\" name=\"wa\" value=\"$action\">" +
"<input type=\"hidden\" name=\"wresult\" value=\"$result\">";
if (StringUtils.isNotBlank(parameters)) {
fileContent = fileContent.replace("<!--$params-->", parameters);
}
return fileContent;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String sessionDataKey = req.getParameter(SESSION_DATA_KEY);
if (sessionDataKey != null) {
handleResponseFromAuthenticationFramework(req, resp);
FrameworkUtils.removeAuthenticationResultFromCache(sessionDataKey);
} else if ("wsignout1.0".equals(getAttribute(req.getParameterMap(), PassiveRequestorConstants.ACTION))) {
handleLogoutRequest(req, resp);
} else {
handleAuthenticationRequest(req, resp);
}
}
private void sendData(HttpServletResponse httpResp, ResponseToken respToken, String action,
String authenticatedIdPs)
throws ServletException, IOException {
if (StringUtils.isBlank(stsRedirectPage)) {
// Read the Passive STS Html Redirect Page File Content
stsRedirectPage = readPassiveSTSHtmlRedirectPage();
}
String finalPage = null;
String htmlPage = stsRedirectPage;
String pageWithReply = htmlPage.replace("$url", String.valueOf(respToken.getReplyTo()));
String pageWithReplyAction = pageWithReply.replace("$action", Encode.forHtmlAttribute(String.valueOf(action)));
String pageWithReplyActionResult = pageWithReplyAction.replace("$result",
Encode.forHtmlAttribute(String.valueOf(respToken.getResults())));
String pageWithReplyActionResultContext;
if (respToken.getContext() != null) {
pageWithReplyActionResultContext = pageWithReplyActionResult.replace(
PassiveRequestorConstants.PASSIVE_ADDITIONAL_PARAMETER,
PassiveRequestorConstants.PASSIVE_ADDITIONAL_PARAMETER + "<input type='hidden' name='wctx' value='"
+ Encode.forHtmlAttribute(respToken.getContext()) + "'>");
} else {
pageWithReplyActionResultContext = pageWithReplyActionResult;
}
if (authenticatedIdPs == null || authenticatedIdPs.isEmpty()) {
finalPage = pageWithReplyActionResultContext;
} else {
finalPage = pageWithReplyActionResultContext.replace(PassiveRequestorConstants.PASSIVE_ADDITIONAL_PARAMETER,
"<input type='hidden' name='AuthenticatedIdPs' value='" +
Encode.forHtmlAttribute(authenticatedIdPs) + "'>");
}
PrintWriter out = httpResp.getWriter();
out.print(finalPage);
if (log.isDebugEnabled()) {
log.debug("sts_response.html : " + finalPage);
}
return;
}
private String getAttribute(Map paramMap, String name) {
if (paramMap.get(name) != null && paramMap.get(name) instanceof String[]) {
return ((String[]) paramMap.get(name))[0];
}
return null;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
private void openURLWithNoTrust(String realm) throws IOException {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
// Nothing to implement
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
// Nothing to implement
}
} };
// Ignore differences between given hostname and certificate hostname
HostnameVerifier hv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
String renegotiation = System.getProperty("sun.security.ssl.allowUnsafeRenegotiation");
try {
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(hv);
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
new URL(realm).getContent();
} finally {
HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
System.getProperty("sun.security.ssl.allowUnsafeRenegotiation", renegotiation);
}
} catch (Exception ignore) {
if (log.isDebugEnabled()) {
log.debug("Error while installing trust manager", ignore);
}
}
}
private void persistRealms(RequestToken reqToken, HttpSession session) {
Set<String> realms = (Set<String>) session.getAttribute("realms");
if (realms == null) {
realms = new HashSet<>();
session.setAttribute("realms", realms);
}
realms.add(reqToken.getRealm());
}
private void sendToAuthenticationFramework(HttpServletRequest request, HttpServletResponse response,
String sessionDataKey, SessionDTO sessionDTO) throws IOException {
String commonAuthURL = IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, false, true);
String selfPath = request.getRequestURI();
//Authentication context keeps data which should be sent to commonAuth endpoint
AuthenticationRequest authenticationRequest = new AuthenticationRequest();
authenticationRequest.setRelyingParty(sessionDTO.getRealm());
authenticationRequest.setCommonAuthCallerPath(selfPath);
authenticationRequest.setForceAuth(false);
authenticationRequest.setRequestQueryParams(request.getParameterMap());
//adding headers in out going request to authentication request context
for (Enumeration e = request.getHeaderNames(); e.hasMoreElements(); ) {
String headerName = e.nextElement().toString();
authenticationRequest.addHeader(headerName, request.getHeader(headerName));
}
//Add authenticationRequest cache entry to cache
AuthenticationRequestCacheEntry authRequest = new AuthenticationRequestCacheEntry(authenticationRequest);
FrameworkUtils.addAuthenticationRequestToCache(sessionDataKey, authRequest);
StringBuilder queryStringBuilder = new StringBuilder();
queryStringBuilder.append("?").
append(FrameworkConstants.SESSION_DATA_KEY).
append("=").
append(sessionDataKey).
append("&").
append(FrameworkConstants.RequestParams.TYPE).
append("=").
append(FrameworkConstants.PASSIVE_STS);
response.sendRedirect(commonAuthURL + queryStringBuilder.toString());
}
private void handleResponseFromAuthenticationFramework(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String sessionDataKey = request.getParameter(FrameworkConstants.SESSION_DATA_KEY);
SessionDTO sessionDTO = getSessionDataFromCache(sessionDataKey);
AuthenticationResult authnResult = getAuthenticationResultFromCache(sessionDataKey);
if (sessionDTO != null && authnResult != null) {
if (authnResult.isAuthenticated()) {
process(request, response, sessionDTO, authnResult);
} else {
// TODO how to send back the authentication failure to client.
//for now user will be redirected back to the framework
// According to ws-federation-1.2-spec; 'wtrealm' will not be sent in the Passive STS Logout Request.
if (sessionDTO.getRealm() == null || sessionDTO.getRealm().trim().length() == 0) {
sessionDTO.setRealm(new String());
}
sendToAuthenticationFramework(request, response, sessionDataKey, sessionDTO);
}
} else {
sendToRetryPage(request, response);
}
}
private void process(HttpServletRequest request, HttpServletResponse response,
SessionDTO sessionDTO, AuthenticationResult authnResult) throws ServletException, IOException {
HttpSession session = request.getSession();
session.removeAttribute(PassiveRequestorConstants.PASSIVE_REQ_ATTR_MAP);
RequestToken reqToken = new RequestToken();
Map<ClaimMapping, String> attrMap = authnResult.getSubject().getUserAttributes();
StringBuilder buffer = null;
if (MapUtils.isNotEmpty(attrMap)) {
buffer = new StringBuilder();
for (Iterator<Entry<ClaimMapping, String>> iterator = attrMap.entrySet().iterator(); iterator
.hasNext(); ) {
Entry<ClaimMapping, String> entry = iterator.next();
buffer.append("{" + entry.getKey().getRemoteClaim().getClaimUri() + "|" + entry.getValue() + "}#CODE#");
}
}
reqToken.setAction(sessionDTO.getAction());
if (buffer != null) {
reqToken.setAttributes(buffer.toString());
} else {
reqToken.setAttributes(sessionDTO.getAttributes());
}
reqToken.setContext(sessionDTO.getContext());
reqToken.setReplyTo(sessionDTO.getReplyTo());
reqToken.setPseudo(sessionDTO.getPseudo());
reqToken.setRealm(sessionDTO.getRealm());
reqToken.setRequest(sessionDTO.getRequest());
reqToken.setRequestPointer(sessionDTO.getRequestPointer());
reqToken.setPolicy(sessionDTO.getPolicy());
reqToken.setPseudo(session.getId());
reqToken.setUserName(authnResult.getSubject().getAuthenticatedSubjectIdentifier());
reqToken.setTenantDomain(sessionDTO.getTenantDomain());
String serverURL = CarbonUIUtil.getServerURL(session.getServletContext(), session);
ConfigurationContext configContext =
(ConfigurationContext) session.getServletContext().getAttribute(CarbonConstants.CONFIGURATION_CONTEXT);
IdentityPassiveSTSClient passiveSTSClient = null;
passiveSTSClient = new IdentityPassiveSTSClient(serverURL, configContext);
ResponseToken respToken = passiveSTSClient.getResponse(reqToken);
if (respToken != null && respToken.getResults() != null) {
persistRealms(reqToken, request.getSession());
sendData(response, respToken, reqToken.getAction(),
authnResult.getAuthenticatedIdPs());
}
}
private void handleLogoutRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
Set<String> realms = (Set<String>) request.getSession().getAttribute("realms");
if (CollectionUtils.isNotEmpty(realms)) {
for (String realm : realms) {
openURLWithNoTrust(realm + "?wa=wsignoutcleanup1.0");
}
}
try {
sendFrameworkForLogout(request, response);
} catch (ServletException e) {
if (log.isDebugEnabled()) {
log.debug("Error while sending the logout request", e);
}
}
}
private void sendFrameworkForLogout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Map paramMap = request.getParameterMap();
SessionDTO sessionDTO = new SessionDTO();
sessionDTO.setAction(getAttribute(paramMap, PassiveRequestorConstants.ACTION));
sessionDTO.setAttributes(getAttribute(paramMap, PassiveRequestorConstants.ATTRIBUTE));
sessionDTO.setContext(getAttribute(paramMap, PassiveRequestorConstants.CONTEXT));
sessionDTO.setReplyTo(getAttribute(paramMap, PassiveRequestorConstants.REPLY_TO));
sessionDTO.setPseudo(getAttribute(paramMap, PassiveRequestorConstants.PSEUDO));
sessionDTO.setRealm(getAttribute(paramMap, PassiveRequestorConstants.REALM));
sessionDTO.setRequest(getAttribute(paramMap, PassiveRequestorConstants.REQUEST));
sessionDTO.setRequestPointer(getAttribute(paramMap, PassiveRequestorConstants.REQUEST_POINTER));
sessionDTO.setPolicy(getAttribute(paramMap, PassiveRequestorConstants.POLCY));
sessionDTO.setReqQueryString(request.getQueryString());
String sessionDataKey = UUIDGenerator.generateUUID();
addSessionDataToCache(sessionDataKey, sessionDTO);
String commonAuthURL = IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, false, true);
String selfPath = request.getRequestURI();
AuthenticationRequest authenticationRequest = new AuthenticationRequest();
authenticationRequest.addRequestQueryParam(FrameworkConstants.RequestParams.LOGOUT,
new String[]{Boolean.TRUE.toString()});
authenticationRequest.setRequestQueryParams(request.getParameterMap());
authenticationRequest.setCommonAuthCallerPath(selfPath);
authenticationRequest.appendRequestQueryParams(request.getParameterMap());
// According to ws-federation-1.2-spec; 'wtrealm' will not be sent in the Passive STS Logout Request.
if (sessionDTO.getRealm() == null || sessionDTO.getRealm().trim().length() == 0) {
authenticationRequest.setRelyingParty(new String());
}
for (Enumeration e = request.getHeaderNames(); e.hasMoreElements(); ) {
String headerName = e.nextElement().toString();
authenticationRequest.addHeader(headerName, request.getHeader(headerName));
}
AuthenticationRequestCacheEntry authRequest = new AuthenticationRequestCacheEntry
(authenticationRequest);
FrameworkUtils.addAuthenticationRequestToCache(sessionDataKey, authRequest);
String queryParams = "?" + FrameworkConstants.SESSION_DATA_KEY + "=" + sessionDataKey
+ "&" + FrameworkConstants.RequestParams.TYPE + "=" + FrameworkConstants.PASSIVE_STS;
response.sendRedirect(commonAuthURL + queryParams);
}
private void handleAuthenticationRequest(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
Map paramMap = request.getParameterMap();
SessionDTO sessionDTO = new SessionDTO();
sessionDTO.setAction(getAttribute(paramMap, PassiveRequestorConstants.ACTION));
sessionDTO.setAttributes(getAttribute(paramMap, PassiveRequestorConstants.ATTRIBUTE));
sessionDTO.setContext(getAttribute(paramMap, PassiveRequestorConstants.CONTEXT));
sessionDTO.setReplyTo(getAttribute(paramMap, PassiveRequestorConstants.REPLY_TO));
sessionDTO.setPseudo(getAttribute(paramMap, PassiveRequestorConstants.PSEUDO));
sessionDTO.setRealm(getAttribute(paramMap, PassiveRequestorConstants.REALM));
sessionDTO.setRequest(getAttribute(paramMap, PassiveRequestorConstants.REQUEST));
sessionDTO.setRequestPointer(getAttribute(paramMap, PassiveRequestorConstants.REQUEST_POINTER));
sessionDTO.setPolicy(getAttribute(paramMap, PassiveRequestorConstants.POLCY));
sessionDTO.setTenantDomain(getAttribute(paramMap, MultitenantConstants.TENANT_DOMAIN));
sessionDTO.setReqQueryString(request.getQueryString());
String sessionDataKey = UUIDGenerator.generateUUID();
addSessionDataToCache(sessionDataKey, sessionDTO);
sendToAuthenticationFramework(request, response, sessionDataKey, sessionDTO);
}
private void addSessionDataToCache(String sessionDataKey, SessionDTO sessionDTO) {
SessionDataCacheKey cacheKey = new SessionDataCacheKey(sessionDataKey);
SessionDataCacheEntry cacheEntry = new SessionDataCacheEntry();
cacheEntry.setSessionDTO(sessionDTO);
SessionDataCache.getInstance().addToCache(cacheKey, cacheEntry);
}
private SessionDTO getSessionDataFromCache(String sessionDataKey) {
SessionDTO sessionDTO = null;
SessionDataCacheKey cacheKey = new SessionDataCacheKey(sessionDataKey);
SessionDataCacheEntry cacheEntry = SessionDataCache.getInstance().getValueFromCache(cacheKey);
if (cacheEntry != null) {
sessionDTO = cacheEntry.getSessionDTO();
} else {
log.error("SessionDTO does not exist. Probably due to cache timeout");
}
return sessionDTO;
}
private AuthenticationResult getAuthenticationResultFromCache(String sessionDataKey) {
AuthenticationResult authResult = null;
AuthenticationResultCacheEntry authResultCacheEntry = FrameworkUtils.getAuthenticationResultFromCache(sessionDataKey);
if (authResultCacheEntry != null) {
authResult = authResultCacheEntry.getResult();
} else {
log.error("AuthenticationResult does not exist. Probably due to cache timeout");
}
return authResult;
}
private void sendToRetryPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.sendRedirect(PassiveSTSUtil.getRetryUrl());
}
}