From bee5dd259a4438d45ecd1bcc26dfba12875236d6 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Tue, 26 Jun 2018 11:03:48 +0200 Subject: initial commit --- .../pvp2/sp/impl/PVPAuthnRequestBuilder.java | 205 +++++++++++++++ .../sp/impl/utils/AssertionAttributeExtractor.java | 275 +++++++++++++++++++++ 2 files changed, 480 insertions(+) create mode 100644 eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/PVPAuthnRequestBuilder.java create mode 100644 eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/utils/AssertionAttributeExtractor.java (limited to 'eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl') diff --git a/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/PVPAuthnRequestBuilder.java b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/PVPAuthnRequestBuilder.java new file mode 100644 index 00000000..9b02dc77 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/PVPAuthnRequestBuilder.java @@ -0,0 +1,205 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.sp.impl; + +import java.security.NoSuchAlgorithmException; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.opensaml.common.impl.SecureRandomIdentifierGenerator; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.Issuer; +import org.opensaml.saml2.core.NameID; +import org.opensaml.saml2.core.NameIDPolicy; +import org.opensaml.saml2.core.NameIDType; +import org.opensaml.saml2.core.RequestedAuthnContext; +import org.opensaml.saml2.core.Subject; +import org.opensaml.saml2.core.SubjectConfirmation; +import org.opensaml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.SingleSignOnService; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.xml.security.SecurityException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IEncoder; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; +import at.gv.egiz.eaaf.modules.pvp2.sp.api.IPVPAuthnRequestBuilderConfiguruation; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AuthnRequestBuildException; + +/** + * @author tlenz + * + */ +@Service("PVPAuthnRequestBuilder") +public class PVPAuthnRequestBuilder { + private static final Logger log = LoggerFactory.getLogger(PVPAuthnRequestBuilder.class); + + + @Autowired(required=true) ApplicationContext springContext; + + + /** + * Build a PVP2.x specific authentication request + * + * @param pendingReq Currently processed pendingRequest + * @param config AuthnRequest builder configuration, never null + * @param idpEntity SAML2 EntityDescriptor of the IDP, which receive this AuthnRequest, never null + * @param httpResp + * @throws NoSuchAlgorithmException + * @throws SecurityException + * @throws PVP2Exception + * @throws MessageEncodingException + */ + public void buildAuthnRequest(IRequest pendingReq, IPVPAuthnRequestBuilderConfiguruation config, + HttpServletResponse httpResp) throws NoSuchAlgorithmException, MessageEncodingException, PVP2Exception, SecurityException { + //get IDP Entity element from config + EntityDescriptor idpEntity = config.getIDPEntityDescriptor(); + + AuthnRequest authReq = SAML2Utils + .createSAMLObject(AuthnRequest.class); + + //select SingleSignOn Service endpoint from IDP metadata + SingleSignOnService endpoint = null; + for (SingleSignOnService sss : + idpEntity.getIDPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleSignOnServices()) { + + // use POST binding as default if it exists + if (sss.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI)) { + endpoint = sss; + + } else if ( sss.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI) + && endpoint == null ) + endpoint = sss; + + } + + if (endpoint == null) { + log.warn("Building AuthnRequest FAILED: > Requested IDP " + idpEntity.getEntityID() + + " does not support POST or Redirect Binding."); + throw new AuthnRequestBuildException("sp.pvp2.00", new Object[]{config.getSPNameForLogging(), idpEntity.getEntityID()}); + + } else + authReq.setDestination(endpoint.getLocation()); + + + //set basic AuthnRequest information + String reqID = config.getRequestID(); + if (StringUtils.isNotEmpty(reqID)) + authReq.setID(reqID); + + else { + SecureRandomIdentifierGenerator gen = new SecureRandomIdentifierGenerator(); + authReq.setID(gen.generateIdentifier()); + + } + + authReq.setIssueInstant(new DateTime()); + + //set isPassive flag + if (config.isPassivRequest() == null) + authReq.setIsPassive(false); + else + authReq.setIsPassive(config.isPassivRequest()); + + //set EntityID of the service provider + Issuer issuer = SAML2Utils.createSAMLObject(Issuer.class); + issuer.setFormat(NameIDType.ENTITY); + issuer.setValue(config.getSPEntityID()); + authReq.setIssuer(issuer); + + //set AssertionConsumerService ID + if (config.getAssertionConsumerServiceId() != null) + authReq.setAssertionConsumerServiceIndex(config.getAssertionConsumerServiceId()); + + //set NameIDPolicy + if (config.getNameIDPolicyFormat() != null) { + NameIDPolicy policy = SAML2Utils.createSAMLObject(NameIDPolicy.class); + policy.setAllowCreate(config.getNameIDPolicyAllowCreation()); + policy.setFormat(config.getNameIDPolicyFormat()); + authReq.setNameIDPolicy(policy); + } + + //set requested QAA level + if (config.getAuthnContextClassRef() != null) { + RequestedAuthnContext reqAuthContext = SAML2Utils.createSAMLObject(RequestedAuthnContext.class); + AuthnContextClassRef authnClassRef = SAML2Utils.createSAMLObject(AuthnContextClassRef.class); + + authnClassRef.setAuthnContextClassRef(config.getAuthnContextClassRef()); + + if (config.getAuthnContextComparison() == null) + reqAuthContext.setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM); + else + reqAuthContext.setComparison(config.getAuthnContextComparison()); + + reqAuthContext.getAuthnContextClassRefs().add(authnClassRef); + authReq.setRequestedAuthnContext(reqAuthContext); + } + + //set request Subject element + if (StringUtils.isNotEmpty(config.getSubjectNameID())) { + Subject reqSubject = SAML2Utils.createSAMLObject(Subject.class); + NameID subjectNameID = SAML2Utils.createSAMLObject(NameID.class); + + subjectNameID.setValue(config.getSubjectNameID()); + if (StringUtils.isNotEmpty(config.getSubjectNameIDQualifier())) + subjectNameID.setNameQualifier(config.getSubjectNameIDQualifier()); + + if (StringUtils.isNotEmpty(config.getSubjectNameIDFormat())) + subjectNameID.setFormat(config.getSubjectNameIDFormat()); + else + subjectNameID.setFormat(NameID.TRANSIENT); + + reqSubject.setNameID(subjectNameID); + + if (config.getSubjectConformationDate() != null) { + SubjectConfirmation subjectConformation = SAML2Utils.createSAMLObject(SubjectConfirmation.class); + SubjectConfirmationData subjectConformDate = SAML2Utils.createSAMLObject(SubjectConfirmationData.class); + subjectConformation.setSubjectConfirmationData(subjectConformDate); + reqSubject.getSubjectConfirmations().add(subjectConformation ); + + if (config.getSubjectConformationMethode() != null) + subjectConformation.setMethod(config.getSubjectConformationMethode()); + + subjectConformDate.setDOM(config.getSubjectConformationDate()); + + } + + authReq.setSubject(reqSubject ); + + } + + //TODO: implement requested attributes + //maybe: config.getRequestedAttributes(); + + //select message encoder + IEncoder binding = null; + if (endpoint.getBinding().equals( + SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { + binding = springContext.getBean("PVPRedirectBinding", RedirectBinding.class); + + } else if (endpoint.getBinding().equals( + SAMLConstants.SAML2_POST_BINDING_URI)) { + binding = springContext.getBean("PVPPOSTBinding", PostBinding.class); + + } + + //encode message + binding.encodeRequest(null, httpResp, authReq, + endpoint.getLocation(), pendingReq.getPendingRequestId(), config.getAuthnRequestSigningCredential(), pendingReq); + } + +} diff --git a/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/utils/AssertionAttributeExtractor.java b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/utils/AssertionAttributeExtractor.java new file mode 100644 index 00000000..1674e3fd --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_sp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/sp/impl/utils/AssertionAttributeExtractor.java @@ -0,0 +1,275 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.sp.impl.utils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.core.AttributeStatement; +import org.opensaml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml2.core.AuthnStatement; +import org.opensaml.saml2.core.Response; +import org.opensaml.saml2.core.StatusResponseType; +import org.opensaml.saml2.core.Subject; +import org.opensaml.xml.XMLObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.modules.pvp2.PVPConstants; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AssertionAttributeExtractorExeption; + +public class AssertionAttributeExtractor { + + private static final Logger log = LoggerFactory.getLogger(AssertionAttributeExtractor.class); + + private Assertion assertion = null; + private Map> attributs = new HashMap>(); + //private PersonalAttributeList storkAttributes = new PersonalAttributeList(); + + private final List minimalMDSAttributeNamesList = Arrays.asList( + PVPConstants.PRINCIPAL_NAME_NAME, + PVPConstants.GIVEN_NAME_NAME, + PVPConstants.BIRTHDATE_NAME, + PVPConstants.BPK_NAME); + + private final List minimalIDLAttributeNamesList = Arrays.asList( + PVPConstants.EID_IDENTITY_LINK_NAME, + PVPConstants.EID_SOURCE_PIN_NAME, + PVPConstants.EID_SOURCE_PIN_TYPE_NAME); + + /** + * Parse the SAML2 Response element and extracts included information + *

+ * INFO: Actually, only the first SAML2 Assertion of the SAML2 Response is used! + * + * @param samlResponse SAML2 Response + * @throws AssertionAttributeExtractorExeption + */ + public AssertionAttributeExtractor(StatusResponseType samlResponse) throws AssertionAttributeExtractorExeption { + if (samlResponse != null && samlResponse instanceof Response) { + List assertions = ((Response) samlResponse).getAssertions(); + if (assertions.size() == 0) + throw new AssertionAttributeExtractorExeption("Assertion"); + + else if (assertions.size() > 1) + log.warn("Found more then ONE PVP2.1 assertions. Only the First is used."); + + assertion = assertions.get(0); + + if (assertion.getAttributeStatements() != null && + assertion.getAttributeStatements().size() > 0) { + AttributeStatement attrStat = assertion.getAttributeStatements().get(0); + for (Attribute attr : attrStat.getAttributes()) { + if (attr.getName().startsWith(PVPConstants.STORK_ATTRIBUTE_PREFIX)) { + List storkAttrValues = new ArrayList(); + for (XMLObject el : attr.getAttributeValues()) + storkAttrValues.add(el.getDOM().getTextContent()); + +// PersonalAttribute storkAttr = new PersonalAttribute(attr.getName(), +// false, storkAttrValues , "Available"); +// storkAttributes.put(attr.getName(), storkAttr ); + + } else { + List attrList = new ArrayList(); + for (XMLObject el : attr.getAttributeValues()) + attrList.add(el.getDOM().getTextContent()); + + attributs.put(attr.getName(), attrList); + + } + } + + } + + } else + throw new AssertionAttributeExtractorExeption(); + } + + /** + * Get all SAML2 attributes from first SAML2 AttributeStatement element + * + * @return List of SAML2 Attributes + */ + public List getAllResponseAttributesFromFirstAttributeStatement() { + return assertion.getAttributeStatements().get(0).getAttributes(); + + } + + /** + * Get all SAML2 attributes of specific SAML2 AttributeStatement element + * + * @param attrStatementID List ID of the AttributeStatement element + * @return List of SAML2 Attributes + */ + public List getAllResponseAttributes(int attrStatementID) { + return assertion.getAttributeStatements().get(attrStatementID).getAttributes(); + + } + + /** + * check attributes from assertion with minimal required attribute list + * @return + */ + public boolean containsAllRequiredAttributes() { + return containsAllRequiredAttributes(minimalMDSAttributeNamesList) + || containsAllRequiredAttributes(minimalIDLAttributeNamesList); + + } + + /** + * check attributes from assertion with attributeNameList + * bPK or enc_bPK are always needed + * + * @param List of attributes which are required + * + * @return + */ + public boolean containsAllRequiredAttributes(Collection attributeNameList) { + + //first check if a bPK or an encrypted bPK is available + boolean flag = true; + for (String attr : attributeNameList) { + if (!attributs.containsKey(attr)) { + flag = false; + log.debug("Assertion contains no Attribute " + attr); + + } + + } + + if (flag) + return flag; + + else { + log.debug("Assertion contains no all minimum attributes from: " + attributeNameList.toString()); + return false; + + } + } + + public boolean containsAttribute(String attributeName) { + return attributs.containsKey(attributeName); + + } + + public String getSingleAttributeValue(String attributeName) { + if (attributs.containsKey(attributeName) && attributs.get(attributeName).size() > 0) + return attributs.get(attributeName).get(0); + else + return null; + + } + + public List getAttributeValues(String attributeName) { + return attributs.get(attributeName); + + } + + /** + * Return all include PVP attribute names + * + * @return + */ + public Set getAllIncludeAttributeNames() { + return attributs.keySet(); + + } + +// public PersonalAttributeList getSTORKAttributes() { +// return storkAttributes; +// } + + + public String getNameID() throws AssertionAttributeExtractorExeption { + if (assertion.getSubject() != null) { + Subject subject = assertion.getSubject(); + + if (subject.getNameID() != null) { + if (StringUtils.isNotEmpty(subject.getNameID().getValue())) + return subject.getNameID().getValue(); + + else + log.error("SAML2 NameID Element is empty."); + } + } + + throw new AssertionAttributeExtractorExeption("nameID"); + } + + public String getSessionIndex() throws AssertionAttributeExtractorExeption { + AuthnStatement authn = getAuthnStatement(); + + if (StringUtils.isNotEmpty(authn.getSessionIndex())) + return authn.getSessionIndex(); + + else + throw new AssertionAttributeExtractorExeption("SessionIndex"); + } + + /** + * @return + * @throws AssertionAttributeExtractorExeption + */ + public String getQAALevel() throws AssertionAttributeExtractorExeption { + AuthnStatement authn = getAuthnStatement(); + if (authn.getAuthnContext() != null && authn.getAuthnContext().getAuthnContextClassRef() != null) { + AuthnContextClassRef qaaClass = authn.getAuthnContext().getAuthnContextClassRef(); + + if (StringUtils.isNotEmpty(qaaClass.getAuthnContextClassRef())) + return qaaClass.getAuthnContextClassRef(); + + else + throw new AssertionAttributeExtractorExeption("AuthnContextClassRef (QAALevel)"); + } + + throw new AssertionAttributeExtractorExeption("AuthnContextClassRef"); + } + + public Assertion getFullAssertion() { + return assertion; + } + + + /** + * Get the Assertion validTo period + * + * Primarily, the 'SessionNotOnOrAfter' attribute in the SAML2 'AuthnStatment' element is used. + * If this is empty, this method returns value of SAML 'Conditions' element. + * + * @return Date, until this SAML2 assertion is valid + */ + public Date getAssertionNotOnOrAfter() { + if (getFullAssertion().getAuthnStatements() != null + && getFullAssertion().getAuthnStatements().size() > 0) { + for (AuthnStatement el : getFullAssertion().getAuthnStatements()) { + if (el.getSessionNotOnOrAfter() != null) + return (el.getSessionNotOnOrAfter().toDate()); + } + + } + + return getFullAssertion().getConditions().getNotOnOrAfter().toDate(); + + } + + private AuthnStatement getAuthnStatement() throws AssertionAttributeExtractorExeption { + List authnList = assertion.getAuthnStatements(); + if (authnList.size() == 0) + throw new AssertionAttributeExtractorExeption("AuthnStatement"); + + else if (authnList.size() > 1) + log.warn("Found more then ONE AuthnStatements in PVP2.1 assertions. Only the First is used."); + + return authnList.get(0); + } + +} -- cgit v1.2.3