/*
* Atricore IDBus
*
* Copyright (c) 2009, Atricore Inc.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.atricore.idbus.capabilities.sso.main.sp.producers;
import oasis.names.tc.saml._2_0.assertion.NameIDType;
import oasis.names.tc.saml._2_0.metadata.EndpointType;
import oasis.names.tc.saml._2_0.metadata.EntityDescriptorType;
import oasis.names.tc.saml._2_0.metadata.IDPSSODescriptorType;
import oasis.names.tc.saml._2_0.metadata.RoleDescriptorType;
import oasis.names.tc.saml._2_0.protocol.ManageNameIDRequestType;
import oasis.names.tc.saml._2_0.protocol.StatusCodeType;
import oasis.names.tc.saml._2_0.protocol.StatusResponseType;
import oasis.names.tc.saml._2_0.protocol.StatusType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.atricore.idbus.capabilities.sso.main.SSOException;
import org.atricore.idbus.capabilities.sso.main.common.producers.SSOProducer;
import org.atricore.idbus.capabilities.sso.main.sp.SSOSPMediator;
import org.atricore.idbus.capabilities.sso.support.SAMLR2Constants;
import org.atricore.idbus.capabilities.sso.support.binding.SSOBinding;
import org.atricore.idbus.capabilities.sso.support.core.NameIDFormat;
import org.atricore.idbus.capabilities.sso.support.core.SSORequestException;
import org.atricore.idbus.capabilities.sso.support.core.StatusCode;
import org.atricore.idbus.capabilities.sso.support.core.StatusDetails;
import org.atricore.idbus.capabilities.sso.support.core.encryption.SamlR2Encrypter;
import org.atricore.idbus.capabilities.sso.support.core.encryption.SamlR2EncrypterException;
import org.atricore.idbus.capabilities.sso.support.core.signature.SamlR2SignatureException;
import org.atricore.idbus.capabilities.sso.support.core.signature.SamlR2SignatureValidationException;
import org.atricore.idbus.capabilities.sso.support.core.signature.SamlR2Signer;
import org.atricore.idbus.capabilities.sso.support.core.util.DateUtils;
import org.atricore.idbus.kernel.main.federation.AccountLink;
import org.atricore.idbus.kernel.main.federation.AccountLinkLifecycle;
import org.atricore.idbus.kernel.main.federation.SubjectNameID;
import org.atricore.idbus.kernel.main.federation.metadata.*;
import org.atricore.idbus.kernel.main.mediation.IdentityMediationException;
import org.atricore.idbus.kernel.main.mediation.MediationMessageImpl;
import org.atricore.idbus.kernel.main.mediation.camel.AbstractCamelEndpoint;
import org.atricore.idbus.kernel.main.mediation.camel.component.binding.CamelMediationExchange;
import org.atricore.idbus.kernel.main.mediation.camel.component.binding.CamelMediationMessage;
import org.atricore.idbus.kernel.main.mediation.channel.FederationChannel;
import org.atricore.idbus.kernel.main.mediation.channel.IdPChannel;
import org.atricore.idbus.kernel.main.util.UUIDGenerator;
import javax.security.auth.Subject;
import java.util.Date;
public class SPNameIDManagementProducer extends SSOProducer {
private static final Log logger = LogFactory.getLog(SPNameIDManagementProducer.class);
private static final String SAML2_VERSION = "2.0";
public SPNameIDManagementProducer(AbstractCamelEndpoint<CamelMediationExchange> endpoint) {
super(endpoint);
}
@Override
protected void doProcess(CamelMediationExchange exchange) throws Exception {
// try{
CamelMediationMessage in = (CamelMediationMessage) exchange.getIn();
ManageNameIDRequestType manageNameID = (ManageNameIDRequestType) in.getMessage().getContent();
StatusType status = new StatusType();
StatusCodeType statusCode = new StatusCodeType();
statusCode.setValue(StatusCode.TOP_SUCCESS.getValue());
status.setStatusCode(statusCode);
boolean validated = true;
StringBuffer secondaryErrorCode = new StringBuffer();
try {
manageNameID = validateManageNameID(manageNameID, secondaryErrorCode);
} catch (SSORequestException e1) {
logger.error("Error validating ManageNameIDRequest", e1);
validated = false;
}
if (validated) {
if (manageNameID.getTerminate() != null) {
SubjectNameID subjectNameID = null;
if (manageNameID.getNameID() != null) {
subjectNameID = new SubjectNameID(manageNameID.getNameID().getValue(), manageNameID.getNameID().getFormat());
subjectNameID.setLocalName(manageNameID.getNameID().getSPProvidedID());
} else {
NameIDType decryptedNameID = null;
SamlR2Encrypter encrypter = ((SSOSPMediator) channel.getIdentityMediator()).getEncrypter();
try {
decryptedNameID = encrypter.decryptNameID(manageNameID.getEncryptedID());
} catch (SamlR2EncrypterException e) {
//TODO should we throw RuntimeException?
throw new SSOException("NameID cannot be decrypted.", e);
}
subjectNameID = new SubjectNameID(decryptedNameID.getValue(), decryptedNameID.getFormat());
subjectNameID.setLocalName(decryptedNameID.getSPProvidedID());
}
Subject idpSubject = new Subject();
idpSubject.getPrincipals().add(subjectNameID);
// check if there is an existing session for the user
FederationChannel fChannel = (FederationChannel) channel;
// if not, check if channel is federation-capable
if (fChannel.getAccountLinkLifecycle() == null) {
// cannot map subject to local account, terminate
logger.error("No Account Lifecycle configured for Channel [" + fChannel.getName() + "] " +
" ManageNameID [" + manageNameID.getID() + "]");
throw new SSOException("No Account Lifecycle configured for Channel [" + fChannel.getName() + "] " +
" ManageNameID [" + manageNameID.getID() + "]");
}
AccountLinkLifecycle accountLinkLifecycle = fChannel.getAccountLinkLifecycle();
AccountLink accountLink = accountLinkLifecycle.findByIDPAccount(idpSubject);
if (accountLink == null) {
logger.error("No Account Link available for Principal [" + subjectNameID.getName() + "]");
throw new SSOException("No Account Link available for Principal [" + subjectNameID.getName() + "]");
}
accountLinkLifecycle.dispose(accountLink);
}
}
// ---------------------------------------------------
// Send ManageNameIDResponse
// ---------------------------------------------------
CircleOfTrustMemberDescriptor idp = this.resolveIdp();
logger.debug("Using IDP " + idp.getAlias());
// Select endpoint, must be a ManageNameIDService endpoint
EndpointType idpSsoEndpoint = resolveIdpMNIDEndpoint(idp);
EndpointDescriptor destination = new EndpointDescriptorImpl(
"IDPMNIEndpoint",
"ManageNameIDService",
idpSsoEndpoint.getBinding(),
idpSsoEndpoint.getLocation(),
idpSsoEndpoint.getResponseLocation());
StatusResponseType mnidResponse = buildMNIDResponse(exchange, idp, idpSsoEndpoint, validated, secondaryErrorCode.toString());
CamelMediationMessage out = (CamelMediationMessage) exchange.getOut();
out.setMessage(new MediationMessageImpl(mnidResponse.getID(),
mnidResponse, "ManageNameIDResponse", null, destination, in.getMessage().getState()));
exchange.setOut(out);
}
//Initial version of buildMNIDResponse method, not using plans
private StatusResponseType buildMNIDResponse(CamelMediationExchange exchange,
CircleOfTrustMemberDescriptor idp, EndpointType idpSsoEndpoint, boolean validated, String secondaryErrorCode) {
CamelMediationMessage in = (CamelMediationMessage) exchange.getIn();
ManageNameIDRequestType manageNameID = (ManageNameIDRequestType) in.getMessage().getContent();
StatusResponseType response = new StatusResponseType();
UUIDGenerator uuidGenerator = new UUIDGenerator();
response.setID(uuidGenerator.generateId());
response.setInResponseTo(manageNameID.getID());
StatusType status = new StatusType();
StatusCodeType statusCode = new StatusCodeType();
if (validated) {
statusCode.setValue("urn:oasis:names:tc:SAML:2.0:status:Success");
} else {
if (secondaryErrorCode.equals(StatusDetails.INVALID_VERSION)) {
statusCode.setValue("urn:oasis:names:tc:SAML:2.0:status:VersionMismatch");
} else {
statusCode.setValue("urn:oasis:names:tc:SAML:2.0:status:Requester");
}
status.setStatusMessage(secondaryErrorCode);
}
status.setStatusCode(statusCode);
response.setStatus(status);
if (idpSsoEndpoint.getResponseLocation() != null) {
response.setDestination(idpSsoEndpoint.getResponseLocation());
} else {
response.setDestination(idpSsoEndpoint.getLocation());
}
response.setVersion(SAMLR2Constants.SAML_VERSION);
response.setIssueInstant(DateUtils.toXMLGregorianCalendar(new Date()));
if (idp != null) {
NameIDType issuer = new NameIDType();
issuer.setFormat(NameIDFormat.ENTITY.getValue());
issuer.setValue(idp.getAlias());
response.setIssuer(issuer);
}
return response;
}
private EndpointType resolveIdpMNIDEndpoint(CircleOfTrustMemberDescriptor idp) throws SSOException {
SSOSPMediator mediator = (SSOSPMediator) ((IdPChannel) channel).getIdentityMediator();
SSOBinding preferredBinding = mediator.getPreferredIdpSSOBindingValue();
MetadataEntry idpMd = idp.getMetadata();
if (idpMd == null || idpMd.getEntry() == null)
throw new SSOException("No metadata descriptor found for IDP " + idp);
if (idpMd.getEntry() instanceof EntityDescriptorType) {
EntityDescriptorType md = (EntityDescriptorType) idpMd.getEntry();
for (RoleDescriptorType role : md.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor()) {
if (role instanceof IDPSSODescriptorType) {
IDPSSODescriptorType idpSsoRole = (IDPSSODescriptorType) role;
EndpointType defaultEndpoint = null;
EndpointType postEndpoint = null;
EndpointType artEndpoint = null;
for (EndpointType idpMnidEndpoint : idpSsoRole.getManageNameIDService()) {
SSOBinding b = SSOBinding.asEnum(idpMnidEndpoint.getBinding());
if (b.equals(preferredBinding))
return idpMnidEndpoint;
if (b.equals(SSOBinding.SAMLR2_ARTIFACT))
artEndpoint = idpMnidEndpoint;
if (b.equals(SSOBinding.SAMLR2_POST))
postEndpoint = idpMnidEndpoint;
if (defaultEndpoint == null)
defaultEndpoint = idpMnidEndpoint;
}
if (artEndpoint != null)
defaultEndpoint = artEndpoint;
else if (postEndpoint != null)
defaultEndpoint = postEndpoint;
return defaultEndpoint;
}
}
} else {
throw new SSOException("Unknown metadata descriptor type " + idpMd.getEntry().getClass().getName());
}
logger.debug("No IDP Endpoint supporting binding : " + preferredBinding);
throw new SSOException("IDP does not support preferred binding " + preferredBinding);
}
protected CircleOfTrustMemberDescriptor resolveIdp() throws SSOException {
SSOSPMediator mediator = (SSOSPMediator) ((IdPChannel) channel).getIdentityMediator();
String idpAlias = mediator.getPreferredIdpAlias();
if (idpAlias == null) {
throw new SSOException("No IDP available");
}
CircleOfTrustMemberDescriptor idp = this.getCotManager().lookupMemberByAlias(idpAlias);
if (idp == null) {
throw new SSOException("No IDP Member descriptor available for " + idpAlias);
}
return idp;
}
protected ManageNameIDRequestType validateManageNameID(ManageNameIDRequestType manageNameID, StringBuffer secondaryErrorCode) throws SSORequestException {
EndpointDescriptor epointDesc;
try {
epointDesc = channel.getIdentityMediator().resolveEndpoint(channel, endpoint);
} catch (IdentityMediationException e1) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
null,
StatusDetails.INVALID_DESTINATION,
"Cannot resolve endpoint descriptor",
e1);
}
String idpAlias = null;
IDPSSODescriptorType idpMd = null;
try {
idpAlias = manageNameID.getIssuer().getValue();
MetadataEntry md = getCotManager().findEntityMetadata(idpAlias);
EntityDescriptorType saml2Md = (EntityDescriptorType) md.getEntry();
boolean found = false;
for (RoleDescriptorType roleMd : saml2Md.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor()) {
if (roleMd instanceof IDPSSODescriptorType) {
idpMd = (IDPSSODescriptorType) roleMd;
}
}
} catch (CircleOfTrustManagerException e) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.NO_SUPPORTED_IDP,
null,
manageNameID.getIssuer().getValue(),
e);
}
//ID
if (manageNameID.getID() == null) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.INVALID_ATTR_NAME_OR_VALUE,
StatusDetails.INVALID_DESTINATION,
"No 'ID' attribute in ManageNameIDRequest.");
}
//destination
if (manageNameID.getDestination() == null && (epointDesc.getBinding().equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST") ||
epointDesc.getBinding().equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"))) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.INVALID_ATTR_NAME_OR_VALUE,
StatusDetails.INVALID_DESTINATION,
"No Destination attribute in ManageNameIDRequest.");
}
//issue instant
if (manageNameID.getIssueInstant() == null) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.INVALID_ATTR_NAME_OR_VALUE,
StatusDetails.NO_ISSUE_INSTANT);
}
//nameID or encryptedNameID required
if (manageNameID.getNameID() == null && manageNameID.getEncryptedID() == null) {
secondaryErrorCode.append(StatusDetails.NO_NAMEID_ENCRYPTEDID.toString());
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.INVALID_ATTR_NAME_OR_VALUE,
StatusDetails.NO_NAMEID_ENCRYPTEDID);
}
//Issuer
if (manageNameID.getIssuer() == null) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.INVALID_ATTR_NAME_OR_VALUE,
StatusDetails.NO_ISSUER,
"No Issuer element.");
} else {
//Format attrib. must be omitted or have value of: urn:oasis:names:tc:SAML:2.0:nameid-format:entity
if (manageNameID.getIssuer().getFormat() != null && !manageNameID.getIssuer().getFormat().equals("urn:oasis:names:tc:SAML:2.0:nameid-format:entity")) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.INVALID_ATTR_NAME_OR_VALUE,
StatusDetails.INVALID_ISSUER_FORMAT);
}
}
// Version, saml2 core, section 3.2.2
if (manageNameID.getVersion() == null || !manageNameID.getVersion().equals(SAML2_VERSION)) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.REQUEST_VERSION_TOO_LOW,
StatusDetails.INVALID_VERSION);
}
//signature must exist for http post and http redirect bindings
SamlR2Signer signer = ((SSOSPMediator) channel.getIdentityMediator()).getSigner();
if (manageNameID.getSignature() != null) {
try {
signer.validate(idpMd, manageNameID);
} catch (SamlR2SignatureValidationException e) {
secondaryErrorCode.append(StatusDetails.INVALID_REQUEST_SIGNATURE.toString());
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.REQUEST_DENIED,
StatusDetails.INVALID_REQUEST_SIGNATURE);
} catch (SamlR2SignatureException e) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.REQUEST_DENIED,
StatusDetails.INVALID_REQUEST_SIGNATURE, e);
}
} else if (epointDesc.getBinding().equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST") ||
epointDesc.getBinding().equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.REQUEST_DENIED,
StatusDetails.INVALID_REQUEST_SIGNATURE,
"No Signature for ManageNameIDRequest for HTTP-POST or HTTP-Redirect binding.");
}
//if one of the following exists: NewID, NewEncryptedID,Terminate
if (manageNameID.getNewID() == null && manageNameID.getNewEncryptedID() == null && manageNameID.getTerminate() == null) {
throw new SSORequestException(manageNameID,
StatusCode.TOP_RESPONDER,
StatusCode.INVALID_ATTR_NAME_OR_VALUE,
StatusDetails.NO_NEWID_NEWENCRYPTEDID_TERMINATE);
}
return manageNameID;
}
}