/******************************************************************************* * 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.stork2; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import at.gv.egovernment.moa.id.auth.data.AuthenticationSession; import at.gv.egovernment.moa.id.auth.exception.AuthenticationException; import at.gv.egovernment.moa.id.auth.exception.MOAIDException; import at.gv.egovernment.moa.id.commons.db.dao.config.AttributeProviderPlugin; import at.gv.egovernment.moa.id.commons.db.dao.config.OAStorkAttribute; import at.gv.egovernment.moa.id.commons.db.dao.config.StorkAttribute; import at.gv.egovernment.moa.id.commons.db.ex.MOADatabaseException; import at.gv.egovernment.moa.id.config.auth.AuthConfigurationProvider; import at.gv.egovernment.moa.id.config.auth.OAAuthParameter; import at.gv.egovernment.moa.id.data.IAuthData; import at.gv.egovernment.moa.id.data.SLOInformationImpl; import at.gv.egovernment.moa.id.data.SLOInformationInterface; import at.gv.egovernment.moa.id.moduls.IAction; import at.gv.egovernment.moa.id.moduls.IRequest; import at.gv.egovernment.moa.id.protocols.stork2.attributeproviders.AttributeProvider; import at.gv.egovernment.moa.id.storage.AssertionStorage; import at.gv.egovernment.moa.logging.Logger; import eu.stork.peps.auth.commons.*; import eu.stork.peps.auth.engine.STORKSAMLEngine; import eu.stork.peps.complex.attributes.AttributeStatusType; import eu.stork.peps.exceptions.STORKSAMLEngineException; import org.opensaml.common.impl.SecureRandomIdentifierGenerator; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * the AttributeCollector Action tries to get all requested attributes from a set of {@link AttributeProvider} Plugins. * The class is called whenever the {@link AuthenticationRequest} Action is invoked and checks for missing attributes. * Furthermore, the class can handle direct posts. That is when the class triggers an attribute query which needs user * interaction, redirect to another portal, etc. The redirect will hit here and the class can continue to fetch attributes. * * TODO how do we treat mandatory and optional attributes? */ public class AttributeCollector implements IAction { /** * The Constant ARTIFACT_ID. */ private static final String ARTIFACT_ID = "artifactId"; /* (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.auth.data.AuthenticationSession) */ public SLOInformationInterface processRequest(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp, IAuthData authData) throws MOAIDException { // - fetch the container String artifactId = (String) httpReq.getParameter(ARTIFACT_ID); DataContainer container; try { container = AssertionStorage.getInstance().get(artifactId, DataContainer.class); } catch (MOADatabaseException e) { Logger.error("Error fetching incomplete Stork response from temporary storage. Most likely a timeout occured.", e); throw new MOAIDException("stork.11", null); } // TODO extract attribute response and check if it corresponds to the container if (httpReq.getParameter("SAMLResponse") != null) { Logger.info("Got SAML response from external attribute provider."); MOASTORKResponse STORK2Response = new MOASTORKResponse(); //extract STORK Response from HTTP Request byte[] decSamlToken; try { decSamlToken = PEPSUtil.decodeSAMLToken(httpReq.getParameter("SAMLResponse")); } catch (NullPointerException e) { if (httpReq.getRemoteHost().contains("129.27.142")) { Logger.warn("Availability check by " + httpReq.getRemoteHost() + " on URI: " + httpReq.getRequestURI()); } else { Logger.error("Unable to retrieve STORK Request for host: " + httpReq.getRemoteHost() + " and URI: " + httpReq.getRequestURI(), e); } throw new MOAIDException("stork.04", null); } //Get SAMLEngine instance STORKSAMLEngine engine = STORKSAMLEngine.getInstance("VIDP"); STORKAuthnResponse authnResponse = null; // check if valid authn request is contained try { authnResponse = engine.validateSTORKAuthnResponse(decSamlToken, httpReq.getRemoteAddr()); } catch (STORKSAMLEngineException ex) { Logger.error("Unable to validate Stork AuthenticationResponse: " + ex.getMessage()); } STORK2Response.setSTORKAuthnResponseToken(decSamlToken); if (authnResponse.getPersonalAttributeList().size() > 0) { Logger.info("Response from external attribute provider contains " + authnResponse.getPersonalAttributeList().size() + " attributes."); addOrUpdateAll(container.getResponse().getPersonalAttributeList(), authnResponse.getPersonalAttributeList()); } } // end addition // read configuration parameters of OA OAAuthParameter oaParam = AuthConfigurationProvider.getInstance().getOnlineApplicationParameter(container.getRequest().getAssertionConsumerServiceURL()); if (oaParam == null) throw new AuthenticationException("stork.12", new Object[]{container.getRequest().getAssertionConsumerServiceURL()}); // find the attribute provider plugin that can handle the response IPersonalAttributeList newAttributes = null; Iterator attibuteProvidersInterator = AttributeProviderFactory.getConfiguredPlugins(oaParam.getStorkAPs()); while(attibuteProvidersInterator.hasNext()) try { newAttributes = attibuteProvidersInterator.next().parse(httpReq); // stop as soon as we hit a capable plugin break; } catch (UnsupportedAttributeException e1) { // the current provider cannot find anything familiar within the // provided httpreq. Try the next one. } if (null == newAttributes) { // we do not have a provider which is capable of fetching something // from the received httpreq. Logger.error("No attribute could be retrieved from the response the attribute provider gave us."); } // - insert the embedded attribute(s) into the container if (null != newAttributes) addOrUpdateAll(container.getResponse().getPersonalAttributeList(), newAttributes); // see if we need some more attributes SLOInformationImpl sloInfo = (SLOInformationImpl) processRequest(container, httpReq, httpResp, authData, oaParam); if (sloInfo == null) { sloInfo = new SLOInformationImpl(null, null, null, req.requestedModule()); } return sloInfo; } /** * Checks if there are missing attributes and tries to fetch them. If there are no more attribute to fetch, * this very method creates and sends the protocol result to the asking S-PEPS. * * @param container the {@link DataContainer} representing the status of the overall query. * @return the string * @throws MOAIDException */ public SLOInformationInterface processRequest(DataContainer container, HttpServletRequest request, HttpServletResponse response, IAuthData authData, OAAuthParameter oaParam) throws MOAIDException { // check if there are attributes we need to fetch IPersonalAttributeList requestAttributeList = container.getRequest().getPersonalAttributeList(); IPersonalAttributeList responseAttributeList = container.getResponse().getPersonalAttributeList(); List missingAttributes = new ArrayList(); for (PersonalAttribute current : requestAttributeList) if (!responseAttributeList.containsKey(current.getName())) missingAttributes.add(current); Logger.info("collecting attributes..."); Logger.debug("found " + missingAttributes.size() + " missing attributes"); // Try to get all missing attributes try { // for each attribute still missing for (PersonalAttribute currentAttribute : missingAttributes) { /* * prefill attributes with "notAvailable". If we get them later, we override the value and status. * This way, there is no error case in which an attribute is left unanswered. */ IPersonalAttributeList aquiredAttributes = new PersonalAttributeList(); currentAttribute.setStatus(AttributeStatusType.NOT_AVAILABLE.value()); aquiredAttributes.add((PersonalAttribute) currentAttribute.clone()); addOrUpdateAll(container.getResponse().getPersonalAttributeList(), aquiredAttributes); // - check if we can find a suitable AttributeProvider Plugin Iterator attibuteProvidersInterator = AttributeProviderFactory.getConfiguredPlugins(oaParam.getStorkAPs()); while(attibuteProvidersInterator.hasNext()) { AttributeProvider currentProvider = attibuteProvidersInterator.next(); // build a section of attribute provider's predefined attributes and missing attributes // only missing attributes that can be handled by attribute provider will be sent to it List currentProviderConfiguredAttributes = new ArrayList(); for (String attributeName : currentProvider.getSupportedAttributeNames()) { for (PersonalAttribute missingAttribute : missingAttributes) { if (missingAttribute.getName().equals(attributeName)) { currentProviderConfiguredAttributes.add(missingAttribute); break; } } } try { // - hand over control to the suitable plugin Logger.info(currentProvider.getClass().getSimpleName() + " called to handle attribute '" + currentAttribute.getName() + "'"); //aquiredAttributes = currentProvider.acquire(currentAttribute, container.getRequest().getSpCountry(), moasession); //aquiredAttributes = currentProvider.acquire(missingAttributes, container.getRequest().getSpCountry(), moasession); aquiredAttributes = currentProvider.acquire(currentProviderConfiguredAttributes, container.getRequest(), authData); Logger.info(currentProvider.getClass().getSimpleName() + " can handle attribute '" + currentAttribute.getName() + "'"); break; } catch (UnsupportedAttributeException e) { // ok, try the next attributeprovider Logger.info(currentProvider.getClass().getSimpleName() + " could not handle attribute '" + currentAttribute.getName() + "'"); } catch (MOAIDException e) { // the current plugin had an error. Try the next one. Logger.info(currentProvider.getClass().getSimpleName() + " could not handle attribute '" + currentAttribute.getName() + "' due to an error"); } } // check if we could fetch the attribute if (null == aquiredAttributes) { // if not Logger.error("We have no suitable plugin for obtaining the attribute '" + currentAttribute.getName() + "'"); } else // else, update any existing attributes addOrUpdateAll(container.getResponse().getPersonalAttributeList(), aquiredAttributes); } Logger.info("collecting attributes done"); // ask for consent if necessary if(oaParam.isRequireConsentForStorkAttributes()) new ConsentEvaluator().requestConsent(container, response, oaParam); else new ConsentEvaluator().generateSTORKResponse(response, container); return null; // AssertionId // TODO } catch (ExternalAttributeRequestRequiredException e) { // the attribute request is ongoing and requires an external service. try { // memorize the container again Logger.debug("prepare putting the container into temporary storage..."); // - generate new key String newArtifactId = new SecureRandomIdentifierGenerator() .generateIdentifier(); // - put container in temporary store. AssertionStorage.getInstance().put(newArtifactId, container); Logger.debug("...successful"); Logger.info(e.getAp().getClass().getSimpleName() + " is going to ask an external service provider for the requested attributes"); // add container-key to redirect embedded within the return URL e.getAp().performRedirect(AuthConfigurationProvider.getInstance().getPublicURLPrefix() + "/stork2/ResumeAuthentication?" + ARTIFACT_ID + "=" + newArtifactId, request, response, oaParam); } catch (Exception e1) { // TODO should we return the response as is to the PEPS? Logger.error("Error putting incomplete Stork response into temporary storage", e1); e1.printStackTrace(); throw new MOAIDException("stork.11", null); } //TODO: in case of Single LogOut -> SLO information has to be stored return null; // TODO what to do here? } } /** * Adds or updates all {@link PersonalAttribute} objects given in {@code source} to/in {@code target}. * * @param target the target * @param source the source * @throws MOAIDException */ private void addOrUpdateAll(IPersonalAttributeList target, IPersonalAttributeList source) throws MOAIDException { Logger.debug("Updating " + source.size() + " attributes..."); for (PersonalAttribute current : source) { Logger.debug("treating " + current.getName()); // check if we need to update the current pa if (target.containsKey(current.getName())) { PersonalAttribute existing = target.get(current.getName()); if(!(existing.isEmptyValue() && existing.isEmptyComplexValue())) if(!(existing.getValue().equals(current.getValue()) || existing.getComplexValue().equals(current.getComplexValue()))) { Logger.error("Attribute Value does not match the value from first authentication!"); throw new MOAIDException("stork.16", new Object[] {existing.getName()}); } target.get(current.getName()).setStatus(current.getStatus()); target.get(current.getName()).setValue(current.getValue()); target.get(current.getName()).setComplexValue(current.getComplexValue()); } else target.add(current); Logger.debug("...successfully treated " + current.getName()); } } /* (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) */ public boolean needAuthentication(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp) { // this action does not need any authentication. The authentication is already done by the preceding AuthenticationRequest-Action. return false; } /* (non-Javadoc) * @see at.gv.egovernment.moa.id.moduls.IAction#getDefaultActionName() */ public String getDefaultActionName() { return STORKProtocol.ATTRIBUTE_COLLECTOR; } }