diff options
Diffstat (limited to 'id/server/modules/module-stork/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/AttributeCollector.java')
-rw-r--r-- | id/server/modules/module-stork/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/AttributeCollector.java | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/id/server/modules/module-stork/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/AttributeCollector.java b/id/server/modules/module-stork/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/AttributeCollector.java new file mode 100644 index 000000000..25cb952d7 --- /dev/null +++ b/id/server/modules/module-stork/src/main/java/at/gv/egovernment/moa/id/protocols/stork2/AttributeCollector.java @@ -0,0 +1,367 @@ +/******************************************************************************* + * 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.exception.AuthenticationException; +import at.gv.egovernment.moa.id.auth.exception.MOAIDException; +import at.gv.egovernment.moa.id.commons.db.ex.MOADatabaseException; +import at.gv.egovernment.moa.id.config.auth.AuthConfigurationProviderFactory; +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.eu.stork.names.tc.stork._1_0.assertion.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); + } + + + 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 response is contained + try { + authnResponse = engine.validateSTORKAuthnResponse(decSamlToken, httpReq.getRemoteAddr()); + } catch (STORKSAMLEngineException ex) { + Logger.error("Unable to validate Stork AuthenticationResponse: " + ex.getMessage()); + } + + STORK2Response.setSTORKAuthnResponseToken(decSamlToken); + + // check if the attributes are provided for the same person from request + // requires presence of eIdentifier for unambigious correlation + Logger.debug("Checking if the attribute relates to the correct person.."); + try { + String remoteEIdentifier= authnResponse.getPersonalAttributeList().get("eIdentifier").getValue().get(0); + String localEidentifier= container.getResponse().getStorkAuthnResponse().getPersonalAttributeList().get("eIdentifier").getValue().get(0); + if (!remoteEIdentifier.equals(localEidentifier)) { + Logger.error("The attribute is not provided for the same person!"); + throw new MOAIDException("stork.25", null); + } + } catch (NullPointerException ex) { + Logger.warn("Could not check the correlation of attributes from external provider. Ignoring the check."); + //Logger.debug(ex); + //throw new MOAIDException("stork.04", null); // TODO revise message, raise exception when ehvd checked + } + + if (authnResponse.getPersonalAttributeList().size() > 0) { + Logger.info("Response from external attribute provider contains " + authnResponse.getPersonalAttributeList().size() + " attributes."); + container.getResponse().setPersonalAttributeList(addOrUpdateAll(container.getResponse().getPersonalAttributeList(), authnResponse.getPersonalAttributeList())); + } + + } + + // end addition + + + // read configuration parameters of OA + OAAuthParameter oaParam = AuthConfigurationProviderFactory.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<AttributeProvider> 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) + container.getResponse().setPersonalAttributeList(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<PersonalAttribute> missingAttributes = new ArrayList<PersonalAttribute>(); + Logger.debug("aquire list of missing attributes"); + for (PersonalAttribute current : requestAttributeList) + if (!responseAttributeList.containsKey(current.getName())) { + if(null == current.getStatus() || (null != current.getStatus() && !current.getStatus().equals(AttributeStatusType.WITHHELD.value()))) { + // add the ones we need + missingAttributes.add(current); + Logger.debug("add " + current.getName() + " to the list of missing attributes"); + } + } else { + // remove the ones we do not want to share from the response list + if(null != current.getStatus() && current.getStatus().equals(AttributeStatusType.WITHHELD.value())) { + responseAttributeList.remove(current.getName()); + Logger.debug("remove " + current.getName() + " from the list of resulting attributes because the user does not want to disclose the data"); + } + } + + 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()); + container.getResponse().setPersonalAttributeList( + addOrUpdateAll(container.getResponse().getPersonalAttributeList(), aquiredAttributes)); + // - check if we can find a suitable AttributeProvider Plugin + + Iterator<AttributeProvider> 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<PersonalAttribute> currentProviderConfiguredAttributes = new ArrayList<PersonalAttribute>(); + 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 + container.getResponse().setPersonalAttributeList(addOrUpdateAll(container.getResponse().getPersonalAttributeList(), aquiredAttributes)); + } + Logger.info("collecting attributes done"); + + // ask for consent if necessary + 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(AuthConfigurationProviderFactory.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 + * @return + * @throws MOAIDException + */ + private PersonalAttributeList addOrUpdateAll(IPersonalAttributeList target, IPersonalAttributeList source) throws MOAIDException { + + PersonalAttributeList updatedList = new PersonalAttributeList(); + for (PersonalAttribute el : target) + updatedList.add(el); + + 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 (updatedList.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()}); + } + + updatedList.get(current.getName()).setStatus(current.getStatus()); + updatedList.get(current.getName()).setValue(current.getValue()); + updatedList.get(current.getName()).setComplexValue(current.getComplexValue()); + } else + updatedList.add(current); + + Logger.debug("...successfully treated " + current.getName()); + } + + return updatedList; + } + + /* (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; + } +} |