/* * Copyright (c) 2005, 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.authenticator.passive.sts.manager; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xerces.impl.Constants; import org.apache.xerces.util.SecurityManager; import org.opensaml.Configuration; import org.opensaml.DefaultBootstrap; import org.opensaml.saml1.core.Attribute; import org.opensaml.saml1.core.AttributeStatement; import org.opensaml.saml1.core.NameIdentifier; import org.opensaml.saml1.core.Subject; import org.opensaml.xml.ConfigurationException; import org.opensaml.xml.XMLObject; import org.opensaml.xml.io.Unmarshaller; import org.opensaml.xml.io.UnmarshallerFactory; import org.opensaml.xml.io.UnmarshallingException; import org.opensaml.xml.security.x509.X509Credential; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.wso2.carbon.identity.application.authentication.framework.config.model.ExternalIdPConfig; import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext; 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.identity.application.authenticator.passive.sts.exception.PassiveSTSException; import org.wso2.carbon.identity.application.authenticator.passive.sts.util.CarbonEntityResolver; import org.wso2.carbon.identity.application.authenticator.passive.sts.util.PassiveSTSConstants; import org.wso2.carbon.identity.application.common.model.Claim; import org.wso2.carbon.identity.application.common.model.ClaimMapping; import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.xml.sax.SAXException; import javax.servlet.http.HttpServletRequest; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.HashMap; import java.util.List; import java.util.Map; public class PassiveSTSManager { private static final String SECURITY_MANAGER_PROPERTY = Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY; private static final int ENTITY_EXPANSION_LIMIT = 0; private static Log log = LogFactory.getLog(PassiveSTSManager.class); private static boolean bootStrapped = false; private X509Credential credential = null; public PassiveSTSManager(ExternalIdPConfig externalIdPConfig) throws PassiveSTSException { String credentialImplClass = "org.wso2.carbon.identity.application.authenticator.passive.sts.manager.STSAgentKeyStoreCredential"; try { synchronized (this) { if (credential == null) { synchronized (this) { STSAgentCredential stsAgentCredential = (STSAgentCredential) Class.forName(credentialImplClass).newInstance(); stsAgentCredential.init(externalIdPConfig); this.credential = new X509CredentialImpl(stsAgentCredential); } } } } catch (ClassNotFoundException|InstantiationException|IllegalAccessException e) { throw new PassiveSTSException("Error while instantiating SSOAgentCredentialImplClass: " + credentialImplClass, e); } } public static void doBootstrap() { /* Initializing the OpenSAML library */ if (!bootStrapped) { try { DefaultBootstrap.bootstrap(); bootStrapped = true; } catch (ConfigurationException e) { log.error("Error in bootstrapping the OpenSAML2 library", e); } } } /** * Returns the redirection URL with the appended SAML2 * Request message * * @param request * @param loginPage * @param contextIdentifier * @return redirectionUrl * @throws PassiveSTSException */ public String buildRequest(HttpServletRequest request, String loginPage, String contextIdentifier, Map<String, String> authenticationProperties) throws PassiveSTSException { String replyUrl = IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, true, true); String action = "wsignin1.0"; String realm = authenticationProperties.get(PassiveSTSConstants.REALM_ID); String redirectUrl = loginPage + "?wa=" + action + "&wreply=" + replyUrl + "&wtrealm=" + realm; try { redirectUrl = redirectUrl + "&wctx=" + URLEncoder.encode(contextIdentifier, "UTF-8").trim(); } catch (UnsupportedEncodingException e) { throw new PassiveSTSException("Error occurred while url encoding WCTX ", e); } return redirectUrl; } /** * @param request * @param externalIdPConfig * @throws PassiveSTSException */ public void processResponse(HttpServletRequest request, AuthenticationContext context) throws PassiveSTSException { doBootstrap(); String response = request.getParameter(PassiveSTSConstants.HTTP_PARAM_PASSIVE_STS_RESULT).replaceAll("(\\r|\\n)", ""); // there is no unmarshaller to unmarshall "RequestSecurityTokenResponseCollection". Therefore retrieve Assertion XMLObject xmlObject = unmarshall(response); if (xmlObject == null) { throw new PassiveSTSException("SAML Assertion not found in the Response"); } String subject = null; Map<String, String> attributeMap = new HashMap<String, String>(); if (xmlObject instanceof org.opensaml.saml1.core.Assertion) { org.opensaml.saml1.core.Assertion assertion = (org.opensaml.saml1.core.Assertion) xmlObject; if (CollectionUtils.isNotEmpty(assertion.getAuthenticationStatements())) { Subject subjectElem = assertion.getAuthenticationStatements().get(0).getSubject(); if (subjectElem != null) { NameIdentifier nameIdentifierElem = subjectElem.getNameIdentifier(); if (nameIdentifierElem != null) { subject = nameIdentifierElem.getNameIdentifier(); } } } if (CollectionUtils.isNotEmpty(assertion.getAttributeStatements())) { if (subject == null) { subject = assertion.getAttributeStatements().get(0).getSubject().getNameIdentifier().getNameIdentifier(); } for (AttributeStatement statement : assertion.getAttributeStatements()) { List<Attribute> attributes = statement.getAttributes(); for (Attribute attribute : attributes) { String attributeUri = attribute.getAttributeNamespace(); List<XMLObject> xmlObjects = attribute.getAttributeValues(); for (XMLObject object : xmlObjects) { String attributeValue = object.getDOM().getTextContent(); attributeMap.put(attributeUri, attributeValue); } } } } } else if (xmlObject instanceof org.opensaml.saml2.core.Assertion) { org.opensaml.saml2.core.Assertion assertion = (org.opensaml.saml2.core.Assertion) xmlObject; if (assertion.getSubject() != null && assertion.getSubject().getNameID() != null) { subject = assertion.getSubject().getNameID().getValue(); } for (org.opensaml.saml2.core.AttributeStatement statement : assertion.getAttributeStatements()) { List<org.opensaml.saml2.core.Attribute> attributes = statement.getAttributes(); for (org.opensaml.saml2.core.Attribute attribute : attributes) { String attributeUri = attribute.getName(); List<XMLObject> xmlObjects = attribute.getAttributeValues(); for (XMLObject object : xmlObjects) { String attributeValue = object.getDOM().getTextContent(); attributeMap.put(attributeUri, attributeValue); } } } } Map<ClaimMapping, String> claimMappingStringMap = getClaimMappingsMap(attributeMap); String isSubjectInClaimsProp = context.getAuthenticatorProperties().get( IdentityApplicationConstants.Authenticator.SAML2SSO.IS_USER_ID_IN_CLAIMS); if ("true".equalsIgnoreCase(isSubjectInClaimsProp)) { subject = FrameworkUtils.getFederatedSubjectFromClaims( context.getExternalIdP().getIdentityProvider(), claimMappingStringMap); if (subject == null) { log.warn("Subject claim could not be found amongst attribute statements. " + "Defaulting to Name Identifier."); } } if (subject == null) { throw new PassiveSTSException("Cannot find federated User Identifier"); } AuthenticatedUser authenticatedUser = AuthenticatedUser.createFederateAuthenticatedUserFromSubjectIdentifier(subject); authenticatedUser.setUserAttributes(claimMappingStringMap); context.setSubject(authenticatedUser); } /** * @param samlString * @return * @throws PassiveSTSException */ private XMLObject unmarshall(String samlString) throws PassiveSTSException { String samlStr = samlString; try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setFeature(PassiveSTSConstants.EXTERNAL_GENERAL_ENTITIES_URI, false); documentBuilderFactory.setNamespaceAware(true); documentBuilderFactory.setExpandEntityReferences(false); documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); SecurityManager securityManager = new SecurityManager(); securityManager.setEntityExpansionLimit(ENTITY_EXPANSION_LIMIT); documentBuilderFactory.setAttribute(SECURITY_MANAGER_PROPERTY, securityManager); DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder(); docBuilder.setEntityResolver(new CarbonEntityResolver()); ByteArrayInputStream is = new ByteArrayInputStream(samlStr.getBytes(Charset.forName("UTF-8"))); Document document = docBuilder.parse(is); Element element = document.getDocumentElement(); NodeList nodeList = element.getElementsByTagNameNS("http://docs.oasis-open.org/ws-sx/ws-trust/200512", "RequestedSecurityToken"); if (nodeList == null || nodeList.getLength() == 0) { throw new PassiveSTSException("Security Token is not found in the Response"); } if (nodeList.getLength() > 1) { log.warn("More than one Security Token is found in the Response"); } Element node = (Element) nodeList.item(0).getFirstChild(); UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory(); Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(node); return unmarshaller.unmarshall(node); } catch (ParserConfigurationException e) { throw new PassiveSTSException(PassiveSTSConstants.ERROR_IN_UNMARSHALLING_SAML_REQUEST_FROM_THE_ENCODED_STRING, e); } catch (UnmarshallingException e) { throw new PassiveSTSException(PassiveSTSConstants.ERROR_IN_UNMARSHALLING_SAML_REQUEST_FROM_THE_ENCODED_STRING, e); } catch (SAXException e) { throw new PassiveSTSException(PassiveSTSConstants.ERROR_IN_UNMARSHALLING_SAML_REQUEST_FROM_THE_ENCODED_STRING, e); } catch (IOException e) { throw new PassiveSTSException(PassiveSTSConstants.ERROR_IN_UNMARSHALLING_SAML_REQUEST_FROM_THE_ENCODED_STRING, e); } } /* * Process the response and returns the results */ private Map<ClaimMapping, String> getClaimMappingsMap(Map<String, String> userAttributes) { Map<ClaimMapping, String> results = new HashMap<ClaimMapping, String>(); for (Map.Entry<String, String> entry : userAttributes.entrySet()) { ClaimMapping claimMapping = new ClaimMapping(); Claim claim = new Claim(); claim.setClaimUri(entry.getKey()); claimMapping.setRemoteClaim(claim); results.put(claimMapping, entry.getValue()); } return results; } }