/* * Copyright 2014 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egovernment.moa.id.protocols.pvp2x; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.joda.time.DateTime; import org.opensaml.saml2.core.Assertion; import org.opensaml.saml2.core.Attribute; import org.opensaml.saml2.core.AttributeQuery; import org.opensaml.saml2.core.Response; import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.ws.soap.common.SOAPException; import org.opensaml.xml.XMLObject; import org.opensaml.xml.security.SecurityException; 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.core.api.idp.IAction; import at.gv.egiz.eaaf.core.api.idp.IAuthData; import at.gv.egiz.eaaf.core.api.idp.IAuthenticationDataBuilder; import at.gv.egiz.eaaf.core.api.idp.slo.SLOInformationInterface; import at.gv.egiz.eaaf.core.exceptions.EAAFAuthenticationException; import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EAAFException; import at.gv.egiz.eaaf.core.impl.data.Trible; import at.gv.egiz.eaaf.modules.pvp2.api.IPVP2BasicConfiguration; import at.gv.egiz.eaaf.modules.pvp2.exception.AttributQueryException; import at.gv.egiz.eaaf.modules.pvp2.idp.impl.PVPSProfilePendingRequest; import at.gv.egiz.eaaf.modules.pvp2.idp.impl.builder.AuthResponseBuilder; import at.gv.egiz.eaaf.modules.pvp2.idp.impl.builder.PVP2AssertionBuilder; import at.gv.egiz.eaaf.modules.pvp2.impl.binding.SoapBinding; import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PVPAttributeBuilder; import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileRequest; import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AssertionAttributeExtractorExeption; import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AssertionValidationExeption; import at.gv.egiz.eaaf.modules.pvp2.sp.impl.utils.AssertionAttributeExtractor; import at.gv.egovernment.moa.id.auth.builder.DynamicOAAuthParameterBuilder; import at.gv.egovernment.moa.id.auth.data.AuthenticationSession; import at.gv.egovernment.moa.id.auth.exception.BuildException; import at.gv.egovernment.moa.id.commons.api.AuthConfiguration; import at.gv.egovernment.moa.id.commons.api.IOAAuthParameters; import at.gv.egovernment.moa.id.commons.api.exceptions.ConfigurationException; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.id.commons.db.dao.session.InterfederationSessionStore; import at.gv.egovernment.moa.id.commons.db.ex.MOADatabaseException; import at.gv.egovernment.moa.id.config.auth.OAAuthParameterDecorator; import at.gv.egovernment.moa.id.data.IMOAAuthData; import at.gv.egovernment.moa.id.protocols.pvp2x.builder.AttributQueryBuilder; import at.gv.egovernment.moa.id.protocols.pvp2x.metadata.MOAMetadataProvider; import at.gv.egovernment.moa.id.protocols.pvp2x.signer.IDPCredentialProvider; import at.gv.egovernment.moa.id.protocols.pvp2x.utils.MOASAMLSOAPClient; import at.gv.egovernment.moa.id.protocols.pvp2x.verification.SAMLVerificationEngineSP; import at.gv.egovernment.moa.id.storage.IAuthenticationSessionStoreage; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; /** * @author tlenz * */ @Service("AttributQueryAction") public class AttributQueryAction implements IAction { @Autowired private IAuthenticationSessionStoreage authenticationSessionStorage; @Autowired private IAuthenticationDataBuilder authDataBuilder; @Autowired private IDPCredentialProvider pvpCredentials; @Autowired private AuthConfiguration authConfig; @Autowired(required=true) private MOAMetadataProvider metadataProvider; @Autowired(required=true) ApplicationContext springContext; @Autowired private AttributQueryBuilder attributQueryBuilder; @Autowired private SAMLVerificationEngineSP samlVerificationEngine; @Autowired(required=true) IPVP2BasicConfiguration pvpBasicConfiguration; @Autowired(required=true) PVP2AssertionBuilder assertionBuilder; private final static List DEFAULTSTORKATTRIBUTES = Arrays.asList( new String[]{PVPConstants.EID_STORK_TOKEN_NAME}); private final static List DEFAULTMANDATEATTRIBUTES = Arrays.asList( new String[]{ PVPConstants.MANDATE_FULL_MANDATE_NAME, PVPConstants.MANDATE_PROF_REP_OID_NAME}); /* (non-Javadoc) * @see at.gv.egovernment.moa.id.moduls.IAction#processRequest(at.gv.egovernment.moa.id.moduls.IRequest, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, at.gv.egovernment.moa.id.data.IAuthData) */ @Override public SLOInformationInterface processRequest(IRequest pendingReq, HttpServletRequest httpReq, HttpServletResponse httpResp, IAuthData authData) throws EAAFException { if (pendingReq instanceof PVPSProfilePendingRequest && ((PVPSProfilePendingRequest) pendingReq).getRequest() instanceof PVPSProfileRequest && ((PVPSProfileRequest)((PVPSProfilePendingRequest) pendingReq).getRequest()).getSamlRequest() instanceof AttributeQuery) { //set time reference DateTime date = new DateTime(); try { //get Single Sign-On information for the Service-Provider // which sends the Attribute-Query request AuthenticationSession moaSession = authenticationSessionStorage.getInternalSSOSession(pendingReq.getInternalSSOSessionIdentifier()); if (moaSession == null) { Logger.warn("No MOASession with ID:" + pendingReq.getInternalSSOSessionIdentifier() + " FOUND."); throw new MOAIDException("auth.02", new Object[]{pendingReq.getInternalSSOSessionIdentifier()}); } InterfederationSessionStore nextIDPInformation = authenticationSessionStorage.searchInterfederatedIDPFORAttributeQueryWithSessionID(moaSession.getSSOSessionID()); AttributeQuery attrQuery = (AttributeQuery)((PVPSProfileRequest)((PVPSProfilePendingRequest) pendingReq).getRequest()).getSamlRequest(); //build PVP 2.1 response-attribute information for this AttributQueryRequest Trible, Date, String> responseInfo = buildResponseInformationForAttributQuery(pendingReq, moaSession, attrQuery.getAttributes(), nextIDPInformation); Logger.debug("AttributQuery return " + responseInfo.getFirst().size() + " attributes with QAA-Level:" + responseInfo.getThird() + " validTo:" + responseInfo.getSecond().toString()); //build PVP 2.1 assertion String issuerEntityID = pvpBasicConfiguration.getIDPEntityId(pendingReq.getAuthURL()); Assertion assertion = assertionBuilder.buildAssertion(issuerEntityID, attrQuery, responseInfo.getFirst(), date, new DateTime(responseInfo.getSecond().getTime()), responseInfo.getThird(), authData.getSessionIndex()); //build PVP 2.1 response Response authResponse = AuthResponseBuilder.buildResponse( metadataProvider, issuerEntityID, attrQuery, date, assertion, authConfig.isPVP2AssertionEncryptionActive()); SoapBinding decoder = springContext.getBean("PVPSOAPBinding", SoapBinding.class); decoder.encodeRespone(httpReq, httpResp, authResponse, null, null, pvpCredentials.getIDPAssertionSigningCredential(), pendingReq); return null; } catch (MessageEncodingException e) { Logger.error("Message Encoding exception", e); throw new MOAIDException("pvp2.01", null, e); } catch (SecurityException e) { Logger.error("Security exception", e); throw new MOAIDException("pvp2.01", null, e); } catch (MOADatabaseException e) { Logger.error("MOASession with SessionID=" + pendingReq.getInternalSSOSessionIdentifier() + " is not found in Database", e); throw new MOAIDException("init.04", new Object[] { pendingReq.getInternalSSOSessionIdentifier() }); } } else { Logger.error("Process AttributeQueryAction but request is NOT of type AttributQuery."); throw new MOAIDException("pvp2.13", null); } } /* (non-Javadoc) * @see at.gv.egovernment.moa.id.moduls.IAction#needAuthentication(at.gv.egovernment.moa.id.moduls.IRequest, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public boolean needAuthentication(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp) { return false; } /* (non-Javadoc) * @see at.gv.egovernment.moa.id.moduls.IAction#getDefaultActionName() */ @Override public String getDefaultActionName() { return at.gv.egiz.eaaf.modules.pvp2.PVPConstants.ATTRIBUTEQUERY; } private Trible, Date, String> buildResponseInformationForAttributQuery(IRequest pendingReq, AuthenticationSession session, List reqAttributes, InterfederationSessionStore nextIDPInformation) throws MOAIDException, AssertionAttributeExtractorExeption, AttributQueryException, AssertionValidationExeption { try { //mark AttributeQuery as used if it exists if ( pendingReq instanceof PVPSProfileRequest && ((PVPSProfilePendingRequest) pendingReq).getRequest() instanceof PVPSProfileRequest && ((PVPSProfilePendingRequest) pendingReq).getRequest().getInboundMessage() instanceof AttributeQuery) { authenticationSessionStorage.markOAWithAttributeQueryUsedFlag(session, pendingReq.getSPEntityId(), pendingReq.requestedModule()); } //build OnlineApplication dynamic from requested attributes (AttributeQuerry Request) and configuration IOAAuthParameters spConfig = DynamicOAAuthParameterBuilder.buildFromAttributeQuery(reqAttributes); //search federated IDP information for this MOASession if (nextIDPInformation != null) { Logger.info("Find active federated IDP information." + ". --> Request next IDP:" + nextIDPInformation.getIdpurlprefix() + " for authentication information."); //load configuration of next IDP IOAAuthParameters idpLoaded = authConfig.getServiceProviderConfiguration( nextIDPInformation.getIdpurlprefix(), OAAuthParameterDecorator.class); if (idpLoaded == null || !(idpLoaded instanceof IOAAuthParameters)) { Logger.warn("Configuration for federated IDP:" + nextIDPInformation.getIdpurlprefix() + "is not loadable."); throw new MOAIDException("auth.32", new Object[]{nextIDPInformation.getIdpurlprefix()}); } IOAAuthParameters idp = idpLoaded; //check if next IDP config allows inbound messages if (!idp.isInboundSSOInterfederationAllowed()) { Logger.warn("Configuration for federated IDP:" + nextIDPInformation.getIdpurlprefix() + "disallow inbound authentication messages."); throw new MOAIDException("auth.33", new Object[]{nextIDPInformation.getIdpurlprefix()}); } //check next IDP service area policy. BusinessService IDPs can only request wbPKs if (!spConfig.hasBaseIdTransferRestriction() && idp.hasBaseIdTransferRestriction()) { Logger.error("Interfederated IDP " + idp.getPublicURLPrefix() + " is a BusinessService-IDP but requests PublicService attributes."); throw new MOAIDException("auth.34", new Object[]{nextIDPInformation.getIdpurlprefix()}); } //validation complete --> start AttributeQuery Request /*TODO: * 'pendingReq.getAuthURL() + "/sp/federated/metadata"' is implemented in federated_authentication module * but used in moa-id-lib. This should be refactored!!! */ AssertionAttributeExtractor extractor = getAuthDataFromAttributeQuery(reqAttributes, nextIDPInformation.getUserNameID(), idp, pendingReq.getAuthURL() + "/sp/federated/metadata"); //mark attribute request as used if (nextIDPInformation.isStoreSSOInformation()) { nextIDPInformation.setAttributesRequested(true); authenticationSessionStorage.persistIdpInformation(nextIDPInformation); //moaSessionDBUtils.saveOrUpdate(nextIDPInformation); //delete federated IDP from Session } else { authenticationSessionStorage.deleteIdpInformation(nextIDPInformation); //moaSessionDBUtils.delete(nextIDPInformation); } return Trible.newInstance( extractor.getAllResponseAttributesFromFirstAttributeStatement(), extractor.getAssertionNotOnOrAfter(), extractor.getQAALevel()); } else { Logger.debug("Build authData for AttributQuery from local MOASession."); IAuthData authData = authDataBuilder.buildAuthenticationData(pendingReq); //add default attributes in case of mandates or STORK is in use List attrList = addDefaultAttributes(reqAttributes, authData); //build Set of response attributes List respAttr = PVPAttributeBuilder.buildSetOfResponseAttributes(authData, attrList); return Trible.newInstance(respAttr, authData.getSsoSessionValidTo(), authData.getEIDASQAALevel()); } } catch (MOAIDException e) { throw e; } catch (EAAFAuthenticationException e) { throw new MOAIDException(e.getErrorId(), e.getParams(), e); } catch (EAAFConfigurationException e) { throw new MOAIDException(e.getErrorId(), e.getParams(), e); } } /** * Add additional PVP Attribute-Names in respect to current MOASession. *

*
As example: if current MOASession includes mandates but mandate attributes are not requested, 
	 * this method a a minimum set of mandate attribute-names
* * @param reqAttr From Service Provider requested attributes * @param authData AuthenticationData * @return List of PVP attribute-names */ private List addDefaultAttributes(List reqAttr, IAuthData authData) { List reqAttributeNames = new ArrayList(); for (Attribute attr : reqAttr) { reqAttributeNames.add(attr.getName()); } //add default STORK attributes if it is a STORK authentication if (authData.isForeigner() && !reqAttributeNames.containsAll(DEFAULTSTORKATTRIBUTES)) { for (String el : DEFAULTSTORKATTRIBUTES) { if (!reqAttributeNames.contains(el)) reqAttributeNames.add(el); } } //add default mandate attributes if it is a authentication with mandates if (authData instanceof IMOAAuthData) if (((IMOAAuthData)authData).isUseMandate() && !reqAttributeNames.containsAll(DEFAULTMANDATEATTRIBUTES)) { for (String el : DEFAULTMANDATEATTRIBUTES) { if (!reqAttributeNames.contains(el)) reqAttributeNames.add(el); } } return reqAttributeNames; } /** * Get PVP authentication attributes by using a SAML2 AttributeQuery * * @param reqQueryAttr List of PVP attributes which are requested * @param userNameID SAML2 UserNameID of the user for which attributes are requested * @param idpConfig Configuration of the IDP, which is requested * @return * @return PVP attribute DAO, which contains all received information * @throws MOAIDException * @throws AttributQueryException * @throws AssertionValidationExeption */ public AssertionAttributeExtractor getAuthDataFromAttributeQuery(List reqQueryAttr, String userNameID, IOAAuthParameters idpConfig, String spEntityID) throws MOAIDException, AttributQueryException, AssertionValidationExeption{ String idpEnityID = idpConfig.getPublicURLPrefix(); try { Logger.debug("Starting AttributeQuery process ..."); //collect attributes by using BackChannel communication String endpoint = idpConfig.getIDPAttributQueryServiceURL(); if (MiscUtil.isEmpty(endpoint)) { Logger.error("No AttributeQueryURL for interfederationIDP " + idpEnityID); throw new ConfigurationException("config.26", new Object[]{idpEnityID}); } //build attributQuery request AttributeQuery query = attributQueryBuilder.buildAttributQueryRequest(spEntityID, userNameID, endpoint, reqQueryAttr); //build SOAP request List xmlObjects = MOASAMLSOAPClient.send(endpoint, query); if (xmlObjects.size() == 0) { Logger.error("Receive emptry AttributeQuery response-body."); throw new AttributQueryException("auth.27", new Object[]{idpEnityID, "Receive emptry AttributeQuery response-body."}); } Response intfResp; if (xmlObjects.get(0) instanceof Response) { intfResp = (Response) xmlObjects.get(0); //validate PVP 2.1 response try { samlVerificationEngine.verifyIDPResponse(intfResp, TrustEngineFactory.getSignatureKnownKeysTrustEngine( metadataProvider)); //create assertion attribute extractor from AttributeQuery response return new AssertionAttributeExtractor(intfResp); } catch (Exception e) { Logger.warn("PVP 2.1 assertion validation FAILED.", e); throw new AssertionValidationExeption("auth.27", new Object[]{idpEnityID, e.getMessage()}, e); } } else { Logger.error("Receive AttributeQuery response-body include no PVP 2.1 response"); throw new AttributQueryException("auth.27", new Object[]{idpEnityID, "Receive AttributeQuery response-body include no PVP 2.1 response"}); } } catch (SOAPException e) { throw new BuildException("builder.06", null, e); } catch (SecurityException e) { throw new BuildException("builder.06", null, e); } } }