package org.jboss.seam.security.external.saml.sp;
import java.util.LinkedList;
import java.util.List;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBElement;
import javax.xml.datatype.DatatypeConstants;
import org.jboss.solder.logging.Logger;
import org.jboss.seam.security.external.InvalidRequestException;
import org.jboss.seam.security.external.ResponseHandler;
import org.jboss.seam.security.external.SamlNameIdImpl;
import org.jboss.seam.security.external.SamlPrincipalImpl;
import org.jboss.seam.security.external.dialogues.DialogueBean;
import org.jboss.seam.security.external.jaxb.samlv2.assertion.AssertionType;
import org.jboss.seam.security.external.jaxb.samlv2.assertion.AttributeStatementType;
import org.jboss.seam.security.external.jaxb.samlv2.assertion.AttributeType;
import org.jboss.seam.security.external.jaxb.samlv2.assertion.AuthnStatementType;
import org.jboss.seam.security.external.jaxb.samlv2.assertion.NameIDType;
import org.jboss.seam.security.external.jaxb.samlv2.assertion.StatementAbstractType;
import org.jboss.seam.security.external.jaxb.samlv2.assertion.SubjectConfirmationDataType;
import org.jboss.seam.security.external.jaxb.samlv2.assertion.SubjectConfirmationType;
import org.jboss.seam.security.external.jaxb.samlv2.protocol.AuthnRequestType;
import org.jboss.seam.security.external.jaxb.samlv2.protocol.ResponseType;
import org.jboss.seam.security.external.jaxb.samlv2.protocol.StatusResponseType;
import org.jboss.seam.security.external.jaxb.samlv2.protocol.StatusType;
import org.jboss.seam.security.external.saml.SamlConstants;
import org.jboss.seam.security.external.saml.SamlDialogue;
import org.jboss.seam.security.external.saml.SamlEntityBean;
import org.jboss.seam.security.external.saml.SamlMessageFactory;
import org.jboss.seam.security.external.saml.SamlMessageSender;
import org.jboss.seam.security.external.saml.SamlProfile;
import org.jboss.seam.security.external.saml.SamlRedirectMessage;
import org.jboss.seam.security.external.saml.SamlServiceType;
import org.jboss.seam.security.external.saml.SamlUtils;
import org.jboss.seam.security.external.spi.SamlServiceProviderSpi;
/**
* @author Marcel Kolsteren
*/
public class SamlSpSingleSignOnService {
@Inject
private Logger log;
@Inject
private SamlSpSessions samlSpSessions;
@Inject
private Instance<SamlServiceProviderSpi> samlServiceProviderSpi;
@Inject
private Instance<SamlEntityBean> samlEntityBean;
@Inject
private DialogueBean dialogue;
@Inject
private SamlMessageSender samlMessageSender;
@Inject
private SamlDialogue samlDialogue;
@Inject
private SamlMessageFactory samlMessageFactory;
@Inject
private ResponseHandler responseHandler;
public void processIDPResponse(HttpServletRequest httpRequest, HttpServletResponse httpResponse, StatusResponseType statusResponse) throws InvalidRequestException {
SamlExternalIdentityProvider idp = (SamlExternalIdentityProvider) samlDialogue.getExternalProvider();
StatusType status = statusResponse.getStatus();
if (status == null) {
throw new InvalidRequestException("Response does not contain a status");
}
String statusValue = status.getStatusCode().getValue();
if (!SamlConstants.STATUS_SUCCESS.equals(statusValue)) {
String statusCodeLevel1 = statusValue;
String statusCodeLevel2 = null;
if (status.getStatusCode().getStatusCode() != null) {
statusCodeLevel2 = status.getStatusCode().getStatusCode().getValue();
}
samlServiceProviderSpi.get().loginFailed(statusCodeLevel1, statusCodeLevel2, responseHandler.createResponseHolder(httpResponse));
}
if (!(statusResponse instanceof ResponseType)) {
throw new InvalidRequestException("Response does not have type ResponseType");
}
ResponseType response = (ResponseType) statusResponse;
List<Object> assertions = response.getAssertionOrEncryptedAssertion();
if (assertions.size() == 0) {
throw new RuntimeException("IDP response does not contain assertions");
}
SamlSpSessionImpl session = createSession(response, idp);
if (session == null) {
throw new InvalidRequestException("Not possible to login based on the supplied assertions");
} else {
session.setIdentityProvider(idp);
loginUser(httpRequest, httpResponse, session, statusResponse.getInResponseTo() == null, httpRequest.getParameter(SamlRedirectMessage.QSP_RELAY_STATE));
}
dialogue.setFinished(true);
}
private SamlSpSessionImpl createSession(ResponseType responseType, SamlExternalIdentityProvider idp) {
SamlSpSessionImpl session = null;
for (Object assertion : responseType.getAssertionOrEncryptedAssertion()) {
if (assertion instanceof AssertionType) {
SamlSpSessionImpl sessionExtractedFromAssertion = handleAssertion((AssertionType) assertion, idp);
if (session == null) {
session = sessionExtractedFromAssertion;
} else {
log.warn("Multiple authenticated users found in assertions. Using the first one.");
}
} else {
/* assertion instanceof EncryptedElementType */
log.warn("Encountered encrypted assertion. Skipping it because decryption is not yet supported.");
}
}
return session;
}
private SamlSpSessionImpl handleAssertion(AssertionType assertion, SamlExternalIdentityProvider idp) {
if (SamlUtils.hasAssertionExpired(assertion)) {
log.warn("Received assertion not processed because it has expired.");
return null;
}
AuthnStatementType authnStatement = extractValidAuthnStatement(assertion);
if (authnStatement == null) {
log.warn("Received assertion not processed because it doesn't contain a valid authnStatement.");
return null;
}
NameIDType nameId = validateSubjectAndExtractNameID(assertion);
if (nameId == null) {
log.warn("Received assertion not processed because it doesn't contain a valid subject.");
return null;
}
SamlPrincipalImpl principal = new SamlPrincipalImpl();
principal.setAssertion(assertion);
principal.setNameId(new SamlNameIdImpl(nameId.getValue(), nameId.getFormat(), nameId.getNameQualifier()));
SamlSpSessionImpl session = new SamlSpSessionImpl();
session.setSessionIndex(authnStatement.getSessionIndex());
session.setPrincipal(principal);
session.setIdentityProvider(idp);
for (StatementAbstractType statement : assertion.getStatementOrAuthnStatementOrAuthzDecisionStatement()) {
if (statement instanceof AttributeStatementType) {
AttributeStatementType attributeStatement = (AttributeStatementType) statement;
List<AttributeType> attributes = new LinkedList<AttributeType>();
for (Object object : attributeStatement.getAttributeOrEncryptedAttribute()) {
if (object instanceof AttributeType) {
attributes.add((AttributeType) object);
} else {
log.warn("Encrypted attributes are not supported. Ignoring the attribute.");
}
}
principal.setAttributes(attributes);
}
}
return session;
}
private AuthnStatementType extractValidAuthnStatement(AssertionType assertion) {
for (StatementAbstractType statement : assertion.getStatementOrAuthnStatementOrAuthzDecisionStatement()) {
if (statement instanceof AuthnStatementType) {
AuthnStatementType authnStatement = (AuthnStatementType) statement;
return authnStatement;
}
}
return null;
}
private NameIDType validateSubjectAndExtractNameID(AssertionType assertion) {
NameIDType nameId = null;
boolean validConfirmationFound = false;
for (JAXBElement<?> contentElement : assertion.getSubject().getContent()) {
if (contentElement.getValue() instanceof NameIDType) {
nameId = (NameIDType) contentElement.getValue();
}
if (contentElement.getValue() instanceof SubjectConfirmationType) {
SubjectConfirmationType confirmation = (SubjectConfirmationType) contentElement.getValue();
if (confirmation.getMethod().equals(SamlConstants.CONFIRMATION_METHOD_BEARER)) {
SubjectConfirmationDataType confirmationData = confirmation.getSubjectConfirmationData();
boolean validRecipient = confirmationData.getRecipient().equals(samlEntityBean.get().getServiceURL(SamlServiceType.SAML_ASSERTION_CONSUMER_SERVICE));
boolean notTooLate = confirmationData.getNotOnOrAfter().compare(SamlUtils.getXMLGregorianCalendarNow()) == DatatypeConstants.GREATER;
boolean validInResponseTo = confirmationData.getInResponseTo() == null || confirmationData.getInResponseTo().equals(dialogue.getId());
if (validRecipient && notTooLate && validInResponseTo) {
validConfirmationFound = true;
} else {
log.debugf("Validation of assertion failed: validRecipient: %b; notTootLate: %b; validInResponseTo: %b", new Object[]{validRecipient, notTooLate, validInResponseTo});
}
}
}
}
if (validConfirmationFound) {
return nameId;
} else {
return null;
}
}
private void loginUser(HttpServletRequest httpRequest, HttpServletResponse response, SamlSpSessionImpl session, boolean unsolicited, String relayState) {
samlSpSessions.addSession(session);
if (unsolicited) {
samlServiceProviderSpi.get().loggedIn(session, relayState, responseHandler.createResponseHolder(response));
} else {
samlServiceProviderSpi.get().loginSucceeded(session, responseHandler.createResponseHolder(response));
}
}
public void sendAuthenticationRequestToIDP(SamlExternalIdentityProvider idp, HttpServletResponse response) {
AuthnRequestType authnRequest = samlMessageFactory.createAuthnRequest();
samlDialogue.setExternalProvider(idp);
samlMessageSender.sendRequest(idp, SamlProfile.SINGLE_SIGN_ON, authnRequest, response);
}
}