/*
* Copyright (c) 2014, 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.samlsso.manager;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
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.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLVersion;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.common.Extensions;
import org.opensaml.saml2.common.impl.ExtensionsBuilder;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.AttributeStatement;
import org.opensaml.saml2.core.Audience;
import org.opensaml.saml2.core.AudienceRestriction;
import org.opensaml.saml2.core.AuthnContext;
import org.opensaml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.EncryptedAssertion;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.LogoutRequest;
import org.opensaml.saml2.core.LogoutResponse;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.NameIDPolicy;
import org.opensaml.saml2.core.NameIDType;
import org.opensaml.saml2.core.RequestAbstractType;
import org.opensaml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.SessionIndex;
import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
import org.opensaml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml2.core.impl.LogoutRequestBuilder;
import org.opensaml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml2.core.impl.NameIDPolicyBuilder;
import org.opensaml.saml2.core.impl.RequestedAuthnContextBuilder;
import org.opensaml.saml2.core.impl.SessionIndexBuilder;
import org.opensaml.saml2.encryption.Decrypter;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.encryption.EncryptedKey;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallerFactory;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.security.SecurityHelper;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver;
import org.opensaml.xml.security.x509.X509Credential;
import org.opensaml.xml.signature.SignatureValidator;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.XMLHelper;
import org.opensaml.xml.validation.ValidationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.identity.application.authentication.framework.config.builder.FileBasedConfigurationBuilder;
import org.wso2.carbon.identity.application.authentication.framework.config.model.AuthenticatorConfig;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticationRequest;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.application.authenticator.samlsso.exception.SAMLSSOException;
import org.wso2.carbon.identity.application.authenticator.samlsso.util.CarbonEntityResolver;
import org.wso2.carbon.identity.application.authenticator.samlsso.util.SSOConstants;
import org.wso2.carbon.identity.application.authenticator.samlsso.util.SSOUtils;
import org.wso2.carbon.identity.application.common.model.ClaimMapping;
import org.wso2.carbon.identity.application.common.model.IdentityProvider;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.xml.sax.SAXException;
import javax.crypto.SecretKey;
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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
public class DefaultSAML2SSOManager implements SAML2SSOManager {
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 final String SIGN_AUTH2_SAML_USING_SUPER_TENANT = "SignAuth2SAMLUsingSuperTenant";
private static Log log = LogFactory.getLog(DefaultSAML2SSOManager.class);
private static boolean bootStrapped = false;
private IdentityProvider identityProvider = null;
private Map<String, String> properties;
private String tenantDomain;
public static void doBootstrap() {
/* Initializing the OpenSAML library */
if (!bootStrapped) {
Thread thread = Thread.currentThread();
ClassLoader loader = thread.getContextClassLoader();
thread.setContextClassLoader(new DefaultSAML2SSOManager().getClass().getClassLoader());
try {
DefaultBootstrap.bootstrap();
bootStrapped = true;
} catch (ConfigurationException e) {
log.error("Error in bootstrapping the OpenSAML2 library", e);
} finally {
thread.setContextClassLoader(loader);
}
}
}
@Override
public void init(String tenantDomain, Map<String, String> properties, IdentityProvider idp)
throws SAMLSSOException {
this.tenantDomain = tenantDomain;
this.identityProvider = idp;
this.properties = properties;
}
/**
* Returns the redirection URL with the appended SAML2
* Request message
*
* @param request SAML 2 request
* @return redirectionUrl
*/
@Override
public String buildRequest(HttpServletRequest request, boolean isLogout, boolean isPassive,
String loginPage, AuthenticationContext context)
throws SAMLSSOException {
doBootstrap();
String contextIdentifier = context.getContextIdentifier();
RequestAbstractType requestMessage;
if (request.getParameter(SSOConstants.HTTP_POST_PARAM_SAML2_AUTH_REQ) == null) {
String queryParam = context.getQueryParams();
if (queryParam != null) {
String[] params = queryParam.split("&");
for (String param : params) {
String[] values = param.split("=");
if (values.length == 2 && SSOConstants.HTTP_POST_PARAM_SAML2_AUTH_REQ.equals(values[0])) {
request.setAttribute(SSOConstants.HTTP_POST_PARAM_SAML2_AUTH_REQ, values[1]);
break;
}
}
}
}
if (!isLogout) {
requestMessage = buildAuthnRequest(request, isPassive, loginPage, context);
} else {
String username = (String) request.getSession().getAttribute(SSOConstants.LOGOUT_USERNAME);
String sessionIndex = (String) request.getSession().getAttribute(SSOConstants.LOGOUT_SESSION_INDEX);
String nameQualifier = (String) request.getSession().getAttribute(SSOConstants.NAME_QUALIFIER);
String spNameQualifier = (String) request.getSession().getAttribute(SSOConstants.SP_NAME_QUALIFIER);
requestMessage = buildLogoutRequest(username, sessionIndex, loginPage, nameQualifier, spNameQualifier);
}
String idpUrl = null;
boolean isSignAuth2SAMLUsingSuperTenant = false;
String encodedRequestMessage = encodeRequestMessage(requestMessage);
StringBuilder httpQueryString = new StringBuilder("SAMLRequest=" + encodedRequestMessage);
try {
httpQueryString.append("&RelayState=" + URLEncoder.encode(contextIdentifier, "UTF-8").trim());
} catch (UnsupportedEncodingException e) {
throw new SAMLSSOException("Error occurred while url encoding RelayState", e);
}
if (SSOUtils.isAuthnRequestSigned(properties)) {
String signatureAlgoProp = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.SIGNATURE_ALGORITHM);
if (StringUtils.isEmpty(signatureAlgoProp)) {
signatureAlgoProp = IdentityApplicationConstants.XML.SignatureAlgorithm.RSA_SHA1;
}
String signatureAlgo = IdentityApplicationManagementUtil.getXMLSignatureAlgorithms()
.get(signatureAlgoProp);
Map<String, String> parameterMap = FileBasedConfigurationBuilder.getInstance()
.getAuthenticatorBean(SSOConstants.AUTHENTICATOR_NAME).getParameterMap();
if (parameterMap.size() > 0) {
isSignAuth2SAMLUsingSuperTenant = Boolean.parseBoolean(parameterMap.
get(SIGN_AUTH2_SAML_USING_SUPER_TENANT));
}
if (isSignAuth2SAMLUsingSuperTenant) {
SSOUtils.addSignatureToHTTPQueryString(httpQueryString, signatureAlgo,
new X509CredentialImpl(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, null));
} else {
SSOUtils.addSignatureToHTTPQueryString(httpQueryString, signatureAlgo,
new X509CredentialImpl(context.getTenantDomain(), null));
}
}
if (loginPage.indexOf("?") > -1) {
idpUrl = loginPage.concat("&").concat(httpQueryString.toString());
} else {
idpUrl = loginPage.concat("?").concat(httpQueryString.toString());
}
return idpUrl;
}
/**
* @param request
* @param isLogout
* @param isPassive
* @param loginPage
* @return return encoded SAML Auth request
* @throws SAMLSSOException
*/
public String buildPostRequest(HttpServletRequest request, boolean isLogout,
boolean isPassive, String loginPage, AuthenticationContext context) throws SAMLSSOException {
doBootstrap();
RequestAbstractType requestMessage;
String signatureAlgoProp = null;
String digestAlgoProp = null;
String includeCertProp = null;
String signatureAlgo = null;
String digestAlgo = null;
boolean includeCert = false;
// get Signature Algorithm
signatureAlgoProp = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.SIGNATURE_ALGORITHM);
if (StringUtils.isEmpty(signatureAlgoProp)) {
signatureAlgoProp = IdentityApplicationConstants.XML.SignatureAlgorithm.RSA_SHA1;
}
signatureAlgo = IdentityApplicationManagementUtil.getXMLSignatureAlgorithms().get(signatureAlgoProp);
// get Digest Algorithm
digestAlgoProp = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.DIGEST_ALGORITHM);
if (StringUtils.isEmpty(digestAlgoProp)) {
digestAlgoProp = IdentityApplicationConstants.XML.DigestAlgorithm.SHA1;
}
digestAlgo = IdentityApplicationManagementUtil.getXMLDigestAlgorithms().get(digestAlgoProp);
includeCertProp = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.INCLUDE_CERT);
if (StringUtils.isEmpty(includeCertProp) || Boolean.parseBoolean(includeCertProp)) {
includeCert = true;
}
if (!isLogout) {
requestMessage = buildAuthnRequest(request, isPassive, loginPage, context);
if (SSOUtils.isAuthnRequestSigned(properties)) {
SSOUtils.setSignature(requestMessage, signatureAlgo, digestAlgo, includeCert,
new X509CredentialImpl(context.getTenantDomain(), null));
}
} else {
String username = (String) request.getSession().getAttribute(SSOConstants.LOGOUT_USERNAME);
String sessionIndex = (String) request.getSession().getAttribute(SSOConstants.LOGOUT_SESSION_INDEX);
String nameQualifier = (String) request.getSession().getAttribute(SSOConstants.NAME_QUALIFIER);
String spNameQualifier = (String) request.getSession().getAttribute(SSOConstants.SP_NAME_QUALIFIER);
requestMessage = buildLogoutRequest(username, sessionIndex, loginPage, nameQualifier, spNameQualifier);
if (SSOUtils.isLogoutRequestSigned(properties)) {
SSOUtils.setSignature(requestMessage, signatureAlgo, digestAlgo, includeCert,
new X509CredentialImpl(context.getTenantDomain(), null));
}
}
return SSOUtils.encode(SSOUtils.marshall(requestMessage));
}
@Override
public void processResponse(HttpServletRequest request) throws SAMLSSOException {
doBootstrap();
String decodedResponse = new String(Base64.decode(request.getParameter(
SSOConstants.HTTP_POST_PARAM_SAML2_RESP)));
XMLObject samlObject = unmarshall(decodedResponse);
if (samlObject instanceof LogoutResponse) {
//This is a SAML response for a single logout request from the SP
doSLO(request);
} else {
processSSOResponse(request);
}
}
protected AuthnRequest getAuthnRequest(AuthenticationContext context) throws SAMLSSOException {
AuthnRequest authnRequest = null;
AuthenticationRequest authenticationRequest = context.getAuthenticationRequest();
String[] samlRequestParams = authenticationRequest
.getRequestQueryParam(SSOConstants.HTTP_POST_PARAM_SAML2_AUTH_REQ);
String samlRequest = null;
if (samlRequestParams != null && samlRequestParams.length > 0) {
samlRequest = samlRequestParams[0];
XMLObject xmlObject;
if (authenticationRequest.isPost()) {
xmlObject = unmarshall(SSOUtils.decodeForPost(samlRequest));
} else {
xmlObject = unmarshall(SSOUtils.decode(samlRequest));
}
if (xmlObject instanceof AuthnRequest) {
authnRequest = (AuthnRequest) xmlObject;
}
}
return authnRequest;
}
protected Extensions getSAMLExtensions(HttpServletRequest request) {
try {
String samlRequest = request.getParameter(SSOConstants.HTTP_POST_PARAM_SAML2_AUTH_REQ);
if (samlRequest == null) {
samlRequest = (String) request.getAttribute(SSOConstants.HTTP_POST_PARAM_SAML2_AUTH_REQ);
}
if (samlRequest != null) {
XMLObject xmlObject;
if (SSOConstants.HTTP_POST.equals(request.getMethod())) {
xmlObject = unmarshall(SSOUtils.decodeForPost(samlRequest));
} else {
xmlObject = unmarshall(SSOUtils.decode(samlRequest));
}
if (xmlObject instanceof AuthnRequest) {
AuthnRequest authnRequest = (AuthnRequest) xmlObject;
Extensions oldExtensions = authnRequest.getExtensions();
if (oldExtensions != null) {
ExtensionsBuilder extBuilder = new ExtensionsBuilder();
Extensions extensions = extBuilder.buildObject(SAMLConstants.SAML20P_NS,
Extensions.LOCAL_NAME, SAMLConstants.SAML20P_PREFIX);
extensions.setDOM(oldExtensions.getDOM());
return extensions;
}
}
}
} catch (Exception e) { // TODO IDENTITY-2421
//ignore
log.debug("Error while loading SAML Extensions", e);
}
return null;
}
protected Extensions getSAMLExtensions(AuthnRequest inboundAuthnRequest) {
Extensions extensions = null;
Extensions oldExtensions = inboundAuthnRequest.getExtensions();
if (oldExtensions != null) {
ExtensionsBuilder extBuilder = new ExtensionsBuilder();
extensions = extBuilder.buildObject(SAMLConstants.SAML20P_NS,
Extensions.LOCAL_NAME, SAMLConstants.SAML20P_PREFIX);
extensions.setDOM(oldExtensions.getDOM());
}
return extensions;
}
/**
* This method handles the logout requests from the IdP
* Any request for the defined logout URL is handled here
*
* @param request
* @throws javax.servlet.ServletException
* @throws IOException
*/
public void doSLO(HttpServletRequest request) throws SAMLSSOException {
doBootstrap();
XMLObject samlObject = null;
if (request.getParameter(SSOConstants.HTTP_POST_PARAM_SAML2_AUTH_REQ) != null) {
samlObject = unmarshall(new String(Base64.decode(request.getParameter(
SSOConstants.HTTP_POST_PARAM_SAML2_AUTH_REQ))));
}
if (samlObject == null) {
samlObject = unmarshall(new String(Base64.decode(request.getParameter(
SSOConstants.HTTP_POST_PARAM_SAML2_RESP))));
}
if (samlObject instanceof LogoutRequest) {
LogoutRequest logoutRequest = (LogoutRequest) samlObject;
String sessionIndex = logoutRequest.getSessionIndexes().get(0).getSessionIndex();
} else if (samlObject instanceof LogoutResponse) {
request.getSession().invalidate();
} else {
throw new SAMLSSOException("Invalid Single Logout SAML Request");
}
}
private void processSSOResponse(HttpServletRequest request) throws SAMLSSOException {
Response samlResponse = (Response) unmarshall(new String(Base64.decode(request.getParameter(
SSOConstants.HTTP_POST_PARAM_SAML2_RESP))));
Assertion assertion = null;
if (SSOUtils.isAssertionEncryptionEnabled(properties)) {
List<EncryptedAssertion> encryptedAssertions = samlResponse.getEncryptedAssertions();
EncryptedAssertion encryptedAssertion = null;
if (CollectionUtils.isNotEmpty(encryptedAssertions)) {
encryptedAssertion = encryptedAssertions.get(0);
try {
assertion = getDecryptedAssertion(encryptedAssertion);
} catch (Exception e) {
throw new SAMLSSOException("Unable to decrypt the SAML Assertion", e);
}
}
} else {
List<Assertion> assertions = samlResponse.getAssertions();
if (CollectionUtils.isNotEmpty(assertions)) {
assertion = assertions.get(0);
}
}
if (assertion == null) {
if (samlResponse.getStatus() != null &&
samlResponse.getStatus().getStatusCode() != null &&
samlResponse.getStatus().getStatusCode().getValue().equals(
SSOConstants.StatusCodes.IDENTITY_PROVIDER_ERROR) &&
samlResponse.getStatus().getStatusCode().getStatusCode() != null &&
samlResponse.getStatus().getStatusCode().getStatusCode().getValue().equals(
SSOConstants.StatusCodes.NO_PASSIVE)) {
return;
}
throw new SAMLSSOException("SAML Assertion not found in the Response");
}
// Get the subject name from the Response Object and forward it to login_action.jsp
String subject = null;
String nameQualifier = null;
String spNameQualifier = null;
if (assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
subject = assertion.getSubject().getNameID().getValue();
}
if (subject == null) {
throw new SAMLSSOException("SAML Response does not contain the name of the subject");
}
request.getSession().setAttribute("username", subject); // get the subject
nameQualifier = assertion.getSubject().getNameID().getNameQualifier();
spNameQualifier = assertion.getSubject().getNameID().getSPNameQualifier();
// validate audience restriction
validateAudienceRestriction(assertion);
// validate signature this SP only looking for assertion signature
validateSignature(samlResponse, assertion);
request.getSession(false).setAttribute("samlssoAttributes", getAssertionStatements(assertion));
//For removing the session when the single sign out request made by the SP itself
if (SSOUtils.isLogoutEnabled(properties)) {
String sessionId = assertion.getAuthnStatements().get(0).getSessionIndex();
if (sessionId == null) {
throw new SAMLSSOException("Single Logout is enabled but IdP Session ID not found in SAML Assertion");
}
request.getSession().setAttribute(SSOConstants.IDP_SESSION, sessionId);
request.getSession().setAttribute(SSOConstants.LOGOUT_USERNAME, nameQualifier);
request.getSession().setAttribute(SSOConstants.SP_NAME_QUALIFIER, spNameQualifier);
}
}
private LogoutRequest buildLogoutRequest(String user, String sessionIndexStr, String idpUrl, String nameQualifier, String spNameQualifier)
throws SAMLSSOException {
LogoutRequest logoutReq = new LogoutRequestBuilder().buildObject();
logoutReq.setID(SSOUtils.createID());
logoutReq.setDestination(idpUrl);
DateTime issueInstant = new DateTime();
logoutReq.setIssueInstant(issueInstant);
logoutReq.setNotOnOrAfter(new DateTime(issueInstant.getMillis() + 5 * 60 * 1000));
IssuerBuilder issuerBuilder = new IssuerBuilder();
Issuer issuer = issuerBuilder.buildObject();
String spEntityId = properties.get(IdentityApplicationConstants.Authenticator.SAML2SSO.SP_ENTITY_ID);
if (spEntityId != null && !spEntityId.isEmpty()) {
issuer.setValue(spEntityId);
} else {
issuer.setValue("carbonServer");
}
logoutReq.setIssuer(issuer);
NameID nameId = new NameIDBuilder().buildObject();
nameId.setFormat(NameIDType.UNSPECIFIED);
nameId.setValue(user);
nameId.setNameQualifier(nameQualifier);
nameId.setSPNameQualifier(spNameQualifier);
logoutReq.setNameID(nameId);
SessionIndex sessionIndex = new SessionIndexBuilder().buildObject();
if (sessionIndexStr != null) {
sessionIndex.setSessionIndex(sessionIndexStr);
} else {
sessionIndex.setSessionIndex(UUID.randomUUID().toString());
}
logoutReq.getSessionIndexes().add(sessionIndex);
logoutReq.setReason("Single Logout");
return logoutReq;
}
private AuthnRequest buildAuthnRequest(HttpServletRequest request,
boolean isPassive, String idpUrl, AuthenticationContext context) throws SAMLSSOException {
IssuerBuilder issuerBuilder = new IssuerBuilder();
Issuer issuer = issuerBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer", "samlp");
String spEntityId = properties.get(IdentityApplicationConstants.Authenticator.SAML2SSO.SP_ENTITY_ID);
if (spEntityId != null && !spEntityId.isEmpty()) {
issuer.setValue(spEntityId);
} else {
issuer.setValue("carbonServer");
}
DateTime issueInstant = new DateTime();
/* Creation of AuthRequestObject */
AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
AuthnRequest authRequest = authRequestBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:protocol",
"AuthnRequest", "samlp");
authRequest.setForceAuthn(isForceAuthenticate(context));
authRequest.setIsPassive(isPassive);
authRequest.setIssueInstant(issueInstant);
String includeProtocolBindingProp = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.INCLUDE_PROTOCOL_BINDING);
if (StringUtils.isEmpty(includeProtocolBindingProp) || Boolean.parseBoolean(includeProtocolBindingProp)) {
authRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
}
String acsUrl = null;
AuthenticatorConfig authenticatorConfig =
FileBasedConfigurationBuilder.getInstance().getAuthenticatorConfigMap()
.get(SSOConstants.AUTHENTICATOR_NAME);
if (authenticatorConfig != null){
String tmpAcsUrl = authenticatorConfig.getParameterMap().get(SSOConstants.ServerConfig.SAML_SSO_ACS_URL);
if(StringUtils.isNotBlank(tmpAcsUrl)){
acsUrl = tmpAcsUrl;
}
}
if(acsUrl == null) {
acsUrl = IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, true, true);
}
authRequest.setAssertionConsumerServiceURL(acsUrl);
authRequest.setIssuer(issuer);
authRequest.setID(SSOUtils.createID());
authRequest.setVersion(SAMLVersion.VERSION_20);
authRequest.setDestination(idpUrl);
String attributeConsumingServiceIndexProp = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.ATTRIBUTE_CONSUMING_SERVICE_INDEX);
if (StringUtils.isNotEmpty(attributeConsumingServiceIndexProp)) {
try {
authRequest.setAttributeConsumingServiceIndex(Integer
.valueOf(attributeConsumingServiceIndexProp));
} catch (NumberFormatException e) {
log.error(
"Error while populating SAMLRequest with AttributeConsumingServiceIndex: "
+ attributeConsumingServiceIndexProp, e);
}
}
String includeNameIDPolicyProp = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.INCLUDE_NAME_ID_POLICY);
if (StringUtils.isEmpty(includeNameIDPolicyProp) || Boolean.parseBoolean(includeNameIDPolicyProp)) {
NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
nameIdPolicy.setFormat(NameIDType.UNSPECIFIED);
//nameIdPolicy.setSPNameQualifier("Issuer");
nameIdPolicy.setAllowCreate(true);
authRequest.setNameIDPolicy(nameIdPolicy);
}
//Get the inbound SAMLRequest
AuthnRequest inboundAuthnRequest = getAuthnRequest(context);
RequestedAuthnContext requestedAuthnContext = buildRequestedAuthnContext(inboundAuthnRequest);
if (requestedAuthnContext != null) {
authRequest.setRequestedAuthnContext(requestedAuthnContext);
}
Extensions extensions = getSAMLExtensions(request);
if (extensions != null) {
authRequest.setExtensions(extensions);
}
return authRequest;
}
private RequestedAuthnContext buildRequestedAuthnContext(AuthnRequest inboundAuthnRequest) throws SAMLSSOException {
/* AuthnContext */
RequestedAuthnContextBuilder requestedAuthnContextBuilder = null;
RequestedAuthnContext requestedAuthnContext = null;
String includeAuthnContext = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.INCLUDE_AUTHN_CONTEXT);
if (StringUtils.isNotEmpty(includeAuthnContext) && "as_request".equalsIgnoreCase(includeAuthnContext)) {
if (inboundAuthnRequest != null) {
RequestedAuthnContext incomingRequestedAuthnContext = inboundAuthnRequest.getRequestedAuthnContext();
if (incomingRequestedAuthnContext != null) {
requestedAuthnContextBuilder = new RequestedAuthnContextBuilder();
requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
requestedAuthnContext.setDOM(incomingRequestedAuthnContext.getDOM());
}
}
} else if (StringUtils.isEmpty(includeAuthnContext) || "yes".equalsIgnoreCase(includeAuthnContext)) {
requestedAuthnContextBuilder = new RequestedAuthnContextBuilder();
requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
/* AuthnContextClass */
AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder
.buildObject(SAMLConstants.SAML20_NS,
AuthnContextClassRef.DEFAULT_ELEMENT_LOCAL_NAME,
SAMLConstants.SAML20_PREFIX);
String authnContextClassProp = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.AUTHENTICATION_CONTEXT_CLASS);
if (StringUtils.isNotEmpty(authnContextClassProp)) {
authnContextClassRef.setAuthnContextClassRef(IdentityApplicationManagementUtil
.getSAMLAuthnContextClasses().get(authnContextClassProp));
} else {
authnContextClassRef.setAuthnContextClassRef(AuthnContext.PPT_AUTHN_CTX);
}
/* Authentication Context Comparison Level */
String authnContextComparison = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.AUTHENTICATION_CONTEXT_COMPARISON_LEVEL);
if (StringUtils.isNotEmpty(authnContextComparison)) {
if (AuthnContextComparisonTypeEnumeration.EXACT.toString().equalsIgnoreCase(
authnContextComparison)) {
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
} else if (AuthnContextComparisonTypeEnumeration.MINIMUM.toString().equalsIgnoreCase(
authnContextComparison)) {
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM);
} else if (AuthnContextComparisonTypeEnumeration.MAXIMUM.toString().equalsIgnoreCase(
authnContextComparison)) {
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.MAXIMUM);
} else if (AuthnContextComparisonTypeEnumeration.BETTER.toString().equalsIgnoreCase(
authnContextComparison)) {
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.BETTER);
}
} else {
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
}
requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
}
return requestedAuthnContext;
}
private boolean isForceAuthenticate(AuthenticationContext context) {
boolean forceAuthenticate = false;
String forceAuthenticateProp = properties
.get(IdentityApplicationConstants.Authenticator.SAML2SSO.FORCE_AUTHENTICATION);
if ("yes".equalsIgnoreCase(forceAuthenticateProp)) {
forceAuthenticate = true;
} else if ("as_request".equalsIgnoreCase(forceAuthenticateProp)) {
forceAuthenticate = context.isForceAuthenticate();
}
return forceAuthenticate;
}
private String encodeRequestMessage(RequestAbstractType requestMessage)
throws SAMLSSOException {
Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(requestMessage);
Element authDOM = null;
try {
authDOM = marshaller.marshall(requestMessage);
/* Compress the message */
Deflater deflater = new Deflater(Deflater.DEFLATED, true);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);
StringWriter rspWrt = new StringWriter();
XMLHelper.writeNode(authDOM, rspWrt);
deflaterOutputStream.write(rspWrt.toString().getBytes());
deflaterOutputStream.close();
/* Encoding the compressed message */
String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream.toByteArray(), Base64.DONT_BREAK_LINES);
byteArrayOutputStream.write(byteArrayOutputStream.toByteArray());
byteArrayOutputStream.toString();
// log saml
if (log.isDebugEnabled()) {
log.debug("SAML Request : " + rspWrt.toString());
}
return URLEncoder.encode(encodedRequestMessage, "UTF-8").trim();
} catch (MarshallingException e) {
throw new SAMLSSOException("Error occurred while encoding SAML request", e);
} catch (UnsupportedEncodingException e) {
throw new SAMLSSOException("Error occurred while encoding SAML request", e);
} catch (IOException e) {
throw new SAMLSSOException("Error occurred while encoding SAML request", e);
}
}
private XMLObject unmarshall(String samlString) throws SAMLSSOException {
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setExpandEntityReferences(false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
org.apache.xerces.util.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(samlString.getBytes());
Document document = docBuilder.parse(is);
Element element = document.getDocumentElement();
UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
return unmarshaller.unmarshall(element);
} catch (ParserConfigurationException e) {
throw new SAMLSSOException("Error in unmarshalling SAML Request from the encoded String", e);
} catch (UnmarshallingException e) {
throw new SAMLSSOException("Error in unmarshalling SAML Request from the encoded String", e);
} catch (SAXException e) {
throw new SAMLSSOException("Error in unmarshalling SAML Request from the encoded String", e);
} catch (IOException e) {
throw new SAMLSSOException("Error in unmarshalling SAML Request from the encoded String", e);
}
}
/*
* Process the response and returns the results
*/
private Map<ClaimMapping, String> getAssertionStatements(Assertion assertion) {
Map<ClaimMapping, String> results = new HashMap<ClaimMapping, String>();
if (assertion != null) {
List<AttributeStatement> attributeStatementList = assertion.getAttributeStatements();
if (attributeStatementList != null) {
for (AttributeStatement statement : attributeStatementList) {
List<Attribute> attributesList = statement.getAttributes();
for (Attribute attribute : attributesList) {
Element value = attribute.getAttributeValues().get(0)
.getDOM();
String attributeValue = value.getTextContent();
results.put(ClaimMapping.build(attribute.getName(),
attribute.getName(), null, false), attributeValue);
}
}
}
}
return results;
}
/**
* Validate the AudienceRestriction of SAML2 Response
*
* @param assertion SAML2 Assertion
* @return validity
*/
private void validateAudienceRestriction(Assertion assertion) throws SAMLSSOException {
if (assertion != null) {
Conditions conditions = assertion.getConditions();
if (conditions != null) {
List<AudienceRestriction> audienceRestrictions = conditions.getAudienceRestrictions();
if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
for (AudienceRestriction audienceRestriction : audienceRestrictions) {
if (CollectionUtils.isNotEmpty(audienceRestriction.getAudiences())) {
boolean audienceFound = false;
for (Audience audience : audienceRestriction.getAudiences()) {
if (properties.get(IdentityApplicationConstants.Authenticator.SAML2SSO.SP_ENTITY_ID)
.equals(audience.getAudienceURI())) {
audienceFound = true;
break;
}
}
if (!audienceFound) {
throw new SAMLSSOException("SAML Assertion Audience Restriction validation failed");
}
} else {
throw new SAMLSSOException("SAML Response's AudienceRestriction doesn't contain Audiences");
}
}
} else {
throw new SAMLSSOException("SAML Response doesn't contain AudienceRestrictions");
}
} else {
throw new SAMLSSOException("SAML Response doesn't contain Conditions");
}
}
}
/**
* Validate the signature of a SAML2 Response and Assertion
*
* @param response SAML2 Response
* @return true, if signature is valid.
*/
private void validateSignature(Response response, Assertion assertion) throws
SAMLSSOException {
if (SSOUtils.isAuthnResponseSigned(properties)) {
if (identityProvider.getCertificate() == null
|| identityProvider.getCertificate().isEmpty()) {
throw new SAMLSSOException(
"SAMLResponse signing is enabled, but IdP doesn't have a certificate");
}
if (response.getSignature() == null) {
throw new SAMLSSOException("SAMLResponse signing is enabled, but signature element " +
"not found in SAML Response element.");
} else {
try {
X509Credential credential =
new X509CredentialImpl(tenantDomain, identityProvider.getCertificate());
SignatureValidator validator = new SignatureValidator(credential);
validator.validate(response.getSignature());
} catch (ValidationException e) {
throw new SAMLSSOException("Signature validation failed for SAML Response", e);
}
}
}
if (SSOUtils.isAssertionSigningEnabled(properties)) {
if (identityProvider.getCertificate() == null
|| identityProvider.getCertificate().isEmpty()) {
throw new SAMLSSOException(
"SAMLAssertion signing is enabled, but IdP doesn't have a certificate");
}
if (assertion.getSignature() == null) {
throw new SAMLSSOException("SAMLAssertion signing is enabled, but signature element " +
"not found in SAML Assertion element.");
} else {
try {
X509Credential credential =
new X509CredentialImpl(tenantDomain, identityProvider.getCertificate());
SignatureValidator validator = new SignatureValidator(credential);
validator.validate(assertion.getSignature());
} catch (ValidationException e) {
throw new SAMLSSOException("Signature validation failed for SAML Assertion", e);
}
}
}
}
/**
* Get Decrypted Assertion
*
* @param encryptedAssertion
* @return
* @throws Exception
*/
private Assertion getDecryptedAssertion(EncryptedAssertion encryptedAssertion) throws Exception {
X509Credential credential = new X509CredentialImpl(tenantDomain, null);
KeyInfoCredentialResolver keyResolver = new StaticKeyInfoCredentialResolver(credential);
EncryptedKey key = encryptedAssertion.getEncryptedData().getKeyInfo().getEncryptedKeys().get(0);
Decrypter decrypter = new Decrypter(null, keyResolver, null);
SecretKey dkey = (SecretKey) decrypter.decryptKey(key, encryptedAssertion.getEncryptedData().
getEncryptionMethod().getAlgorithm());
Credential shared = SecurityHelper.getSimpleCredential(dkey);
decrypter = new Decrypter(new StaticKeyInfoCredentialResolver(shared), null, null);
decrypter.setRootInNewDocument(true);
return decrypter.decrypt(encryptedAssertion);
}
}