summaryrefslogtreecommitdiff
path: root/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp
diff options
context:
space:
mode:
Diffstat (limited to 'eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp')
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/AuthenticationData.java416
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/EAAFCoreSpringResourceProvider.java30
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java341
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java119
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java467
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java302
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/AuthProcessDataWrapper.java219
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/IdentityLink.java312
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/SimpleIdentityLinkAssertionParser.java326
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java220
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java151
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/BPKAttributeBuilder.java55
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/BirthdateAttributeBuilder.java40
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDIdentityLinkBuilder.java54
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDIssuingNationAttributeBuilder.java35
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSectorForIDAttributeBuilder.java36
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSourcePIN.java39
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSourcePINType.java33
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDeIDASQAALevelAttributeBuilder.java31
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/GivenNameAttributeBuilder.java26
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/PVPVersionAttributeBuilder.java26
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/PrincipalNameAttributeBuilder.java26
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/AbstractConfigurationImpl.java185
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/SPConfigurationImpl.java163
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractAuthProtocolModulController.java225
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java354
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractProcessEngineSignalController.java97
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/ProtocolFinalizationController.java178
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java386
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/tasks/FinalizeAuthenticationTask.java57
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/tasks/RestartAuthProzessManagement.java92
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ExecutionContextImpl.java81
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ExpressionEvaluationContextImpl.java46
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessDefinitionParser.java226
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessDefinitionParserException.java37
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessEngineImpl.java424
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessInstance.java166
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessInstanceState.java32
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/dao/ProcessInstanceStore.java75
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/dao/ProcessInstanceStoreDAOImpl.java73
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/EndEvent.java44
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/ProcessDefinition.java160
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/ProcessNode.java71
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/StartEvent.java47
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/TaskInfo.java96
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/Transition.java138
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/spring/SpringExpressionEvaluator.java63
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/AbstractAuthSourceServlet.java118
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/AbstractTask.java101
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/SpringWebExpressionEvaluator.java145
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/support/SecureRandomHolder.java37
51 files changed, 7221 insertions, 0 deletions
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/AuthenticationData.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/AuthenticationData.java
new file mode 100644
index 00000000..f35a54fa
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/AuthenticationData.java
@@ -0,0 +1,416 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp;
+
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.commons.collections4.map.HashedMap;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink;
+import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException;
+
+/**
+ * @author tlenz
+ *
+ */
+public class AuthenticationData implements IAuthData, Serializable {
+
+ private static final Logger log = LoggerFactory.getLogger(AuthenticationData.class);
+
+ private static final long serialVersionUID = -1042697056735596866L;
+ public static final String IDENTITY_LINK_DATE_FORMAT = "yyyy-MM-dd";
+
+ private boolean isBaseIDTransferRestrication = true;
+ private Map<String, Object> genericDataStorate = new HashedMap<String, Object>();
+
+ private String issuer;
+ private Date issueInstant;
+
+ private String identificationValue;
+ private String identificationType;
+ private IIdentityLink identityLink = null;
+
+ private String familyName;
+ private String givenName;
+ private Date dateOfBirth;
+ private String bPK;
+ private String bPKType;
+
+ private String ccc = null;
+
+
+ private boolean foreigner =false;
+ private String eIDASLoA = null;
+
+ private boolean ssoSession = false;
+ private Date ssoSessionValidTo = null;
+
+ private String sessionIndex = null;
+ private String nameID = null;
+ private String nameIDFormat = null;
+
+ public AuthenticationData() {
+ this.issueInstant = new Date();
+
+ }
+
+ @Override
+ public String getAuthenticationIssuer() {
+ return this.issuer;
+ }
+
+ /**
+ * Set an unique identifier for the IDP that authenticates the user
+ *
+ * @param authIssuer
+ */
+ public void setAuthenticationIssuer(String authIssuer) {
+ this.issuer = authIssuer;
+
+ }
+
+
+ @Override
+ public Date getAuthenticationIssueInstant() {
+ return this.issueInstant;
+ }
+
+
+ public String getAuthenticationIssueInstantString() {
+ SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ f.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return f.format(this.issueInstant);
+
+ }
+
+ /**
+ * Set the timestamp for this user authentication process
+ *
+ * @param date
+ */
+ public void setAuthenticationIssueInstant(Date date) {
+ this.issueInstant = date;
+ }
+
+ @Override
+ public String getCiticenCountryCode() {
+ return this.ccc;
+ }
+
+
+ public String getBPK() {
+ return bPK;
+ }
+
+ /**
+ * Sets the bPK.
+ * @param bPK The bPK to set
+ */
+ public void setBPK(String bPK) {
+ this.bPK = bPK;
+ }
+
+
+ public Date getDateOfBirth() {
+ return this.dateOfBirth;
+ }
+
+ public String getFormatedDateOfBirth() {
+ DateFormat pvpDateFormat = new SimpleDateFormat(IDENTITY_LINK_DATE_FORMAT);
+ if (getDateOfBirth() != null)
+ return pvpDateFormat.format(getDateOfBirth());
+ else
+ return "2999-12-31";
+
+ }
+
+
+ public String getFamilyName() {
+ return this.familyName;
+ }
+
+
+ public String getGivenName() {
+ return this.givenName;
+ }
+
+
+ public String getIdentificationValue() {
+ return identificationValue;
+ }
+
+
+ public String getIdentificationType() {
+ return identificationType;
+ }
+
+ @Override
+ public IIdentityLink getIdentityLink() {
+ return identityLink;
+ }
+
+ /**
+ * @param identityLink the identityLink to set
+ */
+ public void setIdentityLink(IIdentityLink identityLink) {
+ this.identityLink = identityLink;
+ }
+
+ /**
+ * Sets the dateOfBirth.
+ * @param dateOfBirth The dateOfBirth to set
+ */
+ public void setDateOfBirth(Date dateOfBirth) {
+ this.dateOfBirth = dateOfBirth;
+ }
+
+ public void setDateOfBirth(String dateOfBirth) {
+ try {
+ if (StringUtils.isNotEmpty(dateOfBirth)) {
+ DateFormat identityLinkFormat = new SimpleDateFormat(IDENTITY_LINK_DATE_FORMAT);
+ this.dateOfBirth = identityLinkFormat.parse(dateOfBirth);
+
+ }
+
+ } catch (ParseException e) {
+ log.warn("Parse dateOfBirht from IdentityLink FAILED", e);
+
+ }
+ }
+
+ /**
+ * Sets the familyName.
+ * @param familyName The familyName to set
+ */
+ public void setFamilyName(String familyName) {
+ this.familyName = familyName;
+ }
+
+ /**
+ * Sets the givenName.
+ * @param givenName The givenName to set
+ */
+ public void setGivenName(String givenName) {
+ this.givenName = givenName;
+ }
+
+ /**
+ * Sets the identificationValue.
+ * @param identificationValue The identificationValue to set
+ */
+ public void setIdentificationValue(String identificationValue) {
+ this.identificationValue = identificationValue;
+ }
+
+ /**
+ * Sets the identificationType.
+ * @param identificationType The identificationType to set
+ */
+ public void setIdentificationType(String identificationType) {
+ this.identificationType = identificationType;
+ }
+
+
+ public String getBPKType() {
+ return bPKType;
+ }
+
+ /**
+ * Set sector identifier of user's bPK
+ *
+ * @param bPKType
+ */
+ public void setBPKType(String bPKType) {
+ this.bPKType = bPKType;
+ }
+
+ public String getEIDASQAALevel() {
+ return this.eIDASLoA;
+
+ }
+
+
+ public boolean isForeigner() {
+ return this.foreigner;
+ }
+
+
+ /**
+ * Indicate the the user is a foreigner
+ *
+ * @param true if the user is a foreigner, otherwise false
+ */
+ public void setForeigner(boolean foreigner) {
+ this.foreigner = foreigner;
+ }
+
+ @Override
+ public boolean isSsoSession() {
+ return ssoSession;
+ }
+
+
+ /**
+ * Indicate that the authentication was done by using an active SSO session
+ *
+ * @param true if a SSO was used, otherwise false
+ */
+ public void setSsoSession(boolean ssoSession) {
+ this.ssoSession = ssoSession;
+ }
+
+
+ /**
+ * Country Code for the authenticated user
+ *
+ * @param ccc Two letter country code
+ */
+ public void setCiticenCountryCode(String ccc) {
+ this.ccc = ccc;
+ }
+
+ public String getSessionIndex() {
+ return sessionIndex;
+ }
+
+ /**
+ * @param sessionIndex the sessionIndex to set
+ */
+ public void setSessionIndex(String sessionIndex) {
+ this.sessionIndex = sessionIndex;
+ }
+
+
+ @Override
+ public String getNameID() {
+ return this.nameID;
+ }
+
+ /**
+ * @param nameID the nameID to set
+ */
+ public void setNameID(String nameID) {
+ this.nameID = nameID;
+ }
+
+ /**
+ * @return the nameIDFormat
+ */
+ public String getNameIDFormat() {
+ return nameIDFormat;
+ }
+
+ /**
+ * @param nameIDFormat the nameIDFormat to set
+ */
+ public void setNameIDFormat(String nameIDFormat) {
+ this.nameIDFormat = nameIDFormat;
+ }
+
+ /**
+ * @return the ssoSessionValidTo
+ */
+ public Date getSsoSessionValidTo() {
+ return ssoSessionValidTo;
+ }
+
+ /**
+ * @param ssoSessionValidTo the ssoSessionValidTo to set
+ */
+ public void setSsoSessionValidTo(Date ssoSessionValidTo) {
+ this.ssoSessionValidTo = ssoSessionValidTo;
+ }
+
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.data.IAuthData#isBusinessService()
+ */
+ @Override
+ public boolean isBaseIDTransferRestrication() {
+ return isBaseIDTransferRestrication;
+ }
+
+ /**
+ * @param isBaseIDTransmittionAllowed the isBaseIDTransmittionAllowed to set
+ */
+ public void setBaseIDTransferRestrication(boolean isBaseIDTransferRestrication) {
+ this.isBaseIDTransferRestrication = isBaseIDTransferRestrication;
+ }
+
+ /**
+ * Returns a generic data-object with is stored with a specific identifier
+ *
+ * @param key The specific identifier of the data object
+ * @param clazz The class type which is stored with this key
+ * @return The data object or null if no data is found with this key
+ */
+ public <T> T getGenericData(String key, final Class<T> clazz) {
+ if (StringUtils.isNotEmpty(key)) {
+ Object data = genericDataStorate.get(key);
+
+ if (data == null)
+ return null;
+
+ try {
+ @SuppressWarnings("unchecked")
+ T test = (T) data;
+ return test;
+
+ } catch (Exception e) {
+ log.warn("Generic authentication-data object can not be casted to requsted type", e);
+ return null;
+
+ }
+
+ }
+
+ log.info("Can not load generic session-data with key='null'");
+ return null;
+
+ }
+
+ /**
+ * Store a generic data-object to session with a specific identifier
+ *
+ * @param key Identifier for this data-object
+ * @param object Generic data-object which should be stored. This data-object had to be implement the 'java.io.Serializable' interface
+ * @throws SessionDataStorageException Error message if the data-object can not stored to generic session-data storage
+ */
+ public void setGenericData(String key, Object object) throws EAAFStorageException {
+ if (StringUtils.isEmpty(key)) {
+ log.info("Generic session-data can not be stored with a 'null' key");
+ throw new EAAFStorageException("Generic data can not be stored with a 'null' key", null);
+
+ }
+
+ if (object != null) {
+ if (!Serializable.class.isInstance(object)) {
+ log.warn("Generic data can only store objects which implements the 'Seralizable' interface");
+ throw new EAAFStorageException("Generic data can only store objects which implements the 'Seralizable' interface", null);
+
+ }
+ }
+
+ if (genericDataStorate.containsKey(key))
+ log.debug("Overwrite generic data with key:" + key);
+ else
+ log.trace("Add generic data with key:" + key + " to session.");
+
+ genericDataStorate.put(key, object);
+ }
+
+ public void seteIDASLoA(String eIDASLoA) {
+ this.eIDASLoA = eIDASLoA;
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/EAAFCoreSpringResourceProvider.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/EAAFCoreSpringResourceProvider.java
new file mode 100644
index 00000000..a289f49d
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/EAAFCoreSpringResourceProvider.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import at.gv.egiz.components.spring.api.SpringResourceProvider;
+
+public class EAAFCoreSpringResourceProvider implements SpringResourceProvider {
+
+ @Override
+ public String getName() {
+ return "EAAF Core SpringResourceProvider";
+ }
+
+ @Override
+ public String[] getPackagesToScan() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Resource[] getResourcesToLoad() {
+ ClassPathResource sl20AuthConfig = new ClassPathResource("/eaaf_core.beans.xml", EAAFCoreSpringResourceProvider.class);
+
+ return new Resource[] {sl20AuthConfig};
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java
new file mode 100644
index 00000000..e52a7884
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java
@@ -0,0 +1,341 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.auth;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.StringEscapeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.IRequestStorage;
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.api.idp.auth.IAuthenticationManager;
+import at.gv.egiz.eaaf.core.api.idp.auth.ISSOManager;
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.api.idp.process.ProcessEngine;
+import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFSSOException;
+import at.gv.egiz.eaaf.core.exceptions.NoPassivAuthenticationException;
+import at.gv.egiz.eaaf.core.exceptions.ProcessExecutionException;
+import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;
+import at.gv.egiz.eaaf.core.impl.idp.auth.modules.ModuleRegistration;
+import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl;
+import at.gv.egiz.eaaf.core.impl.idp.process.ExecutionContextImpl;
+import at.gv.egiz.eaaf.core.impl.utils.TransactionIDUtils;
+
+public abstract class AbstractAuthenticationManager implements IAuthenticationManager {
+ private static final Logger log = LoggerFactory.getLogger(AbstractAuthenticationManager.class);
+
+ private static List<String> reqParameterWhiteListeForModules = new ArrayList<String>();
+ private static List<String> reqHeaderWhiteListeForModules = new ArrayList<String>();
+
+ public static final String MOA_SESSION = "MoaAuthenticationSession";
+ public static final String MOA_AUTHENTICATED = "MoaAuthenticated";
+
+ public static final int SLOTIMEOUT = 30 * 1000; //30 sec
+
+ @Autowired(required=true) protected IConfiguration authConfig;
+ @Autowired(required=true) private ProcessEngine processEngine;
+ @Autowired(required=true) private IRequestStorage requestStoreage;
+ @Autowired(required=true) protected IRevisionLogger revisionsLogger;
+ @Autowired(required=false) protected ISSOManager ssoManager;
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.eaaf.core.impl.idp.auth.IAuthenticationManager#addParameterNameToWhiteList(java.lang.String)
+ */
+ @Override
+ public final void addParameterNameToWhiteList(String httpReqParam) {
+ if (StringUtils.isNotEmpty(httpReqParam))
+ reqParameterWhiteListeForModules.add(httpReqParam);
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.eaaf.core.impl.idp.auth.IAuthenticationManager#addHeaderNameToWhiteList(java.lang.String)
+ */
+ @Override
+ public final void addHeaderNameToWhiteList(String httpReqParam) {
+ if (StringUtils.isNotEmpty(httpReqParam))
+ reqHeaderWhiteListeForModules.add(httpReqParam.toLowerCase());
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.eaaf.core.impl.idp.auth.IAuthenticationManager#addHeaderNameToWhiteList(java.lang.String)
+ */
+ @Override
+ public final boolean doAuthentication(HttpServletRequest httpReq, HttpServletResponse httpResp,
+ IRequest pendingReq) throws EAAFException {
+
+ if (!(pendingReq instanceof RequestImpl)) {
+ log.error("Requests that need authentication MUST be of type 'RequestImpl'");
+ throw new RuntimeException("Requests that need authentication HAS TO BE of type 'RequestImpl'");
+
+ }
+
+ //load OA configuration from pending request
+ ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration();
+
+ //set logging context and log unique OA identifier to revision log
+ TransactionIDUtils.setServiceProviderId(oaParam.getUniqueIdentifier());
+ revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_FOR_SP, pendingReq.getSPEntityId());
+
+ //generic authentication request validation
+ if (pendingReq.isPassiv() && pendingReq.forceAuth()) {
+ // conflict!
+ throw new NoPassivAuthenticationException();
+ }
+
+
+ //check Single Sign-On functionality if SSOManager is available
+ boolean isValidSSOSession = false;
+ if (ssoManager != null) {
+ log.trace("SSOManager is loaded. Starting SSO session validation ... ");
+ //check if SSO is allowed for this service provider
+ ssoManager.isSSOAllowedForSP(pendingReq, httpReq);
+
+ //check if SSO session is active and valid
+ isValidSSOSession = ssoManager.checkAndValidateSSOSession(pendingReq, httpReq, httpResp);
+
+ }
+
+ //check if session is already authenticated
+ //boolean isSessionAuthenticated = tryPerformAuthentication((RequestImpl) pendingReq, isValidSSOSession);
+ //boolean isSessionAuthenticated = isValidSSOSession && StringUtils.isNotEmpty(pendingReq.getSSOSessionIdentifier());
+
+
+ //force new authentication authentication process
+ if (pendingReq.forceAuth()) {
+ startAuthenticationProcess(httpReq, httpResp, (RequestImpl) pendingReq);
+ return false;
+
+ //perform SSO-Consents evaluation if it it required
+ } else if (isValidSSOSession && pendingReq.isNeedUserConsent()) {
+ sendSingleSignOnConsentsEvaluation(httpReq, httpResp, (RequestImpl) pendingReq);
+ return false;
+
+
+ } else if (pendingReq.isPassiv()) {
+ if (isValidSSOSession &&
+ StringUtils.isNotEmpty(pendingReq.getSSOSessionIdentifier()) ) {
+ // Passive authentication ok! --> Populate pending request from SSO session
+ ssoManager.populatePendingRequestWithSSOInformation(pendingReq);
+ revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_FINISHED);
+ return true;
+
+ } else {
+ throw new NoPassivAuthenticationException();
+
+ }
+
+ } else {
+ if (isValidSSOSession &&
+ StringUtils.isNotEmpty(pendingReq.getSSOSessionIdentifier())) {
+ // Is authenticated .. proceed
+ ssoManager.populatePendingRequestWithSSOInformation(pendingReq);
+ revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_FINISHED);
+ return true;
+
+ } else {
+ // Start authentication!
+ startAuthenticationProcess(httpReq, httpResp, (RequestImpl) pendingReq);
+ return false;
+
+ }
+ }
+ }
+
+ public final void performOnlyIDPLogOut(HttpServletRequest request, HttpServletResponse response, IRequest pendingReq) {
+
+ log.debug("Close session. Remove pending request ... ");
+ requestStoreage.removePendingRequest(pendingReq.getPendingRequestId());
+
+
+ if (ssoManager != null) {
+ try {
+ log.trace("'SSOManager' active. Search for active SSO sessions ... ");
+ if (ssoManager.destroySSOSessionOnIDPOnly(request, response, pendingReq))
+ log.info("SSO session successfully closed");
+ else
+ log.info("Closing SSO session NOT successfully");
+
+ } catch (EAAFSSOException e) {
+ log.warn("Destroying of SSO session FAILED. Reason: " + e.getMessage(), e);
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Populate process execution context and start process engine
+ *
+ * @param httpReq
+ * @param httpResp
+ * @param pendingReq
+ * @throws ServletException
+ * @throws IOException
+ * @throws EAAFException
+ */
+ private void startAuthenticationProcess(HttpServletRequest httpReq,
+ HttpServletResponse httpResp, RequestImpl pendingReq)
+ throws EAAFException {
+
+ log.info("Starting authentication ...");
+ revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_STARTED);
+
+ //create authentication process execution context
+ ExecutionContext executionContext = new ExecutionContextImpl();
+
+ //set oaIdentifeir
+ executionContext.put(EAAFConstants.PROCESS_ENGINE_SERVICE_PROVIDER_ENTITYID,
+ pendingReq.getServiceProviderConfiguration().getUniqueIdentifier());
+
+ //add X509 SSL client certificate if exist
+ if (httpReq.getAttribute("javax.servlet.request.X509Certificate") != null) {
+ log.debug("Find SSL-client-certificate on request --> Add it to context");
+ executionContext.put(EAAFConstants.PROCESS_ENGINE_SSL_CLIENT_CERTIFICATE,
+ ((X509Certificate[])httpReq.getAttribute("javax.servlet.request.X509Certificate")));
+ pendingReq.setGenericDataToSession(EAAFConstants.PROCESS_ENGINE_SSL_CLIENT_CERTIFICATE,
+ ((X509Certificate[])httpReq.getAttribute("javax.servlet.request.X509Certificate")));
+
+ }
+
+ //add additional http request parameter to context
+ if (!reqParameterWhiteListeForModules.isEmpty()) {
+ Enumeration<String> reqParamNames = httpReq.getParameterNames();
+ while(reqParamNames.hasMoreElements()) {
+ String paramName = reqParamNames.nextElement();
+ if (StringUtils.isNotEmpty(paramName) && reqParameterWhiteListeForModules.contains(paramName) )
+ executionContext.put(paramName, StringEscapeUtils.escapeHtml4(httpReq.getParameter(paramName)));
+ }
+ }
+
+ //add additional http request parameter to context
+ if (!reqHeaderWhiteListeForModules.isEmpty()) {
+ Enumeration<String> reqHeaderNames = httpReq.getHeaderNames();
+ while(reqHeaderNames.hasMoreElements()) {
+ String paramName = reqHeaderNames.nextElement();
+ if (StringUtils.isNotEmpty(paramName) && reqHeaderWhiteListeForModules.contains(paramName.toLowerCase()) )
+ executionContext.put(paramName, StringEscapeUtils.escapeHtml4(httpReq.getHeader(paramName)));
+
+ }
+ }
+
+ //populate more IDP specific information to execution context
+ populateExecutionContext(executionContext, pendingReq, httpReq);
+
+ //start process engine
+ startProcessEngine(pendingReq, executionContext);
+
+ }
+
+ /**
+ *
+ *
+ * @throws EAAFException
+ */
+ abstract protected void populateExecutionContext(ExecutionContext executionContext,
+ RequestImpl pendingReq, HttpServletRequest httpReq) throws EAAFException;
+
+ /**
+ * Starting a user consent evaluation
+ *
+ * @param request
+ * @param response
+ * @param pendingReq
+ * @throws ServletException
+ * @throws IOException
+ * @throws EAAFException
+ */
+ private void sendSingleSignOnConsentsEvaluation(HttpServletRequest request,
+ HttpServletResponse response, RequestImpl pendingReq)
+ throws EAAFException {
+
+ log.debug("Starting SSO user-consents evaluation ...");
+
+ //set authenticated flag to false, because user consents is required
+ pendingReq.setAuthenticated(false);
+
+ //create execution context
+ ExecutionContext executionContext = new ExecutionContextImpl();
+ executionContext.put(ISSOManager.PROCESS_ENGINE_SSO_CONSENTS_EVALUATION, true);
+
+ //start process engine
+ startProcessEngine(pendingReq, executionContext);
+
+ }
+
+
+ /**
+ * Select a specific process and starting process engine
+ *
+ * @param pendingReq
+ * @param executionContext
+ * @throws EAAFException
+ */
+ private void startProcessEngine(RequestImpl pendingReq, ExecutionContext executionContext) throws EAAFException {
+ try {
+ //put pending-request ID on execurtionContext
+ executionContext.put(EAAFConstants.PROCESS_ENGINE_PENDINGREQUESTID, pendingReq.getPendingRequestId());
+
+ // create process instance
+ String processDefinitionId = ModuleRegistration.getInstance().selectProcess(executionContext);
+
+ if (processDefinitionId == null) {
+ log.warn("No suitable process found for PendingReqId " + pendingReq.getPendingRequestId() );
+ throw new EAAFException(
+ "process.02",
+ new Object[] {pendingReq.getPendingRequestId()}
+ ,"No suitable process found for PendingReqId " + pendingReq.getPendingRequestId());
+
+ }
+
+ String processInstanceId = processEngine.createProcessInstance(processDefinitionId, executionContext);
+
+ // keep process instance id in protocol pending-request
+ pendingReq.setProcessInstanceId(processInstanceId);
+
+ //store pending-request
+ requestStoreage.storePendingRequest(pendingReq);
+
+ // start process
+ processEngine.start(pendingReq);
+
+ } catch (ProcessExecutionException e) {
+ Throwable cause = e.getCause();
+ if (cause != null && cause instanceof TaskExecutionException) {
+ Throwable taskCause = cause.getCause();
+ if (taskCause != null && taskCause instanceof EAAFException) {
+ EAAFException moaTaskCause = (EAAFException) taskCause;
+ log.warn(taskCause.getMessage(), taskCause);
+ throw moaTaskCause;
+
+ }
+ }
+
+ throw new EAAFException(
+ "process.01",
+ new Object[] { pendingReq.getProcessInstanceId(), pendingReq.getPendingRequestId() },
+ "Authentication process execution FAILED",
+ e);
+ }
+
+ }
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java
new file mode 100644
index 00000000..7e0d4cc7
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.auth;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.IRequestStorage;
+import at.gv.egiz.eaaf.core.api.idp.process.ProcessInstanceStoreDAO;
+import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException;
+import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl;
+import at.gv.egiz.eaaf.core.impl.utils.Random;
+import at.gv.egiz.eaaf.core.impl.utils.TransactionIDUtils;
+
+@Service("RequestStorage")
+public class RequestStorage implements IRequestStorage{
+ private static final Logger log = LoggerFactory.getLogger(RequestStorage.class);
+
+ @Autowired ITransactionStorage transactionStorage;
+ @Autowired ProcessInstanceStoreDAO processInstanceStore;
+
+ @Override
+ public IRequest getPendingRequest(String pendingReqID) {
+
+ try {
+ IRequest pendingRequest = transactionStorage.get(pendingReqID, IRequest.class);
+ if (pendingRequest == null) {
+ log.info("No PendingRequst found with pendingRequestID " + pendingReqID);
+ return null;
+
+ }
+
+ //set transactionID and sessionID to Logger
+ TransactionIDUtils.setAllLoggingVariables(pendingRequest);
+
+ return pendingRequest;
+
+ } catch (EAAFException | NullPointerException e) {
+ log.info("No PendingRequst found with pendingRequestID " + pendingReqID);
+ return null;
+
+ }
+ }
+
+ @Override
+ public void storePendingRequest(IRequest pendingRequest) throws EAAFException {
+ try {
+ if (pendingRequest instanceof IRequest)
+ transactionStorage.put(((IRequest)pendingRequest).getPendingRequestId(), pendingRequest, -1);
+
+ else
+ throw new EAAFException("PendigRequest is NOT of type 'IRequest'", null);
+
+
+ } catch (EAAFException e) {
+ log.warn("PendingRequest with ID=" + ((IRequest)pendingRequest).getPendingRequestId() +
+ " can not stored.", e);
+ throw new EAAFStorageException("PendingRequest with Id: " + ((IRequest)pendingRequest).getPendingRequestId()
+ + " can not be stored", e);
+
+ }
+
+ }
+
+ @Override
+ public void removePendingRequest(String requestID) {
+
+ if (requestID != null) {
+
+ //remove process-management execution instance
+ try {
+ IRequest pendingReq = getPendingRequest(requestID);
+
+ if (pendingReq != null &&
+ pendingReq.getProcessInstanceId() != null)
+ processInstanceStore.remove(pendingReq.getProcessInstanceId());
+
+ } catch (EAAFException e) {
+ log.warn("Removing process associated with pending-request:" + requestID + " FAILED.", e);
+
+ }
+
+ transactionStorage.remove(requestID);
+
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.storage.IRequestStorage#changePendingRequestID(at.gv.egovernment.moa.id.moduls.IRequest)
+ */
+ @Override
+ public String changePendingRequestID(IRequest pendingRequest) throws EAAFException {
+
+ if (pendingRequest instanceof RequestImpl) {
+ String newRequestID = Random.nextHexRandom32();
+ String oldRequestID = pendingRequest.getPendingRequestId();
+
+ log.debug("Change pendingRequestID from " + pendingRequest.getPendingRequestId()
+ + " to " + newRequestID);
+
+ ((RequestImpl)pendingRequest).setPendingRequestId(newRequestID);
+ transactionStorage.changeKey(oldRequestID, newRequestID, pendingRequest);
+
+ //only delete oldRequestID, no change.
+
+ return newRequestID;
+
+ } else {
+ log.error("PendingRequest object is not of type 'RequestImpl.class'");
+ throw new EAAFException("PendingRequest object is not of type 'RequestImpl.class'", null);
+ }
+
+ }
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java
new file mode 100644
index 00000000..561f6f32
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java
@@ -0,0 +1,467 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.auth.builder;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.Base64Utils;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.data.PVPAttributeDefinitions;
+import at.gv.egiz.eaaf.core.api.idp.IAuthenticationDataBuilder;
+import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.api.idp.auth.data.IAuthProcessDataContainer;
+import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink;
+import at.gv.egiz.eaaf.core.exceptions.EAAFBuilderException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFParserException;
+import at.gv.egiz.eaaf.core.exceptions.XPathException;
+import at.gv.egiz.eaaf.core.impl.data.Pair;
+import at.gv.egiz.eaaf.core.impl.idp.AuthenticationData;
+import at.gv.egiz.eaaf.core.impl.idp.auth.data.SimpleIdentityLinkAssertionParser;
+import at.gv.egiz.eaaf.core.impl.utils.XPathUtils;
+
+
+public abstract class AbstractAuthenticationDataBuilder implements IAuthenticationDataBuilder {
+ private static final Logger log = LoggerFactory.getLogger(AbstractAuthenticationDataBuilder.class);
+ protected Collection<String> includedToGenericAuthData = null;
+ @Autowired protected IConfiguration basicConfig;
+
+ protected void generateBasicAuthData(AuthenticationData authData, IRequest pendingReq,
+ IAuthProcessDataContainer authProcessData) throws EAAFBuilderException, EAAFConfigurationException, XPathException, DOMException, EAAFParserException {
+
+ if (authProcessData.getGenericSessionDataStorage() != null &&
+ !authProcessData.getGenericSessionDataStorage().isEmpty())
+ includedToGenericAuthData = authProcessData.getGenericSessionDataStorage().keySet();
+ else
+ includedToGenericAuthData = new ArrayList<String>();
+
+ //####################################################
+ //set general authData info's
+ authData.setAuthenticationIssuer(pendingReq.getAuthURL());
+ authData.setSsoSession(pendingReq.needSingleSignOnFunctionality());
+ authData.setBaseIDTransferRestrication(pendingReq.getServiceProviderConfiguration().hasBaseIdTransferRestriction());
+
+
+ //####################################################
+ //parse user info's from identityLink
+ IIdentityLink idlFromPVPAttr = null;
+ IIdentityLink identityLink = authProcessData.getIdentityLink();
+ if (identityLink != null) {
+ parseBasicUserInfosFromIDL(authData, identityLink, includedToGenericAuthData);
+
+ } else {
+ // identityLink is not direct in MOASession
+ String pvpAttrIDL = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_IDENTITY_LINK_NAME, String.class);
+ //find PVP-Attr. which contains the IdentityLink
+ if (StringUtils.isNotEmpty(pvpAttrIDL)) {
+ log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_IDENTITY_LINK_FRIENDLY_NAME
+ + " --> Parse basic user info's from that attribute.");
+ InputStream idlStream = null;
+ try {
+ idlStream = new ByteArrayInputStream(Base64Utils.decodeFromString(pvpAttrIDL));
+ idlFromPVPAttr = new SimpleIdentityLinkAssertionParser(idlStream).parseIdentityLink();
+ parseBasicUserInfosFromIDL(authData, idlFromPVPAttr, includedToGenericAuthData);
+
+ //set identitylink into AuthProcessData
+ authProcessData.setIdentityLink(idlFromPVPAttr);;
+
+ } catch (EAAFParserException e) {
+ log.warn("Received IdentityLink is not valid", e);
+
+ } catch (Exception e) {
+ log.warn("Received IdentityLink is not valid", e);
+
+ } finally {
+ try {
+ includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_IDENTITY_LINK_NAME);
+ if (idlStream != null)
+ idlStream.close();
+
+ } catch (IOException e) {
+ log.warn("Close InputStream FAILED.", e);
+
+ }
+ }
+ }
+
+ //if no basic user info's are set yet, parse info's single PVP-Attributes
+ if (StringUtils.isEmpty(authData.getFamilyName())) {
+ log.debug("No IdentityLink found or not parseable --> Parse basic user info's from single PVP-Attributes.");
+ authData.setFamilyName(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME, String.class));
+ authData.setGivenName(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.GIVEN_NAME_NAME, String.class));
+ authData.setDateOfBirth(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.BIRTHDATE_NAME, String.class));
+ authData.setIdentificationValue(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME, String.class));
+ authData.setIdentificationType(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME, String.class));
+
+ //remove corresponding keys from genericSessionData if exists
+ includedToGenericAuthData.remove(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME);
+ includedToGenericAuthData.remove(PVPAttributeDefinitions.GIVEN_NAME_NAME);
+ includedToGenericAuthData.remove(PVPAttributeDefinitions.BIRTHDATE_NAME);
+ includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME);
+ includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME);
+ }
+
+ }
+
+ if (authData.getIdentificationType() != null &&
+ !authData.getIdentificationType().equals(EAAFConstants.URN_PREFIX_BASEID)) {
+ log.trace("IdentificationType is not a baseID --> clear it. ");
+ authData.setBPK(authData.getIdentificationValue());
+ authData.setBPKType(authData.getIdentificationType());
+
+ authData.setIdentificationValue(null);
+ authData.setIdentificationType(null);
+ }
+
+ //####################################################
+ //set QAA level
+ includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME);
+ String currentLoA = null;
+ if (StringUtils.isNotEmpty(authProcessData.getQAALevel()))
+ currentLoA = authProcessData.getQAALevel();
+ else {
+ currentLoA = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, String.class);
+ if (StringUtils.isNotEmpty(currentLoA)) {
+ log.debug("Find PVP-Attr '" + PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_FRIENDLY_NAME + "':" + currentLoA
+ + " --> Parse QAA-Level from that attribute.");
+
+ }
+ }
+ if (StringUtils.isNotEmpty(currentLoA)) {
+ if (currentLoA.startsWith(EAAFConstants.EIDAS_QAA_PREFIX)) {
+ authData.seteIDASLoA(currentLoA);
+
+ } else
+ log.info("Only eIDAS LoAs are supported by this implementation");
+
+ } else {
+ log.info("No QAA level found. Set to default level " + EAAFConstants.EIDAS_QAA_LOW);
+ authData.seteIDASLoA(EAAFConstants.EIDAS_QAA_LOW);
+
+ }
+
+ //####################################################
+ //set isForeigner flag
+ //TODO: change to new eIDAS-token attribute identifier
+ if (authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_STORK_TOKEN_NAME) != null) {
+ log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_STORK_TOKEN_FRIENDLY_NAME
+ + " --> Set 'isForeigner' flag to TRUE");
+ authData.setForeigner(true);
+
+ } else {
+ authData.setForeigner(authProcessData.isForeigner());
+
+ }
+
+ //####################################################
+ //set citizen country-code
+ includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_ISSUING_NATION_NAME);
+ String pvpCCCAttr = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_ISSUING_NATION_NAME, String.class);
+ if (StringUtils.isNotEmpty(pvpCCCAttr)) {
+ authData.setCiticenCountryCode(pvpCCCAttr);
+ log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_ISSUING_NATION_FRIENDLY_NAME);
+
+ } else {
+ if (authData.isForeigner()) {
+ //TODO!!!!
+
+ } else {
+ authData.setCiticenCountryCode(basicConfig.getBasicConfiguration(
+ IConfiguration.CONFIG_PROPS_AUTH_DEFAULT_COUNTRYCODE,
+ EAAFConstants.COUNTRYCODE_AUSTRIA));
+
+ }
+ }
+
+
+ //####################################################
+ // set bPK and IdentityLink
+ String pvpbPKValue = getbPKValueFromPVPAttribute(authProcessData);
+ String pvpbPKTypeAttr = getbPKTypeFromPVPAttribute(authProcessData);
+ Pair<String, String> pvpEncbPKAttr = getEncryptedbPKFromPVPAttribute(authProcessData, authData, pendingReq.getServiceProviderConfiguration());
+
+ //check if a unique ID for this citizen exists
+ if (StringUtils.isEmpty(authData.getIdentificationValue()) &&
+ StringUtils.isEmpty(pvpbPKValue) && StringUtils.isEmpty(authData.getBPK()) &&
+ pvpEncbPKAttr == null) {
+ log.info("Can not build authData, because moaSession include no bPK, encrypted bPK or baseID");
+ throw new EAAFBuilderException("builder.08", new Object[]{"No " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME
+ + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME
+ + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME},
+ "No " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME
+ + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME
+ + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME);
+
+ }
+
+ // baseID is in MOASesson --> calculate bPK directly
+ if (StringUtils.isNotEmpty(authData.getIdentificationValue())) {
+ log.debug("Citizen baseID is in MOASession --> calculate bPK from this.");
+ Pair<String, String> result = buildOAspecificbPK(pendingReq, authData);
+ authData.setBPK(result.getFirst());
+ authData.setBPKType(result.getSecond());
+
+ //check if bPK already added to AuthData matches OA
+ } else if (StringUtils.isNotEmpty(authData.getBPK())
+ && matchsReceivedbPKToOnlineApplication(pendingReq.getServiceProviderConfiguration(), authData.getBPKType()) ) {
+ log.debug("Correct bPK is already included in AuthData.");
+
+ //check if bPK received by PVP-Attribute matches OA
+ } else if (StringUtils.isNotEmpty(pvpbPKValue) &&
+ matchsReceivedbPKToOnlineApplication(pendingReq.getServiceProviderConfiguration(), pvpbPKTypeAttr)) {
+ log.debug("Receive correct bPK from PVP-Attribute");
+ authData.setBPK(pvpbPKValue);
+ authData.setBPKType(pvpbPKTypeAttr);
+
+ //check if decrypted bPK exists
+ } else if (pvpEncbPKAttr != null) {
+ log.debug("Receive bPK as encrypted bPK and decryption was possible.");
+ authData.setBPK(pvpEncbPKAttr.getFirst());
+ authData.setBPKType(pvpEncbPKAttr.getSecond());
+
+ //ask SZR to get bPK
+ } else {
+ String notValidbPK = authData.getBPK();
+ String notValidbPKType = authData.getBPKType();
+ if (StringUtils.isEmpty(notValidbPK) &&
+ StringUtils.isEmpty(notValidbPKType)) {
+ notValidbPK = pvpbPKValue;
+ notValidbPKType = pvpbPKTypeAttr;
+
+ if (StringUtils.isEmpty(notValidbPK) &&
+ StringUtils.isEmpty(notValidbPKType)) {
+ log.error("No bPK in MOASession. THIS error should not occur any more.");
+ throw new NullPointerException("No bPK in MOASession. THIS error should not occur any more.");
+ }
+ }
+
+ Pair<String, String> baseIDFromSZR = getbaseIDFromSZR(authData, notValidbPK, notValidbPKType);
+ if (baseIDFromSZR != null) {
+ log.info("Receive citizen baseID from SRZ. Authentication can be completed");
+ authData.setIdentificationValue(baseIDFromSZR.getFirst());
+ authData.setIdentificationType(baseIDFromSZR.getSecond());
+ Pair<String, String> result = buildOAspecificbPK(pendingReq, authData);
+ authData.setBPK(result.getFirst());
+ authData.setBPKType(result.getSecond());
+
+ } else {
+ log.warn("Can not build authData, because moaSession include no valid bPK, encrypted bPK or baseID");
+ throw new EAAFBuilderException("builder.08", new Object[]{"No valid " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME
+ + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME
+ + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME},
+ "No valid " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME
+ + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME
+ + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME);
+
+ }
+ }
+
+ //build IdentityLink
+ if (authProcessData.getIdentityLink() != null)
+ authData.setIdentityLink(buildOAspecificIdentityLink(
+ pendingReq.getServiceProviderConfiguration(),
+ authProcessData.getIdentityLink(),
+ authData.getBPK(),
+ authData.getBPKType()));
+ else
+ log.info("Can NOT set IdentityLink. Msg: No IdentityLink found");
+
+ }
+
+ //extract a encrypted bPK from PVP attrobute
+ protected abstract Pair<String, String> getEncryptedbPKFromPVPAttribute(IAuthProcessDataContainer authProcessDataContainer,
+ AuthenticationData authData, ISPConfiguration spConfig) throws EAAFBuilderException;
+
+ //request baseId from SRZ
+ protected abstract Pair<String, String> getbaseIDFromSZR(AuthenticationData authData, String notValidbPK,
+ String notValidbPKType);
+
+
+ protected Pair<String, String> buildOAspecificbPK(IRequest pendingReq, AuthenticationData authData) throws EAAFBuilderException {
+ ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration();
+
+ String baseID = authData.getIdentificationValue();
+ String baseIDType = authData.getIdentificationType();
+ Pair<String, String> sectorSpecId = null;
+
+ if (EAAFConstants.URN_PREFIX_BASEID.equals(baseIDType)) {
+ //SAML1 legacy target parameter work-around
+ String spTargetId = oaParam.getAreaSpecificTargetIdentifier();
+ log.debug("Use OA target identifier '" + spTargetId + "' from configuration");
+
+ //calculate sector specific unique identifier
+ sectorSpecId = new BPKBuilder().generateAreaSpecificPersonIdentifier(baseID, spTargetId);
+
+ } else {
+ log.error("!!!baseID-element does not include a baseID. This should not be happen any more!!!");
+ sectorSpecId = Pair.newInstance(baseID, baseIDType);
+
+ }
+
+ log.trace("Authenticate user with bPK:" + sectorSpecId.getFirst() + " Type:" + sectorSpecId.getSecond());
+ return sectorSpecId;
+
+ }
+
+ protected IIdentityLink buildOAspecificIdentityLink(ISPConfiguration spConfig, IIdentityLink idl, String bPK, String bPKType) throws EAAFConfigurationException, XPathException, DOMException, EAAFParserException {
+ if (spConfig.hasBaseIdTransferRestriction()) {
+ log.debug("SP: " + spConfig.getUniqueIdentifier() + " has baseId transfer restriction. Remove baseId from IDL ...");
+ Element idlassertion = idl.getSamlAssertion();
+ //set bpk/wpbk;
+ Node prIdentification = XPathUtils.selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_IDENT_VALUE_XPATH);
+ prIdentification.getFirstChild().setNodeValue(bPK);
+ //set bkp/wpbk type
+ Node prIdentificationType = XPathUtils.selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_IDENT_TYPE_XPATH);
+ prIdentificationType.getFirstChild().setNodeValue(bPKType);
+
+ SimpleIdentityLinkAssertionParser idlparser = new SimpleIdentityLinkAssertionParser(idlassertion);
+ return idlparser.parseIdentityLink();
+
+ } else
+ return idl;
+
+ }
+
+ /**
+ * Check a bPK-Type against a Service-Provider configuration <br>
+ * If bPK-Type is <code>null</code> the result is <code>false</code>.
+ *
+ * @param oaParam Service-Provider configuration, never null
+ * @param bPKType bPK-Type to check
+ * @return true, if bPK-Type matchs to Service-Provider configuration, otherwise false
+ */
+ private boolean matchsReceivedbPKToOnlineApplication(ISPConfiguration oaParam, String bPKType) {
+ return oaParam.getAreaSpecificTargetIdentifier().equals(bPKType);
+
+ }
+
+ /**
+ * Parse information from an IdentityLink into AuthData object
+ *
+ * @param authData
+ * @param identityLink
+ * @param includedGenericSessionData
+ */
+ private void parseBasicUserInfosFromIDL(AuthenticationData authData, IIdentityLink identityLink, Collection<String> includedGenericSessionData) {
+ authData.setIdentificationValue(identityLink.getIdentificationValue());
+ authData.setIdentificationType(identityLink.getIdentificationType());
+
+ authData.setGivenName(identityLink.getGivenName());
+ authData.setFamilyName(identityLink.getFamilyName());
+ authData.setDateOfBirth(identityLink.getDateOfBirth());
+
+ //remove corresponding keys from genericSessionData if exists
+ includedGenericSessionData.remove(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME);
+ includedGenericSessionData.remove(PVPAttributeDefinitions.GIVEN_NAME_NAME);
+ includedGenericSessionData.remove(PVPAttributeDefinitions.BIRTHDATE_NAME);
+ includedGenericSessionData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME);
+ includedGenericSessionData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME);
+
+ }
+
+ /**
+ * Get bPK from PVP Attribute 'BPK_NAME', which could be exist in
+ * MOASession as 'GenericData' <br> <pre><code>session.getGenericDataFromSession(PVPConstants.BPK_NAME, String.class)</code></pre>
+ *
+ * @param session MOASession, but never null
+ * @return bPK, which was received by PVP-Attribute, or <code>null</code> if no attribute exists
+ */
+ private String getbPKValueFromPVPAttribute(IAuthProcessDataContainer session) {
+ String pvpbPKValueAttr = session.getGenericDataFromSession(PVPAttributeDefinitions.BPK_NAME, String.class);
+ if (StringUtils.isNotEmpty(pvpbPKValueAttr)) {
+
+ //fix a wrong bPK-value prefix, which was used in some PVP Standardportal implementations
+ if (pvpbPKValueAttr.startsWith("bPK:")) {
+ log.warn("Attribute " + PVPAttributeDefinitions.BPK_NAME
+ + " contains a not standardize prefix! Staring attribute value correction process ...");
+ pvpbPKValueAttr = pvpbPKValueAttr.substring("bPK:".length());
+
+ }
+
+ String[] spitted = pvpbPKValueAttr.split(":");
+ if (spitted.length != 2) {
+ log.warn("Attribute " + PVPAttributeDefinitions.BPK_NAME + " has a wrong encoding and can NOT be USED!"
+ + " Value:" + pvpbPKValueAttr);
+ return null;
+
+ }
+ log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME);
+ return spitted[1];
+
+ }
+
+ return null;
+ }
+
+ /**
+ * Get bPK-Type from PVP Attribute 'EID_SECTOR_FOR_IDENTIFIER_NAME', which could be exist in
+ * MOASession as 'GenericData' <br> <pre><code>session.getGenericDataFromSession(PVPConstants.EID_SECTOR_FOR_IDENTIFIER_NAME, String.class)</code></pre>
+ *
+ * @param session MOASession, but never null
+ * @return bPKType, which was received by PVP-Attribute, or <code>null</code> if no attribute exists
+ */
+ private String getbPKTypeFromPVPAttribute(IAuthProcessDataContainer session) {
+ String pvpbPKTypeAttr = session.getGenericDataFromSession(PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, String.class);
+ if (StringUtils.isNotEmpty(pvpbPKTypeAttr)) {
+
+ //fix a wrong bPK-Type encoding, which was used in some PVP Standardportal implementations
+ if (pvpbPKTypeAttr.startsWith(EAAFConstants.URN_PREFIX_CDID) &&
+ !pvpbPKTypeAttr.substring(EAAFConstants.URN_PREFIX_CDID.length(),
+ EAAFConstants.URN_PREFIX_CDID.length() + 1).equals("+")) {
+ log.warn("Receive uncorrect encoded bBKType attribute " + pvpbPKTypeAttr + " Starting attribute value correction ... ");
+ pvpbPKTypeAttr = EAAFConstants.URN_PREFIX_CDID + "+" + pvpbPKTypeAttr.substring(EAAFConstants.URN_PREFIX_CDID.length() + 1);
+
+ }
+ log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_FRIENDLY_NAME);
+ return pvpbPKTypeAttr;
+ }
+
+ return null;
+
+
+ /*
+ * INFO: This code could be used to extract the bPKType from 'PVPConstants.BPK_NAME',
+ * because the prefix of BPK_NAME attribute contains the postfix of the bPKType
+ *
+ * Now, all PVP Standardportals should be able to send 'EID_SECTOR_FOR_IDENTIFIER'
+ * PVP attributes
+ */
+// String pvpbPKValueAttr = session.getGenericDataFromSession(PVPConstants.BPK_NAME, String.class);
+// String[] spitted = pvpbPKValueAttr.split(":");
+// if (MiscUtil.isEmpty(authData.getBPKType())) {
+// Logger.debug("PVP assertion contains NO bPK/wbPK target attribute. " +
+// "Starting target extraction from bPK/wbPK prefix ...");
+// //exract bPK/wbPK type from bpk attribute value prefix if type is
+// //not transmitted as single attribute
+// Pattern pattern = Pattern.compile("[a-zA-Z]{2}(-[a-zA-Z]+)?");
+// Matcher matcher = pattern.matcher(spitted[0]);
+// if (matcher.matches()) {
+// //find public service bPK
+// authData.setBPKType(Constants.URN_PREFIX_CDID + "+" + spitted[0]);
+// Logger.debug("Found bPK prefix. Set target to " + authData.getBPKType());
+//
+// } else {
+// //find business service wbPK
+// authData.setBPKType(Constants.URN_PREFIX_WBPK+ "+" + spitted[0]);
+// Logger.debug("Found wbPK prefix. Set target to " + authData.getBPKType());
+//
+// }
+// }
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java
new file mode 100644
index 00000000..62a57dd1
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java
@@ -0,0 +1,302 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+/*
+ * Copyright 2003 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.egiz.eaaf.core.impl.idp.auth.builder;
+
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.Base64Utils;
+
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.exceptions.EAAFBuilderException;
+import at.gv.egiz.eaaf.core.impl.data.Pair;
+
+/**
+ * Builder for the bPK, as defined in
+ * <code>&quot;Ableitung f&uml;r die bereichsspezifische Personenkennzeichnung&quot;</code>
+ * version <code>1.0.1</code> from <code>&quot;reference.e-government.gv.at&quot;</code>.
+ *
+ */
+public class BPKBuilder {
+ private static final Logger log = LoggerFactory.getLogger(BPKBuilder.class);
+
+ /**
+ * Calculates an area specific unique person-identifier from a baseID
+ *
+ * @param baseID baseId from user but never null
+ * @param targetIdentifier target identifier for area specific identifier calculation but never null
+ * @return Pair<unique person identifier for this target, targetArea> but never null
+ * @throws EAAFBuilderException if some input data are not valid
+ */
+ public Pair<String, String> generateAreaSpecificPersonIdentifier(String baseID, String targetIdentifier) throws EAAFBuilderException {
+ return generateAreaSpecificPersonIdentifier(baseID, EAAFConstants.URN_PREFIX_BASEID, targetIdentifier);
+
+ }
+
+ /**
+ * Calculates an area specific unique person-identifier from an unique identifier with a specific type
+ *
+ * @param baseID baseId from user but never null
+ * @param baseIdType Type of the baseID but never null
+ * @param targetIdentifier target identifier for area specific identifier calculation but never null
+ * @return Pair<unique person identifier for this target, targetArea> but never null
+ * @throws EAAFBuilderException if some input data are not valid
+ */
+ public Pair<String, String> generateAreaSpecificPersonIdentifier(String baseID, String baseIdType, String targetIdentifier) throws EAAFBuilderException{
+ if (StringUtils.isEmpty(baseID))
+ throw new EAAFBuilderException("builder.00", new Object[]{"baseID is empty or null"},
+ "BaseId is empty or null");
+
+ if (StringUtils.isEmpty(baseIdType))
+ throw new EAAFBuilderException("builder.00", new Object[]{"the type of baseID is empty or null"},
+ "Type of baseId is empty or null");
+
+ if (StringUtils.isEmpty(targetIdentifier))
+ throw new EAAFBuilderException("builder.00", new Object[]{"SP specific target identifier is empty or null"},
+ "SP specific target identifier is empty or null");
+
+ if (baseIdType.equals(EAAFConstants.URN_PREFIX_BASEID)) {
+ log.trace("Find baseID. Starting unique identifier caluclation for this target");
+
+ if (targetIdentifier.startsWith(EAAFConstants.URN_PREFIX_CDID) ||
+ targetIdentifier.startsWith(EAAFConstants.URN_PREFIX_WBPK)) {
+ log.trace("Calculate bPK, wbPK, or STORK identifier for target: " + targetIdentifier);
+ return Pair.newInstance(calculatebPKwbPK(baseID + "+" + targetIdentifier), targetIdentifier);
+
+ } else if (targetIdentifier.startsWith(EAAFConstants.URN_PREFIX_EIDAS)) {
+ log.trace("Calculate eIDAS identifier for target: " + targetIdentifier);
+ String[] splittedTarget = targetIdentifier.split("\\+");
+ String cititzenCountryCode = splittedTarget[1];
+ String eIDASOutboundCountry = splittedTarget[2];
+
+ if (cititzenCountryCode.equalsIgnoreCase(eIDASOutboundCountry)) {
+ log.warn("Suspect configuration FOUND!!! CitizenCountry equals DestinationCountry");
+
+ }
+ return buildeIDASIdentifer(baseID, baseIdType, cititzenCountryCode, eIDASOutboundCountry);
+
+
+ } else
+ throw new EAAFBuilderException("builder.00",
+ new Object[]{"Target identifier: " + targetIdentifier + " is NOT allowed or unknown"},
+ "Target identifier: " + targetIdentifier + " is NOT allowed or unknown");
+
+ } else {
+ log.trace("BaseID is not of type " + EAAFConstants.URN_PREFIX_BASEID + ". Check type against requested target ...");
+ if (baseIdType.equals(targetIdentifier)) {
+ log.debug("Unique identifier is already area specific. Is nothing todo");
+ return Pair.newInstance(baseID, targetIdentifier);
+
+ } else {
+ log.warn("Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier + " is required!");
+ throw new EAAFBuilderException("builder.00",
+ new Object[]{"Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier + " is required"},
+ "Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier + " is required");
+
+ }
+ }
+ }
+
+
+ /**
+ * Builds the eIDAS from the given parameters.
+ *
+ * @param baseID baseID of the citizen
+ * @param baseIDType Type of the baseID
+ * @param sourceCountry CountryCode of that country, which build the eIDAs ID
+ * @param destinationCountry CountryCode of that country, which receives the eIDAs ID
+ *
+ * @return Pair<eIDAs, bPKType> in a BASE64 encoding
+ * @throws EAAFBuilderException if some input data are not valid
+ */
+ private Pair<String, String> buildeIDASIdentifer(String baseID, String baseIDType, String sourceCountry, String destinationCountry)
+ throws EAAFBuilderException {
+ String bPK = null;
+ String bPKType = null;
+
+ // check if we have been called by public sector application
+ if (baseIDType.startsWith(EAAFConstants.URN_PREFIX_BASEID)) {
+ bPKType = EAAFConstants.URN_PREFIX_EIDAS + "+" + sourceCountry + "+" + destinationCountry;
+ log.debug("Building eIDAS identification from: [identValue]+" + bPKType);
+ bPK = calculatebPKwbPK(baseID + "+" + bPKType);
+
+ } else { // if not, sector identification value is already calculated by BKU
+ log.debug("eIDAS eIdentifier already provided by BKU");
+ bPK = baseID;
+ }
+
+ if ((StringUtils.isEmpty(bPK) ||
+ StringUtils.isEmpty(sourceCountry) ||
+ StringUtils.isEmpty(destinationCountry))) {
+ throw new EAAFBuilderException("builder.00",
+ new Object[]{"eIDAS-ID", "Unvollständige Parameterangaben: identificationValue=" +
+ bPK + ", Zielland=" + destinationCountry + ", Ursprungsland=" + sourceCountry}
+ ,"eIDAS-ID: Unvollständige Parameterangaben: identificationValue=" +
+ bPK + ", Zielland=" + destinationCountry + ", Ursprungsland=" + sourceCountry);
+ }
+
+ log.debug("Building eIDAS identification from: " + sourceCountry+"/"+destinationCountry+"/" + "[identValue]");
+ String eIdentifier = sourceCountry + "/" + destinationCountry + "/" + bPK;
+
+ return Pair.newInstance(eIdentifier, bPKType);
+ }
+
+ public static String encryptBPK(String bpk, String target, PublicKey publicKey) throws EAAFBuilderException {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+ if (target.startsWith(EAAFConstants.URN_PREFIX_CDID + "+"))
+ target = target.substring((EAAFConstants.URN_PREFIX_CDID + "+").length());
+
+ String input = "V1::urn:publicid:gv.at:cdid+" + target + "::"
+ + bpk + "::"
+ + sdf.format(new Date());
+ System.out.println(input);
+ byte[] result;
+ try {
+ byte[] inputBytes = input.getBytes("ISO-8859-1");
+ result = encrypt(inputBytes, publicKey);
+ return new String(Base64Utils.encode(result), "ISO-8859-1").replaceAll("\r\n", "");
+ //return new String(Base64Utils.encode(result, "ISO-8859-1")).replaceAll("\r\n", "");
+
+
+ } catch (Exception e) {
+ throw new EAAFBuilderException("bPK encryption FAILED", null,
+ e.getMessage(), e);
+
+ }
+ }
+
+ public static String decryptBPK(String encryptedBpk, String target, PrivateKey privateKey) throws EAAFBuilderException {
+ String decryptedString;
+ try {
+ //byte[] encryptedBytes = Base64Utils.decode(encryptedBpk, false, "ISO-8859-1");
+ byte[] encryptedBytes = Base64Utils.decode(encryptedBpk.getBytes("ISO-8859-1"));
+ byte[] decryptedBytes = decrypt(encryptedBytes, privateKey);
+ decryptedString = new String(decryptedBytes, "ISO-8859-1");
+
+ } catch (Exception e) {
+ throw new EAAFBuilderException("bPK decryption FAILED", null,
+ e.getMessage(), e);
+
+ }
+
+ String tmp = decryptedString.substring(decryptedString.indexOf('+') + 1);
+ String sector = tmp.substring(0, tmp.indexOf("::"));
+ tmp = tmp.substring(tmp.indexOf("::") + 2);
+ String bPK = tmp.substring(0, tmp.indexOf("::"));
+
+ if (target.startsWith(EAAFConstants.URN_PREFIX_CDID + "+"))
+ target = target.substring((EAAFConstants.URN_PREFIX_CDID + "+").length());
+
+ if (target.equals(sector))
+ return bPK;
+
+ else {
+ log.error("Decrypted bPK does not match to request bPK target.");
+ return null;
+ }
+ }
+
+ private String calculatebPKwbPK(String basisbegriff) throws EAAFBuilderException {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] hash = md.digest(basisbegriff.getBytes("ISO-8859-1"));
+ String hashBase64 = new String(Base64Utils.encode(hash), "ISO-8859-1").replaceAll("\r\n", ""); //Base64Utils.encode(hash);
+ return hashBase64;
+
+ } catch (Exception ex) {
+ throw new EAAFBuilderException("builder.00", new Object[]{"bPK/wbPK", ex.toString()},
+ ex.getMessage(), ex);
+
+ }
+
+ }
+
+ private static byte[] encrypt(byte[] inputBytes, PublicKey publicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ byte[] result;
+ Cipher cipher = null;
+ try {
+ cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle
+
+ } catch(NoSuchAlgorithmException e) {
+ cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider
+ }
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ result = cipher.doFinal(inputBytes);
+
+ return result;
+ }
+
+ private static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey)
+ throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
+ byte[] result;
+ Cipher cipher = null;
+ try {
+ cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle
+
+ } catch(NoSuchAlgorithmException e) {
+ cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider
+
+ }
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ result = cipher.doFinal(encryptedBytes);
+ return result;
+
+ }
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/AuthProcessDataWrapper.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/AuthProcessDataWrapper.java
new file mode 100644
index 00000000..3fca5300
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/AuthProcessDataWrapper.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.auth.data;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.idp.EAAFAuthProcessDataConstants;
+import at.gv.egiz.eaaf.core.api.idp.auth.data.IAuthProcessDataContainer;
+import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink;
+import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException;
+
+public class AuthProcessDataWrapper implements IAuthProcessDataContainer, EAAFAuthProcessDataConstants {
+ private static final Logger log = LoggerFactory.getLogger(AuthProcessDataWrapper.class);
+
+ protected Map<String, Object> authProcessData;
+
+ public AuthProcessDataWrapper(Map<String, Object> authProcessData) {
+ this.authProcessData = authProcessData;
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getIssueInstant()
+ */
+ @Override
+ public String getIssueInstant() {
+ return wrapStringObject(VALUE_ISSUEINSTANT, null, String.class);
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setIssueInstant(java.lang.String)
+ */
+ @Override
+ public void setIssueInstant(String issueInstant) {
+ authProcessData.put(VALUE_ISSUEINSTANT, issueInstant);
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#isAuthenticated()
+ */
+ @Override
+ public boolean isAuthenticated() {
+ return wrapStringObject(FLAG_IS_AUTHENTICATED, false, Boolean.class);
+
+ }
+
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setAuthenticated(boolean)
+ */
+ @Override
+ public void setAuthenticated(boolean authenticated) {
+ authProcessData.put(FLAG_IS_AUTHENTICATED, authenticated);
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getIdentityLink()
+ */
+ @Override
+ public IIdentityLink getIdentityLink() {
+ return wrapStringObject(VALUE_IDENTITYLINK, null, IIdentityLink.class);
+
+ }
+
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setIdentityLink(at.gv.egovernment.moa.id.auth.data.IdentityLink)
+ */
+ @Override
+ public void setIdentityLink(IIdentityLink identityLink) {
+ authProcessData.put(VALUE_IDENTITYLINK, identityLink);
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#isMandateUsed()
+ */
+ @Override
+ public boolean isMandateUsed() {
+ return wrapStringObject(FLAG_USE_MANDATE, false, Boolean.class);
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setUseMandates(boolean)
+ */
+ @Override
+ public void setUseMandates(boolean useMandates) {
+ authProcessData.put(FLAG_USE_MANDATE, useMandates);
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getQAALevel()
+ */
+ @Override
+ public String getQAALevel() {
+ return wrapStringObject(VALUE_QAALEVEL, null, String.class);
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setQAALevel(java.lang.String)
+ */
+ @Override
+ public void setQAALevel(String qAALevel) {
+ authProcessData.put(VALUE_QAALEVEL, qAALevel);
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#isForeigner()
+ */
+ @Override
+ public boolean isForeigner() {
+ return wrapStringObject(FLAG_IS_FOREIGNER, false, Boolean.class);
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setForeigner(boolean)
+ */
+ @Override
+ public void setForeigner(boolean isForeigner) {
+ authProcessData.put(FLAG_IS_FOREIGNER, isForeigner);
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#isOW()
+ */
+ @Override
+ public boolean isOW() {
+ return wrapStringObject(FLAG_IS_ORGANWALTER, false, Boolean.class);
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setOW(boolean)
+ */
+ @Override
+ public void setOW(boolean isOW) {
+ authProcessData.put(FLAG_IS_ORGANWALTER, isOW);
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getSessionCreated()
+ */
+ @Override
+ public Date getSessionCreated() {
+ return wrapStringObject(EAAFConstants.AUTH_DATA_CREATED, null, Date.class);
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getGenericSessionDataStorage()
+ */
+ @Override
+ public Map<String, Object> getGenericSessionDataStorage() {
+ Map<String, Object> result = new HashMap<String, Object>();
+ for (String el : authProcessData.keySet()) {
+ if (el.startsWith(GENERIC_PREFIX))
+ result.put(el.substring(GENERIC_PREFIX.length()), authProcessData.get(el));
+
+ }
+
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getGenericDataFromSession(java.lang.String)
+ */
+ @Override
+ public Object getGenericDataFromSession(String key) {
+ return authProcessData.get(GENERIC_PREFIX + key);
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getGenericDataFromSession(java.lang.String, java.lang.Class)
+ */
+ @Override
+ public <T> T getGenericDataFromSession(String key, Class<T> clazz) {
+ return wrapStringObject(GENERIC_PREFIX + key, null, clazz);
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setGenericDataToSession(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public void setGenericDataToSession(String key, Object object) throws EAAFStorageException {
+ authProcessData.put(GENERIC_PREFIX + key, object);
+
+ }
+
+ protected <T> T wrapStringObject(String key, Object defaultValue, Class<T> clazz) {
+ if (StringUtils.isNotEmpty(key)) {
+ Object obj = authProcessData.get(key);
+ if (obj != null && clazz.isInstance(obj))
+ return (T) obj;
+ }
+
+ if (defaultValue == null)
+ return null;
+
+ else if (clazz.isInstance(defaultValue))
+ return (T)defaultValue;
+
+ else {
+ log.error("DefaultValue: " + defaultValue.getClass().getName() + " is not of Type:" + clazz.getName());
+ throw new IllegalStateException("DefaultValue: " + defaultValue.getClass().getName() + " is not of Type:" + clazz.getName());
+
+ }
+ }
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/IdentityLink.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/IdentityLink.java
new file mode 100644
index 00000000..becd630e
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/IdentityLink.java
@@ -0,0 +1,312 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+/*
+ * Copyright 2003 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.egiz.eaaf.core.impl.idp.auth.data;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.PublicKey;
+
+import javax.xml.transform.TransformerException;
+
+import org.w3c.dom.Element;
+
+import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink;
+import at.gv.egiz.eaaf.core.impl.utils.DOMUtils;
+
+
+/**
+ * Data contained in an identity link issued by BMI, relevant to the MOA ID component.
+ * <br><code>"IdentityLink"</code> is the translation of <code>"Personenbindung"</code>.
+ *
+ * @author Paul Ivancsics
+ * @version $Id$
+ */
+public class IdentityLink implements Serializable, IIdentityLink{
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * <code>"identificationValue"</code> is the translation of <code>"Stammzahl"</code>.
+ */
+ private String identificationValue;
+ /**
+ * <code>"identificationType"</code> type of the identificationValue in the IdentityLink.
+ */
+ private String identificationType;
+ /**
+ * first name
+ */
+ private String givenName;
+ /**
+ * family name
+ */
+ private String familyName;
+
+ /**
+ * The name as (givenName + familyName)
+ */
+ private String name;
+ /**
+ * date of birth
+ */
+ private String dateOfBirth;
+ /**
+ * the original saml:Assertion-Element
+ */
+ private Element samlAssertion;
+ /**
+ * the serializes saml:Assertion
+ */
+ private String serializedSamlAssertion;
+ /**
+ * Element /saml:Assertion/saml:AttributeStatement/saml:Subject/saml:SubjectConfirmation/saml:SubjectConfirmationData/pr:Person
+ */
+ private Element prPerson;
+ /**
+ * we need for each dsig:Reference Element all
+ * transformation elements
+ */
+ private Element[] dsigReferenceTransforms;
+
+ /**
+ * The issuing time of the identity link SAML assertion.
+ */
+ private String issueInstant;
+
+ /**
+ * we need all public keys stored in
+ * the identity link
+ */
+ private PublicKey[] publicKey;
+
+ /**
+ * Constructor for IdentityLink
+ */
+ public IdentityLink() {
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getDateOfBirth()
+ */
+ @Override
+public String getDateOfBirth() {
+ return dateOfBirth;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getFamilyName()
+ */
+ @Override
+public String getFamilyName() {
+ return familyName;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getGivenName()
+ */
+ @Override
+public String getGivenName() {
+ return givenName;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getName()
+ */
+ @Override
+public String getName() {
+ if (name == null) {
+ name = givenName + " " + familyName;
+ }
+ return name;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getIdentificationValue()
+ */
+ @Override
+public String getIdentificationValue() {
+ return identificationValue;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getIdentificationType()
+ */
+ @Override
+ public String getIdentificationType() {
+ return identificationType;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setDateOfBirth(java.lang.String)
+ */
+ @Override
+public void setDateOfBirth(String dateOfBirth) {
+ this.dateOfBirth = dateOfBirth;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setFamilyName(java.lang.String)
+ */
+ @Override
+public void setFamilyName(String familyName) {
+ this.familyName = familyName;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setGivenName(java.lang.String)
+ */
+ @Override
+public void setGivenName(String givenName) {
+ this.givenName = givenName;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setIdentificationValue(java.lang.String)
+ */
+ @Override
+public void setIdentificationValue(String identificationValue) {
+ this.identificationValue = identificationValue;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setIdentificationType(java.lang.String)
+ */
+ @Override
+ public void setIdentificationType(String identificationType) {
+ this.identificationType = identificationType;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getSamlAssertion()
+ */
+ @Override
+public Element getSamlAssertion() {
+ return samlAssertion;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getSerializedSamlAssertion()
+ */
+ @Override
+public String getSerializedSamlAssertion() {
+ return serializedSamlAssertion;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setSamlAssertion(org.w3c.dom.Element)
+ */
+ @Override
+public void setSamlAssertion(Element samlAssertion) throws TransformerException, IOException {
+ this.samlAssertion = samlAssertion;
+ this.serializedSamlAssertion = DOMUtils.serializeNode(samlAssertion);
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getDsigReferenceTransforms()
+ */
+ @Override
+public Element[] getDsigReferenceTransforms() {
+ return dsigReferenceTransforms;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setDsigReferenceTransforms(org.w3c.dom.Element[])
+ */
+ @Override
+public void setDsigReferenceTransforms(Element[] dsigReferenceTransforms) {
+ this.dsigReferenceTransforms = dsigReferenceTransforms;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getPublicKey()
+ */
+ @Override
+public PublicKey[] getPublicKey() {
+ return publicKey;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setPublicKey(java.security.PublicKey[])
+ */
+ @Override
+public void setPublicKey(PublicKey[] publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getPrPerson()
+ */
+ @Override
+public Element getPrPerson() {
+ return prPerson;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setPrPerson(org.w3c.dom.Element)
+ */
+ @Override
+public void setPrPerson(Element prPerson) {
+ this.prPerson = prPerson;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getIssueInstant()
+ */
+ @Override
+public String getIssueInstant() {
+ return issueInstant;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setIssueInstant(java.lang.String)
+ */
+ @Override
+public void setIssueInstant(String issueInstant) {
+ this.issueInstant = issueInstant;
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/SimpleIdentityLinkAssertionParser.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/SimpleIdentityLinkAssertionParser.java
new file mode 100644
index 00000000..7fb5e642
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/SimpleIdentityLinkAssertionParser.java
@@ -0,0 +1,326 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+/*
+ * Copyright 2003 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.egiz.eaaf.core.impl.idp.auth.data;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.util.Base64Utils;
+import org.w3c.dom.Element;
+import org.w3c.dom.traversal.NodeIterator;
+
+import at.gv.egiz.eaaf.core.api.data.XMLNamespaceConstants;
+import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink;
+import at.gv.egiz.eaaf.core.exceptions.EAAFParserException;
+import at.gv.egiz.eaaf.core.impl.utils.DOMUtils;
+import at.gv.egiz.eaaf.core.impl.utils.XPathUtils;
+
+/**
+ * Parses MDS from an identity link <code>&lt;saml:Assertion&gt;</code>
+ * <br>
+ * <b>This IDL parser extract NO key information!</b>
+
+ */
+public class SimpleIdentityLinkAssertionParser {
+
+ //
+ // XPath namespace prefix shortcuts
+ //
+
+ /** Xpath prefix for reaching PersonData Namespaces */
+ private static final String PDATA = XMLNamespaceConstants.PD_PREFIX + ":";
+ /** Xpath prefix for reaching SAML Namespaces */
+ private static final String SAML = XMLNamespaceConstants.SAML_PREFIX + ":";
+ /** Xpath prefix for reaching XML-DSIG Namespaces */
+ private static final String DSIG = XMLNamespaceConstants.DSIG_PREFIX + ":";
+ /** Xpath prefix for reaching ECDS Namespaces */
+ private static final String ECDSA = XMLNamespaceConstants.ECDSA_PREFIX + ":";
+ /** Xpath expression to the root element */
+ private static final String ROOT = "";
+ /** Xpath expression to the SAMLSubjectConfirmationData element */
+ private static final String SAML_SUBJECT_CONFIRMATION_DATA_XPATH =
+ ROOT
+ + SAML
+ + "AttributeStatement/"
+ + SAML
+ + "Subject/"
+ + SAML
+ + "SubjectConfirmation/"
+ + SAML
+ + "SubjectConfirmationData";
+ /** Xpath expression to the PersonData element */
+ private static final String PERSON_XPATH =
+ SAML_SUBJECT_CONFIRMATION_DATA_XPATH
+ + "/"
+ + PDATA
+ + "Person";
+ /** Xpath expression to the PersonData GivenName element */
+ public static final String PERSON_GIVEN_NAME_XPATH =
+ PERSON_XPATH
+ + "/"
+ + PDATA
+ + "Name/"
+ + PDATA
+ + "GivenName";
+ /** Xpath expression to the PersonData FamilyName element */
+ public static final String PERSON_FAMILY_NAME_XPATH =
+ PERSON_XPATH
+ + "/"
+ + PDATA
+ + "Name/"
+ + PDATA
+ + "FamilyName";
+ /** Xpath expression to the PersonData DateOfBirth element */
+ public static final String PERSON_DATE_OF_BIRTH_XPATH =
+ PERSON_XPATH
+ + "/"
+ + PDATA
+ + "DateOfBirth";
+ /** Xpath expression to the Identification element */
+ private static final String PERSON_IDENT_XPATH =
+ PERSON_XPATH
+ + "/"
+ + PDATA
+ + "Identification";
+
+ /** Xpath expression to the Identification Value element */
+ public static final String PERSON_IDENT_VALUE_XPATH =
+ PERSON_XPATH
+ + "/"
+ + PDATA
+ + "Identification/"
+ + PDATA
+ + "Value";
+
+ /** Xpath expression to the Identification Value element */
+ public static final String PERSON_IDENT_TYPE_XPATH =
+ PERSON_XPATH
+ + "/"
+ + PDATA
+ + "Identification/"
+ + PDATA
+ + "Type";
+
+ /** Xpath expression to the RSAKeyValue element */
+ private static final String RSA_KEY_VALUE_XPATH =
+ ROOT
+ + SAML
+ + "AttributeStatement/"
+ + SAML
+ + "Attribute/"
+ + SAML
+ + "AttributeValue/"
+ + DSIG
+ + "RSAKeyValue";
+
+ /** Xpath expression to the ECKeyValue element */
+ private static final String ECDSA_KEY_VALUE_XPATH =
+ ROOT
+ + SAML
+ + "AttributeStatement/"
+ + SAML
+ + "Attribute/"
+ + SAML
+ + "AttributeValue/"
+ + ECDSA
+ + "ECDSAKeyValue";
+
+
+ /** Xpath expression to the RSA Modulus element */
+ private static final String RSA_KEY_MODULUS_XPATH = DSIG + "Modulus";
+ /** Xpath expression to the RSA Exponent element */
+ private static final String RSA_KEY_EXPONENT_XPATH = DSIG + "Exponent";
+ /** Xpath expression to the DSIG X509Certificate element */
+ private static final String DSIG_CERTIFICATES_XPATH =
+ ROOT
+ + DSIG
+ + "Signature/"
+ + DSIG
+ + "KeyInfo/"
+ + DSIG
+ + "X509Data/"
+ + DSIG
+ + "X509Certificate";
+ /** Xpath expression to the DSIG Transforms element */
+ private static final String DSIG_REFERENCE_TRANSFORMATION_XPATH =
+ ROOT
+ + DSIG
+ + "Signature/"
+ + DSIG
+ + "SignedInfo/"
+ + DSIG
+ + "Reference/"
+ + DSIG
+ + "Transforms";
+
+ /** The IssueInstant attribute of the SAML assertion */
+ private static final String ISSUE_INSTANT_ATTR = "IssueInstant";
+
+ /**This is the root element of the XML-Document provided by the Security Layer Card*/
+ private Element assertionElem;
+
+ /**
+ * Constructor for <code>IdentityLinkAssertionParser</code>.
+ * A DOM-representation of the incoming String will be created
+ * @param xmlAssertion <code>&lt;saml:Assertion&gt;</code> as String
+ * @throws EAAFParserException on any parsing error
+ */
+ public SimpleIdentityLinkAssertionParser(String xmlAssertion) throws EAAFParserException {
+ try {
+ InputStream s = new ByteArrayInputStream(xmlAssertion.getBytes("UTF-8"));
+ assertionElem = DOMUtils.parseXmlValidating(s);
+
+ }
+ catch (Throwable t) {
+ throw new EAAFParserException("parser.01", new Object[] { t.toString()},
+ t.getMessage(), t);
+
+ }
+ }
+
+ /**
+ * Sets the <@link assertionElem>.
+ * @param xmlAssertion the assertion element
+ * @throws EAAFParserException on any parsing error
+ */
+ public SimpleIdentityLinkAssertionParser(Element xmlAssertion) throws EAAFParserException {
+ assertionElem = xmlAssertion;
+ }
+
+ /**
+ * Constructor for <code>IdentityLinkAssertionParser</code>.
+ * A DOM-representation of the incoming Inputstream will be created
+ * @param xmlAssertion <code>&lt;saml:Assertion&gt;</code> as InputStream
+ * @throws EAAFParserException on any parsing error
+ */
+ public SimpleIdentityLinkAssertionParser(InputStream xmlAssertion) throws EAAFParserException {
+ try {
+ assertionElem = DOMUtils.parseXmlValidating(xmlAssertion);
+
+ }
+ catch (Throwable t) {
+ throw new EAAFParserException("parser.01", new Object[] { t.toString() }, t.getMessage(), t);
+
+ }
+ }
+
+ /**
+ * Parses the identity link from the <code>&lt;saml:Assertion&gt;</code>
+ * @return Identity link
+ * @throws EAAFParserException on any parsing error
+ */
+
+ public IIdentityLink parseIdentityLink() throws EAAFParserException {
+ IIdentityLink identityLink;
+ try {
+ identityLink = new IdentityLink();
+ identityLink.setSamlAssertion(assertionElem);
+ identityLink.setIssueInstant(assertionElem.getAttribute(ISSUE_INSTANT_ATTR));
+ identityLink.setPrPerson((Element)
+ XPathUtils.selectSingleNode(assertionElem, PERSON_XPATH));
+ identityLink.setIdentificationValue(
+ XPathUtils.getElementValue(assertionElem, PERSON_IDENT_VALUE_XPATH, ""));
+ identityLink.setIdentificationType(
+ XPathUtils.getElementValue(assertionElem, PERSON_IDENT_TYPE_XPATH, ""));
+
+ String givenname = XPathUtils.getElementValue(assertionElem, PERSON_GIVEN_NAME_XPATH, "");
+ String familyname = XPathUtils.getElementValue(assertionElem, PERSON_FAMILY_NAME_XPATH, "");
+
+ // replace ' in name with &#39;
+ givenname = givenname.replaceAll("'", "&#39;");
+ familyname = familyname.replaceAll("'", "&#39;");
+
+ identityLink.setGivenName(givenname);
+ identityLink.setFamilyName(familyname);
+ identityLink.setDateOfBirth(
+ XPathUtils.getElementValue(assertionElem, PERSON_DATE_OF_BIRTH_XPATH, ""));
+ NodeIterator dsigRefTransforms =
+ XPathUtils.selectNodeIterator(assertionElem, DSIG_REFERENCE_TRANSFORMATION_XPATH);
+ List transElems = new ArrayList();
+ Element transformsElem;
+ while ((transformsElem = (Element) dsigRefTransforms.nextNode()) != null) {
+ transElems.add(transformsElem);
+ }
+ Element[] result = new Element[transElems.size()];
+ transElems.toArray(result);
+ identityLink.setDsigReferenceTransforms(result);
+
+ //identityLink.setPublicKey(getPublicKeys());
+
+ }
+ catch (Throwable t) {
+ throw new EAAFParserException("parser.01", new Object[] { t.toString() },
+ t.getMessage(), t);
+ }
+
+ return identityLink;
+ }
+
+ /**
+ * Parses a string array of decoded base64 certificates from
+ * the <code>&lt;InfoboxReadResponse&gt;</code> found in the dsig-signature
+ * @return String[] with raw-certificates from the dsig-signature keyinfo
+ * @throws Exception
+ */
+ public String[] getCertificates() throws Exception {
+ List certs = new ArrayList();
+ NodeIterator rsaIter =
+ XPathUtils.selectNodeIterator(assertionElem, DSIG_CERTIFICATES_XPATH);
+ Element certElem;
+ while ((certElem = (Element) rsaIter.nextNode()) != null) {
+ String content = DOMUtils.getText(certElem);
+ certs.add(new String(Base64Utils.decodeFromString(content)));
+
+ }
+ String[] result = new String[certs.size()];
+ certs.toArray(result);
+ return result;
+
+ }
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java
new file mode 100644
index 00000000..a421ff67
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java
@@ -0,0 +1,220 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.auth.modules;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileItemFactory;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.IRequestStorage;
+import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger;
+import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;
+import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractAuthProtocolModulController;
+import at.gv.egiz.eaaf.core.impl.idp.process.springweb.AbstractTask;
+import at.gv.egiz.eaaf.core.impl.utils.DataURLBuilder;
+
+/**
+ * Task based counterpart to {@link AuthServlet}, providing the same utility methods (error handling, parameter parsing
+ * etc.).</p> The code has been taken from {@link AuthServlet}.
+ */
+public abstract class AbstractAuthServletTask extends AbstractTask {
+ private static final Logger log = LoggerFactory.getLogger(AbstractAuthServletTask.class);
+
+ @Autowired(required=true) protected IRequestStorage requestStoreage;
+ @Autowired(required=true) protected IConfiguration authConfig;
+
+ @Autowired protected IRevisionLogger revisionsLogger;
+
+ protected static final String ERROR_CODE_PARAM = "errorid";
+
+ protected IRequest pendingReq = null;
+
+ public abstract void execute(ExecutionContext executionContext, HttpServletRequest request,
+ HttpServletResponse response) throws TaskExecutionException;
+
+
+ protected final IRequest internalExecute(IRequest pendingReq, ExecutionContext executionContext, HttpServletRequest request,
+ HttpServletResponse response) throws TaskExecutionException {
+ //set pending-request object
+ this.pendingReq = pendingReq;
+
+ //execute task specific action
+ execute(executionContext, request, response);
+
+ //return pending-request object
+ return this.pendingReq;
+ }
+
+ /**
+ * Redirect the authentication process to protocol specific finalization endpoint.
+ *
+ * @param pendingReq Actually processed protocol specific authentication request
+ * @param httpResp
+ */
+ protected void performRedirectToProtocolFinialization(IRequest pendingReq, HttpServletResponse httpResp) {
+ performRedirectToItself(pendingReq, httpResp, AbstractAuthProtocolModulController.ENDPOINT_FINALIZEPROTOCOL);
+
+ }
+
+ /**
+ * Redirect the authentication process to IDP itself
+ *
+ * @param pendingReq Actually processed protocol specific authentication request
+ * @param httpResp
+ * @param idpEndPoint Servlet EndPoint that should receive the redirect
+ */
+ protected void performRedirectToItself(IRequest pendingReq, HttpServletResponse httpResp, String idpEndPoint) {
+ String redirectURL = new DataURLBuilder().buildDataURL(pendingReq.getAuthURL(),
+ idpEndPoint, pendingReq.getPendingRequestId());
+
+ httpResp.setContentType("text/html");
+ httpResp.setStatus(302);
+ httpResp.addHeader("Location", redirectURL);
+ log.debug("REDIRECT TO: " + redirectURL);
+
+ }
+
+
+ /**
+ * Parses the request input stream for parameters, assuming parameters are
+ * encoded UTF-8 (no standard exists how browsers should encode them).
+ *
+ * @param req
+ * servlet request
+ *
+ * @return mapping parameter name -> value
+ *
+ * @throws IOException
+ * if parsing request parameters fails.
+ *
+ * @throws FileUploadException
+ * if parsing request parameters fails.
+ */
+ protected Map<String, String> getParameters(HttpServletRequest req) throws IOException,
+ FileUploadException {
+
+ Map<String, String> parameters = new HashMap<String, String>();
+
+ if (ServletFileUpload.isMultipartContent(req)) {
+ // request is encoded as mulitpart/form-data
+ FileItemFactory factory = new DiskFileItemFactory();
+ ServletFileUpload upload = null;
+ upload = new ServletFileUpload(factory);
+ List items = null;
+ items = upload.parseRequest(req);
+ for (int i = 0; i < items.size(); i++) {
+ FileItem item = (FileItem) items.get(i);
+ if (item.isFormField()) {
+ // Process only form fields - no file upload items
+ parameters.put(item.getFieldName(), item.getString("UTF-8"));
+
+ //log requests on trace
+ if (log.isTraceEnabled()) {
+ String logString = item.getString("UTF-8");
+
+ // TODO use RegExp
+ String startS = "<pr:Identification><pr:Value>";
+ String endS = "</pr:Value><pr:Type>urn:publicid:gv.at:baseid</pr:Type>";
+ String logWithMaskedBaseid = logString;
+ int start = logString.indexOf(startS);
+ if (start > -1) {
+ int end = logString.indexOf(endS);
+ if (end > -1) {
+ logWithMaskedBaseid = logString.substring(0, start);
+ logWithMaskedBaseid += startS;
+ logWithMaskedBaseid += "xxxxxxxxxxxxxxxxxxxxxxxx";
+ logWithMaskedBaseid += logString.substring(end,
+ logString.length());
+ }
+ }
+
+ log.debug("Processed multipart/form-data request parameter: \nName: "
+ + item.getFieldName()
+ + "\nValue: "
+ + logWithMaskedBaseid);
+ }
+
+ }
+ }
+ }
+
+ else {
+ Iterator<Entry<String, String[]>> requestParamIt = req.getParameterMap().entrySet().iterator();
+ while (requestParamIt.hasNext()) {
+ Entry<String, String[]> entry = requestParamIt.next();
+ String key = entry.getKey();
+ String[] values = entry.getValue();
+ // take the last value from the value array since the legacy code above also does it this way
+ parameters.put(key, ArrayUtils.isEmpty(values) ? null : values[values.length-1]);
+ }
+
+ }
+
+ return parameters;
+ }
+
+ /**
+ * Reads bytes up to a delimiter, consuming the delimiter.
+ *
+ * @param in
+ * input stream
+ * @param delimiter
+ * delimiter character
+ * @return String constructed from the read bytes
+ * @throws IOException
+ */
+ protected String readBytesUpTo(InputStream in, char delimiter)
+ throws IOException {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ boolean done = false;
+ int b;
+ while (!done && (b = in.read()) >= 0) {
+ if (b == delimiter)
+ done = true;
+ else
+ bout.write(b);
+ }
+ return bout.toString();
+ }
+
+ /**
+ * Adds a parameter to a URL.
+ *
+ * @param url
+ * the URL
+ * @param paramname
+ * parameter name
+ * @param paramvalue
+ * parameter value
+ * @return the URL with parameter added
+ */
+ protected static String addURLParameter(String url, String paramname,
+ String paramvalue) {
+ String param = paramname + "=" + paramvalue;
+ if (url.indexOf("?") < 0)
+ return url + "?" + param;
+ else
+ return url + "&" + param;
+ }
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java
new file mode 100644
index 00000000..cb4a055a
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.auth.modules;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.io.Resource;
+
+import at.gv.egiz.eaaf.core.api.idp.auth.modules.AuthModule;
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.api.idp.process.ProcessEngine;
+import at.gv.egiz.eaaf.core.impl.idp.process.ProcessDefinitionParserException;
+
+/**
+ * This class handles registering modules. The modules are detected either with
+ * the ServiceLoader mechanism or via Spring. All detected modules are ranked
+ * according to their priority.
+ */
+public class ModuleRegistration {
+
+ private static ModuleRegistration instance = new ModuleRegistration();
+
+ private List<AuthModule> priorizedModules = new ArrayList<>();
+
+ @Autowired
+ private ApplicationContext ctx;
+
+ @Autowired
+ private ProcessEngine processEngine;
+
+ private Logger log = LoggerFactory.getLogger(getClass());
+
+ public static ModuleRegistration getInstance() {
+ return instance;
+ }
+
+ private ModuleRegistration() {
+ }
+
+ @PostConstruct
+ private void init() {
+ // load modules via the ServiceLoader
+ initServiceLoaderModules();
+
+ // load modules via Spring
+ initSpringModules();
+
+ // order modules according to their priority
+ sortModules();
+ }
+
+ /**
+ * Discovers modules which use the ServiceLoader mechanism.
+ */
+ private void initServiceLoaderModules() {
+ log.info("Looking for auth modules.");
+ ServiceLoader<AuthModule> loader = ServiceLoader.load(AuthModule.class);
+ Iterator<AuthModule> modules = loader.iterator();
+ while (modules.hasNext()) {
+ AuthModule module = modules.next();
+ log.info("Detected module {}", module.getClass().getName());
+ registerModuleProcessDefinitions(module);
+ priorizedModules.add(module);
+ }
+ }
+
+ /**
+ * Discovers modules which use Spring.
+ */
+ private void initSpringModules() {
+ log.debug("Discovering Spring modules.");
+ Map<String, AuthModule> modules = ctx.getBeansOfType(AuthModule.class);
+ for (AuthModule module : modules.values()) {
+ registerModuleProcessDefinitions(module);
+ priorizedModules.add(module);
+ }
+ }
+
+ /**
+ * Registers the resource uris for the module.
+ *
+ * @param module
+ * the module.
+ */
+ private void registerModuleProcessDefinitions(AuthModule module) {
+ for (String uri : module.getProcessDefinitions()) {
+ Resource resource = ctx.getResource(uri);
+ if (resource.isReadable()) {
+ log.info("Registering process definition '{}'.", uri);
+ try (InputStream processDefinitionInputStream = resource.getInputStream()) {
+ processEngine.registerProcessDefinition(processDefinitionInputStream);
+ } catch (IOException e) {
+ log.error("Process definition '{}' could NOT be read.", uri, e);
+ } catch (ProcessDefinitionParserException e) {
+ log.error("Error while parsing process definition '{}'", uri, e);
+ }
+ } else {
+ log.error("Process definition '{}' cannot be read.", uri);
+ }
+ }
+ }
+
+ /**
+ * Order the modules in descending order according to their priority.
+ */
+ private void sortModules() {
+ Collections.sort(priorizedModules, new Comparator<AuthModule>() {
+ @Override
+ public int compare(AuthModule thisAuthModule, AuthModule otherAuthModule) {
+ int thisOrder = thisAuthModule.getPriority();
+ int otherOrder = otherAuthModule.getPriority();
+ return (thisOrder < otherOrder ? 1 : (thisOrder == otherOrder ? 0 : -1));
+ }
+ });
+ }
+
+ /**
+ * Returns the process description id of the first process, in the highest ranked
+ * module, which is able to work with the given execution context.
+ *
+ * @param context
+ * the {@link ExecutionContext}.
+ * @return the process id or {@code null}
+ */
+ public String selectProcess(ExecutionContext context) {
+ for (AuthModule module : priorizedModules) {
+ String id = module.selectProcess(context);
+ if (StringUtils.isNotEmpty(id)) {
+ log.debug("Process with id '{}' selected, for context '{}'.", id, context);
+ return id;
+ }
+ }
+ log.info("No process is able to handle context '{}'.", context);
+ return null;
+ }
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/BPKAttributeBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/BPKAttributeBuilder.java
new file mode 100644
index 00000000..575f2beb
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/BPKAttributeBuilder.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+import at.gv.egiz.eaaf.core.exceptions.UnavailableAttributeException;
+
+public class BPKAttributeBuilder implements IPVPAttributeBuilder {
+
+ private static final Logger log = LoggerFactory.getLogger(BPKAttributeBuilder.class);
+
+ public String getName() {
+ return BPK_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+ String bpk = authData.getBPK();
+ String type = authData.getBPKType();
+
+ if (StringUtils.isEmpty(bpk))
+ throw new UnavailableAttributeException(BPK_NAME);
+
+ if (type.startsWith(EAAFConstants.URN_PREFIX_WBPK))
+ type = type.substring((EAAFConstants.URN_PREFIX_WBPK).length());
+
+ else if (type.startsWith(EAAFConstants.URN_PREFIX_CDID))
+ type = type.substring((EAAFConstants.URN_PREFIX_CDID).length());
+
+ else if (type.startsWith(EAAFConstants.URN_PREFIX_EIDAS))
+ type = type.substring((EAAFConstants.URN_PREFIX_EIDAS).length());
+
+ if (bpk.length() > BPK_MAX_LENGTH) {
+ bpk = bpk.substring(0, BPK_MAX_LENGTH);
+ }
+
+ log.trace("Authenticate user with bPK/wbPK " + bpk + " and Type=" + type);
+
+ return g.buildStringAttribute(BPK_FRIENDLY_NAME, BPK_NAME, type + ":" + bpk);
+ }
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(BPK_FRIENDLY_NAME, BPK_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/BirthdateAttributeBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/BirthdateAttributeBuilder.java
new file mode 100644
index 00000000..cac7e3bf
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/BirthdateAttributeBuilder.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+
+public class BirthdateAttributeBuilder implements IPVPAttributeBuilder {
+
+ public String getName() {
+ return BIRTHDATE_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+
+ if (authData.getDateOfBirth() != null) {
+ DateFormat pvpDateFormat = new SimpleDateFormat(BIRTHDATE_FORMAT_PATTERN);
+ String dateString = pvpDateFormat.format(authData.getDateOfBirth());
+
+ return g.buildStringAttribute(BIRTHDATE_FRIENDLY_NAME, BIRTHDATE_NAME, dateString);
+
+ } else {
+ //build empty attribute if no Birthday date is found (STORK2)
+ return g.buildEmptyAttribute(BIRTHDATE_FRIENDLY_NAME, BIRTHDATE_NAME);
+
+ }
+ }
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(BIRTHDATE_FRIENDLY_NAME, BIRTHDATE_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDIdentityLinkBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDIdentityLinkBuilder.java
new file mode 100644
index 00000000..f55353d2
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDIdentityLinkBuilder.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+import java.io.IOException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.Base64Utils;
+
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+import at.gv.egiz.eaaf.core.exceptions.UnavailableAttributeException;
+
+
+
+public class EIDIdentityLinkBuilder implements IPVPAttributeBuilder {
+ private static final Logger log = LoggerFactory.getLogger(EIDIdentityLinkBuilder.class);
+
+
+ public String getName() {
+ return EID_IDENTITY_LINK_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+ try {
+ String ilAssertion = null;
+ if (authData.getIdentityLink() == null)
+ throw new UnavailableAttributeException(EID_IDENTITY_LINK_NAME);
+
+ ilAssertion = authData.getIdentityLink().getSerializedSamlAssertion();
+
+ return g.buildStringAttribute(EID_IDENTITY_LINK_FRIENDLY_NAME,
+ EID_IDENTITY_LINK_NAME, Base64Utils.encodeToString(ilAssertion.getBytes("UTF-8")));
+
+
+ } catch (IOException e) {
+ log.warn("IdentityLink serialization error.", e);
+ return g.buildEmptyAttribute(EID_IDENTITY_LINK_FRIENDLY_NAME,
+ EID_IDENTITY_LINK_NAME);
+ }
+
+ }
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(EID_IDENTITY_LINK_FRIENDLY_NAME,
+ EID_IDENTITY_LINK_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDIssuingNationAttributeBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDIssuingNationAttributeBuilder.java
new file mode 100644
index 00000000..9a038aa2
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDIssuingNationAttributeBuilder.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+import org.apache.commons.lang3.StringUtils;
+
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+
+public class EIDIssuingNationAttributeBuilder implements IPVPAttributeBuilder {
+
+ public String getName() {
+ return EID_ISSUING_NATION_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+ String countryCode = authData.getCiticenCountryCode();
+ if (StringUtils.isNotEmpty(countryCode))
+ return g.buildStringAttribute(EID_ISSUING_NATION_FRIENDLY_NAME,
+ EID_ISSUING_NATION_NAME, countryCode);
+
+ else
+ return null;
+ }
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(EID_ISSUING_NATION_FRIENDLY_NAME,
+ EID_ISSUING_NATION_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSectorForIDAttributeBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSectorForIDAttributeBuilder.java
new file mode 100644
index 00000000..c170a124
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSectorForIDAttributeBuilder.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+import org.apache.commons.lang3.StringUtils;
+
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+import at.gv.egiz.eaaf.core.exceptions.UnavailableAttributeException;
+
+public class EIDSectorForIDAttributeBuilder implements IPVPAttributeBuilder {
+
+ public String getName() {
+ return EID_SECTOR_FOR_IDENTIFIER_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+ String bpktype = authData.getBPKType();
+
+ if (StringUtils.isEmpty(authData.getBPKType()))
+ throw new UnavailableAttributeException(EID_SECTOR_FOR_IDENTIFIER_NAME);
+
+ return g.buildStringAttribute(EID_SECTOR_FOR_IDENTIFIER_FRIENDLY_NAME,
+ EID_SECTOR_FOR_IDENTIFIER_NAME, bpktype);
+ }
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(EID_SECTOR_FOR_IDENTIFIER_FRIENDLY_NAME,
+ EID_SECTOR_FOR_IDENTIFIER_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSourcePIN.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSourcePIN.java
new file mode 100644
index 00000000..52654f86
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSourcePIN.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+import org.apache.commons.lang3.StringUtils;
+
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+import at.gv.egiz.eaaf.core.exceptions.AttributePolicyException;
+import at.gv.egiz.eaaf.core.exceptions.UnavailableAttributeException;
+
+public class EIDSourcePIN implements IPVPAttributeBuilder {
+
+ public String getName() {
+ return EID_SOURCE_PIN_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+
+ if (authData.isBaseIDTransferRestrication())
+ throw new AttributePolicyException(EID_SOURCE_PIN_NAME);
+
+ else {
+ if (StringUtils.isNoneEmpty(authData.getIdentificationValue()))
+ throw new UnavailableAttributeException(EID_SOURCE_PIN_NAME);
+
+ return g.buildStringAttribute(EID_SOURCE_PIN_FRIENDLY_NAME, EID_SOURCE_PIN_NAME, authData.getIdentificationValue());
+ }
+ }
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(EID_SOURCE_PIN_FRIENDLY_NAME, EID_SOURCE_PIN_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSourcePINType.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSourcePINType.java
new file mode 100644
index 00000000..ef2d8e82
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSourcePINType.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+import at.gv.egiz.eaaf.core.exceptions.UnavailableAttributeException;
+
+public class EIDSourcePINType implements IPVPAttributeBuilder {
+
+ public String getName() {
+ return EID_SOURCE_PIN_TYPE_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+
+ if (authData.isBaseIDTransferRestrication())
+ throw new UnavailableAttributeException(EID_SOURCE_PIN_TYPE_NAME);
+
+ else {
+ return g.buildStringAttribute(EID_SOURCE_PIN_TYPE_FRIENDLY_NAME, EID_SOURCE_PIN_TYPE_NAME, authData.getIdentificationType());
+ }
+ }
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(EID_SOURCE_PIN_TYPE_FRIENDLY_NAME, EID_SOURCE_PIN_TYPE_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDeIDASQAALevelAttributeBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDeIDASQAALevelAttributeBuilder.java
new file mode 100644
index 00000000..213faeb8
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDeIDASQAALevelAttributeBuilder.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+
+public class EIDeIDASQAALevelAttributeBuilder implements IPVPAttributeBuilder {
+
+ public String getName() {
+ return EID_CITIZEN_EIDAS_QAA_LEVEL_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+
+ return g.buildStringAttribute(EID_CITIZEN_EIDAS_QAA_LEVEL_FRIENDLY_NAME,
+ EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, authData.getEIDASQAALevel());
+ }
+
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(EID_CITIZEN_EIDAS_QAA_LEVEL_FRIENDLY_NAME,
+ EID_CITIZEN_EIDAS_QAA_LEVEL_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/GivenNameAttributeBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/GivenNameAttributeBuilder.java
new file mode 100644
index 00000000..083adb36
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/GivenNameAttributeBuilder.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+
+public class GivenNameAttributeBuilder implements IPVPAttributeBuilder {
+
+ public String getName() {
+ return GIVEN_NAME_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+ return g.buildStringAttribute(GIVEN_NAME_FRIENDLY_NAME, GIVEN_NAME_NAME, authData.getGivenName());
+ }
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(GIVEN_NAME_FRIENDLY_NAME, GIVEN_NAME_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/PVPVersionAttributeBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/PVPVersionAttributeBuilder.java
new file mode 100644
index 00000000..006f9854
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/PVPVersionAttributeBuilder.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+
+public class PVPVersionAttributeBuilder implements IPVPAttributeBuilder {
+
+ public String getName() {
+ return PVP_VERSION_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+ return g.buildStringAttribute(PVP_VERSION_FRIENDLY_NAME, PVP_VERSION_NAME, PVP_VERSION_2_1);
+ }
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(PVP_VERSION_FRIENDLY_NAME, PVP_VERSION_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/PrincipalNameAttributeBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/PrincipalNameAttributeBuilder.java
new file mode 100644
index 00000000..8828a022
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/PrincipalNameAttributeBuilder.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.builder.attributes;
+
+import at.gv.egiz.eaaf.core.api.idp.IAttributeGenerator;
+import at.gv.egiz.eaaf.core.api.idp.IAuthData;
+import at.gv.egiz.eaaf.core.api.idp.IPVPAttributeBuilder;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.AttributeBuilderException;
+
+public class PrincipalNameAttributeBuilder implements IPVPAttributeBuilder {
+
+ public String getName() {
+ return PRINCIPAL_NAME_NAME;
+ }
+
+ public <ATT> ATT build(ISPConfiguration oaParam, IAuthData authData,
+ IAttributeGenerator<ATT> g) throws AttributeBuilderException {
+ return g.buildStringAttribute(PRINCIPAL_NAME_FRIENDLY_NAME, PRINCIPAL_NAME_NAME, authData.getFamilyName());
+ }
+
+ public <ATT> ATT buildEmpty(IAttributeGenerator<ATT> g) {
+ return g.buildEmptyAttribute(PRINCIPAL_NAME_FRIENDLY_NAME, PRINCIPAL_NAME_NAME);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/AbstractConfigurationImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/AbstractConfigurationImpl.java
new file mode 100644
index 00000000..2b868b16
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/AbstractConfigurationImpl.java
@@ -0,0 +1,185 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.conf;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException;
+import at.gv.egiz.eaaf.core.impl.utils.KeyValueUtils;
+
+public abstract class AbstractConfigurationImpl implements IConfiguration {
+ private static final Logger log = LoggerFactory.getLogger(AbstractConfigurationImpl.class);
+
+ private static final String URI_SCHEME_CLASSPATH = "classpath";
+ private static final String URI_SCHEME_FILESYSTEM = "file";
+
+ private final URI internalConfigPath;
+ private final URI configRootDirectory;
+ private final Properties properties;
+
+ public AbstractConfigurationImpl(final String configPath) throws EAAFConfigurationException {
+ InputStream is = null;
+ try {
+ log.debug("Starting EAAFCore initialization process .... ");
+
+ if (StringUtils.isEmpty(configPath)) {
+ log.debug("Primary configuration is empty. Search for backup configuration .... ");
+ internalConfigPath = new URI(getBackupConfigPath());
+
+ } else
+ internalConfigPath = new URI(configPath);
+
+ log.info("Load EAAFCore configuration from " + internalConfigPath);
+
+
+ //extract configuration root directory
+ //TODO: check if it works with classpath
+ File propertiesFile = new File(internalConfigPath);
+ String configDir = propertiesFile.getParent();
+ configRootDirectory = new File(configDir).toURI();
+ log.debug("Set EAAFCore configuration root directory to " + configRootDirectory.toString());
+
+
+ //get input stream from configuration path
+ if (internalConfigPath.getScheme().equals(URI_SCHEME_FILESYSTEM)) {
+ log.trace("Load config from filesystem");
+ is = new FileInputStream(propertiesFile);
+
+ } else if (internalConfigPath.getScheme().equals(URI_SCHEME_CLASSPATH)) {
+ log.trace("Load config from classpath");
+ is = this.getClass().getResourceAsStream(internalConfigPath.toString());
+
+ } else {
+ log.error("Can not load EAAFCore configuration. Unsupported prefix! (Only 'file:' and 'classpath:') ");
+ throw new EAAFConfigurationException("Can not load EAAFCore configuration. Unsupported prefix");
+
+ }
+
+ if (is == null) {
+ log.error("Can NOT load EAAFCore configuration from file " + internalConfigPath.toString());
+ throw new EAAFConfigurationException("Can NOT load EAAFCore configuration from file " + internalConfigPath.toString());
+
+ }
+
+
+ //load EAAF core configuration into properties object
+ properties = new Properties();
+ properties.load(is);
+
+ log.info("EAAFCore configuration loaded");
+
+
+ } catch (URISyntaxException | IOException e) {
+ log.error("Can not parse configuration path " + configPath + " or " + getBackupConfigPath());
+ throw new EAAFConfigurationException("Can not parse configuration path", e);
+
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+
+ } catch (IOException e) {
+ log.warn("Can not close inputstream from configuration loader!");
+
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public String getBasicConfiguration(String key) {
+ if (StringUtils.isNotEmpty(key)) {
+ String value = properties.getProperty(addPrefixToKey(key));
+ if (value != null)
+ return value.trim();
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getBasicConfiguration(String key, String defaultValue) {
+ if (StringUtils.isNotEmpty(key)) {
+ String value = properties.getProperty(addPrefixToKey(key), defaultValue);
+ if (value != null)
+ return value.trim();
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public Map<String, String> getBasicMOAIDConfigurationWithPrefix(String prefix) {
+ return KeyValueUtils.getSubSetWithPrefix(KeyValueUtils.convertPropertiesToMap(properties), addPrefixToKey(prefix));
+
+ }
+
+ @Override
+ public boolean getBasicMOAIDConfigurationBoolean(String key, boolean defaultValue) {
+ String value = getBasicConfiguration(key);
+ if (StringUtils.isNotEmpty(value))
+ return Boolean.valueOf(value.trim());
+
+ return defaultValue;
+
+ }
+
+ @Override
+ public Properties getFullConfigurationProperties() {
+ return properties;
+
+ }
+
+ @Override
+ public URI getConfigurationRootDirectory() {
+ return configRootDirectory;
+
+ }
+
+ @Override
+ public URI getConfigurationFilePath() {
+ return internalConfigPath;
+
+ }
+
+ /**
+ * Get the path to backup configuration
+ *
+ * @return A filepath file: or a classpath classpath:
+ */
+ abstract protected String getBackupConfigPath();
+
+ /**
+ * Get a specific configuration-key prefix for this software implementation
+ *
+ * @return
+ */
+ abstract public String getApplicationSpecificKeyPrefix();
+
+
+ private String addPrefixToKey(String key) {
+ if (StringUtils.isNotEmpty(getApplicationSpecificKeyPrefix())) {
+ if (getApplicationSpecificKeyPrefix().endsWith(KeyValueUtils.KEY_DELIMITER))
+ return getApplicationSpecificKeyPrefix() + key;
+ else
+ return getApplicationSpecificKeyPrefix() + KeyValueUtils.KEY_DELIMITER + key;
+
+ }
+
+ return key;
+
+ }
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/SPConfigurationImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/SPConfigurationImpl.java
new file mode 100644
index 00000000..e402983d
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/SPConfigurationImpl.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.conf;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.eaaf.core.api.data.EAAFConfigConstants;
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.impl.utils.KeyValueUtils;
+
+public class SPConfigurationImpl implements ISPConfiguration {
+ private static final long serialVersionUID = 688541755446463453L;
+
+ private static final Logger log = LoggerFactory.getLogger(SPConfigurationImpl.class);
+
+ private final Map<String, String> spConfiguration;
+ private final List<String> targetAreasWithNoInteralBaseIdRestriction;
+ private final List<String> targetAreasWithNoBaseIdTransmissionRestriction;
+
+
+ public SPConfigurationImpl(final Map<String, String> spConfig, IConfiguration authConfig) {
+ this.spConfiguration = spConfig;
+
+ //set oa specific restrictions
+ targetAreasWithNoInteralBaseIdRestriction = Collections.unmodifiableList(
+ KeyValueUtils.getListOfCSVValues(
+ authConfig.getBasicConfiguration(
+ CONFIG_KEY_RESTRICTIONS_BASEID_INTERNAL,
+ EAAFConstants.URN_PREFIX_CDID)));
+
+ targetAreasWithNoBaseIdTransmissionRestriction = Collections.unmodifiableList(
+ KeyValueUtils.getListOfCSVValues(
+ authConfig.getBasicConfiguration(
+ CONFIG_KEY_RESTRICTIONS_BASEID_TRANSMISSION,
+ EAAFConstants.URN_PREFIX_CDID)));
+
+ if (log.isTraceEnabled()) {
+ log.trace("Internal policy for OA: " + getUniqueIdentifier());
+ for (String el : targetAreasWithNoInteralBaseIdRestriction)
+ log.trace(" Allow baseID processing for prefix " + el);
+ for (String el : targetAreasWithNoBaseIdTransmissionRestriction)
+ log.trace(" Allow baseID transfer for prefix " + el);
+
+ }
+ }
+
+
+ @Override
+ public final Map<String, String> getFullConfiguration() {
+ return this.spConfiguration;
+
+ }
+
+ @Override
+ public final String getConfigurationValue(String key) {
+ if (key == null)
+ return null;
+ else
+ return this.spConfiguration.get(key);
+
+ }
+
+ @Override
+ public final String getConfigurationValue(String key, String defaultValue) {
+ String value = getConfigurationValue(key);
+ if (value == null)
+ return defaultValue;
+ else
+ return value;
+ }
+
+
+ @Override
+ public final Boolean isConfigurationValue(String key) {
+ String value = getConfigurationValue(key);
+ if (value != null) {
+ return Boolean.parseBoolean(value);
+
+ }
+
+ return null;
+ }
+
+
+ @Override
+ public final boolean isConfigurationValue(String key, boolean defaultValue) {
+ String value = getConfigurationValue(key);
+ if (value != null) {
+ return Boolean.parseBoolean(value);
+
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public final boolean containsConfigurationKey(String key) {
+ if (key == null)
+ return false;
+ else
+ return this.spConfiguration.containsKey(key);
+
+ }
+
+ @Override
+ public String getUniqueIdentifier() {
+ return getConfigurationValue(EAAFConfigConstants.SERVICE_UNIQUEIDENTIFIER);
+
+ }
+
+ @Override
+ public boolean hasBaseIdInternalProcessingRestriction() {
+ return false;
+
+ }
+
+ @Override
+ public boolean hasBaseIdTransferRestriction() {
+ return true;
+
+ }
+
+
+ @Override
+ public final List<String> getTargetsWithNoBaseIdInternalProcessingRestriction() {
+ return this.targetAreasWithNoInteralBaseIdRestriction;
+ }
+
+
+ @Override
+ public final List<String> getTargetsWithNoBaseIdTransferRestriction() {
+ return this.targetAreasWithNoBaseIdTransmissionRestriction;
+ }
+
+
+ @Override
+ public String getMinimumLevelOfAssurence() {
+ log.warn("Method not implemented: " + SPConfigurationImpl.class.getName() + " 'getMinimumLevelOfAssurence()'");
+ return null;
+ }
+
+
+ @Override
+ public String getAreaSpecificTargetIdentifier() {
+ log.warn("Method not implemented: " + SPConfigurationImpl.class.getName() + " 'getAreaSpecificTargetIdentifier()'");
+ return null;
+ }
+
+
+ @Override
+ public String getFriendlyName() {
+ log.warn("Method not implemented: " + SPConfigurationImpl.class.getName() + " 'getFriendlyName()'");
+ return null;
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractAuthProtocolModulController.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractAuthProtocolModulController.java
new file mode 100644
index 00000000..d72ee404
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractAuthProtocolModulController.java
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.controller;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import at.gv.egiz.components.eventlog.api.EventConstants;
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.IStatusMessager;
+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.IModulInfo;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.api.idp.auth.IAuthenticationManager;
+import at.gv.egiz.eaaf.core.api.idp.auth.ISSOManager;
+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.EAAFSSOException;
+
+/**
+ * @author tlenz
+ *
+ */
+
+public abstract class AbstractAuthProtocolModulController extends AbstractController {
+ private static final Logger log = LoggerFactory.getLogger(AbstractAuthProtocolModulController.class);
+
+ public static final String ENDPOINT_FINALIZEPROTOCOL = "finalizeAuthProtocol";
+ public static final String ENDPOINT_ERRORHANDLING = "errorHandling";
+
+
+ @Autowired(required=true) private IAuthenticationManager authmanager;
+ @Autowired(required=true) private IAuthenticationDataBuilder authDataBuilder;
+ @Autowired(required=false) private ISSOManager ssoManager;
+
+ /**
+ * Initialize an authentication process for this protocol request
+ *
+ * @param httpReq HttpServletRequest
+ * @param httpResp HttpServletResponse
+ * @param protocolRequest Authentication request which is actually in process
+ * @throws IOException
+ */
+ protected void performAuthentication(HttpServletRequest req, HttpServletResponse resp,
+ IRequest pendingReq) throws IOException {
+ try {
+ if (pendingReq.isNeedAuthentication()) {
+ //request needs authentication --> start authentication process ...
+
+ //load Parameters from OnlineApplicationConfiguration
+ ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration();
+
+ if (oaParam == null)
+ throw new EAAFAuthenticationException(
+ IStatusMessager.CODES_INTERNAL_ERROR_AUTH_NOSPCONFIG,
+ new Object[] { pendingReq.getSPEntityId() },
+ "No Service Provider configuration found.");
+
+ if (authmanager.doAuthentication(req, resp, pendingReq)) {
+ //pending request is already authenticated --> protocol-specific postProcessing can start directly
+ finalizeAuthenticationProcess(req, resp, pendingReq);
+
+ //transaction is finished, log transaction finished event
+ revisionsLogger.logEvent(EventConstants.TRANSACTION_DESTROYED, pendingReq.getUniqueTransactionIdentifier());
+
+ }
+
+ } else {
+ executeProtocolSpecificAction(req, resp, pendingReq, null);
+
+ }
+
+ } catch (Exception e) {
+ buildProtocolSpecificErrorResponse(e, req, resp, pendingReq);
+ authmanager.performOnlyIDPLogOut(req, resp, pendingReq);
+
+ }
+ }
+
+
+ /**
+ * Finalize the requested protocol operation
+ *
+ * @param httpReq HttpServletRequest
+ * @param httpResp HttpServletResponse
+ * @param protocolRequest Authentication request which is actually in process
+ * @param moaSession MOASession object, which is used to generate the protocol specific authentication information
+ * @throws Exception
+ */
+ protected void finalizeAuthenticationProcess(HttpServletRequest req, HttpServletResponse resp,
+ IRequest pendingReq) throws Exception {
+
+ String newSSOSessionId = null;
+
+ //if Single Sign-On functionality is enabled for this request
+ if (pendingReq.needSingleSignOnFunctionality()) {
+ if (ssoManager != null)
+ newSSOSessionId = ssoManager.createNewSSOSessionCookie(req, resp, pendingReq);
+ else
+ log.warn("SSO is requested but there is not SSO Session-Manager available");
+
+ }
+
+ //build authenticationdata from session information and OA configuration
+ IAuthData authData = authDataBuilder.buildAuthenticationData(pendingReq);
+
+ //execute the protocol-specific action
+ SLOInformationInterface sloInformation = executeProtocolSpecificAction(req, resp, pendingReq, authData);
+
+ //Store OA specific SSO session information if an SSO cookie is set
+ if (StringUtils.isNotEmpty(newSSOSessionId)) {
+ try {
+ //create new SSO session, if actually no SSO session exists
+ if (StringUtils.isEmpty(pendingReq.getSSOSessionIdentifier())) {
+ ssoManager.createNewSSOSession(pendingReq, newSSOSessionId, sloInformation);
+
+ //MOA SSO-session already exists only update is required
+ } else {
+ ssoManager.updateSSOSession(pendingReq, newSSOSessionId, sloInformation);
+
+
+ }
+
+ } catch (EAAFSSOException e) {
+ log.warn("SSO Session information can not be stored -> SSO is not enabled!");
+ authmanager.performOnlyIDPLogOut(req, resp, pendingReq);
+
+ }
+
+ } else {
+ //remove MOASession from database
+ authmanager.performOnlyIDPLogOut(req, resp, pendingReq);
+
+ }
+
+ //Advanced statistic logging
+ statisticLogger.logSuccessOperation(pendingReq, authData, StringUtils.isNotEmpty(newSSOSessionId));
+
+ }
+
+ /**
+ * Executes the requested protocol action
+ *
+ * @param httpReq HttpServletRequest
+ * @param httpResp HttpServletResponse
+ * @param protocolRequest Authentication request which is actually in process
+ * @param authData Service-provider specific authentication data
+ *
+ * @return Return Single LogOut information or null if protocol supports no SSO
+ *
+ * @throws Exception
+ */
+ private SLOInformationInterface executeProtocolSpecificAction(HttpServletRequest httpReq, HttpServletResponse httpResp,
+ IRequest pendingReq, IAuthData authData) throws Exception {
+ try {
+ // request needs no authentication --> start request processing
+ Class<?> clazz = Class.forName(pendingReq.requestedAction());
+ if (clazz == null ||
+ !IAction.class.isAssignableFrom(clazz)) {
+ log.error("Requested protocol-action processing Class is NULL or does not implement the IAction interface.");
+ throw new Exception("Requested protocol-action processing Class is NULL or does not implement the IAction interface.");
+
+ }
+
+ IAction protocolAction = (IAction) applicationContext.getBean(clazz);
+ return protocolAction.processRequest(pendingReq, httpReq, httpResp, authData);
+
+ } catch (ClassNotFoundException e) {
+ log.error("Requested Auth. protocol processing Class is NULL or does not implement the IAction interface.");
+ throw new Exception("Requested Auth. protocol processing Class is NULL or does not implement the IAction interface.");
+ }
+
+ }
+
+ protected void buildProtocolSpecificErrorResponse(Throwable throwable, HttpServletRequest req,
+ HttpServletResponse resp, IRequest protocolRequest) throws IOException {
+ try {
+
+ Class<?> clazz = Class.forName(protocolRequest.requestedModule());
+
+ if (clazz == null ||
+ !IModulInfo.class.isAssignableFrom(clazz)) {
+ log.error("Requested protocol module Class is NULL or does not implement the IModulInfo interface.");
+ throw new Exception("Requested protocol module Class is NULL or does not implement the IModulInfo interface.");
+
+ }
+
+ IModulInfo handlingModule = (IModulInfo) applicationContext.getBean(clazz);
+
+ if (handlingModule.generateErrorMessage(
+ throwable, req, resp, protocolRequest)) {
+
+ //log Error to technical log
+ logExceptionToTechnicalLog(throwable);
+
+ //log Error Message
+ statisticLogger.logErrorOperation(throwable, protocolRequest);
+
+ //write revision log entries
+ revisionsLogger.logEvent(protocolRequest, EventConstants.TRANSACTION_ERROR, protocolRequest.getUniqueTransactionIdentifier());
+
+ return;
+
+ } else {
+ handleErrorNoRedirect(throwable, req, resp, true);
+
+ }
+
+ } catch (Throwable e) {
+ handleErrorNoRedirect(throwable, req, resp, true);
+
+ }
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java
new file mode 100644
index 00000000..980d77ba
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java
@@ -0,0 +1,354 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.controller;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.naming.ConfigurationException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.StringEscapeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import at.gv.egiz.components.eventlog.api.EventConstants;
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.IRequestStorage;
+import at.gv.egiz.eaaf.core.api.IStatusMessager;
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.data.ExceptionContainer;
+import at.gv.egiz.eaaf.core.api.gui.IGUIBuilderConfiguration;
+import at.gv.egiz.eaaf.core.api.gui.IGUIBuilderConfigurationFactory;
+import at.gv.egiz.eaaf.core.api.gui.IGUIFormBuilder;
+import at.gv.egiz.eaaf.core.api.gui.ModifyableGuiBuilderConfiguration;
+import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
+import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger;
+import at.gv.egiz.eaaf.core.api.logging.IStatisticLogger;
+import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage;
+import at.gv.egiz.eaaf.core.exceptions.AuthnRequestValidatorException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+import at.gv.egiz.eaaf.core.exceptions.GUIBuildException;
+import at.gv.egiz.eaaf.core.exceptions.InvalidProtocolRequestException;
+import at.gv.egiz.eaaf.core.exceptions.ProcessExecutionException;
+import at.gv.egiz.eaaf.core.exceptions.ProtocolNotActiveException;
+import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;
+import at.gv.egiz.eaaf.core.impl.utils.HTTPUtils;
+import at.gv.egiz.eaaf.core.impl.utils.Random;
+import at.gv.egiz.eaaf.core.impl.utils.ServletUtils;
+
+
+/**
+ * @author tlenz
+ *
+ */
+public abstract class AbstractController {
+
+ private static final Logger log = LoggerFactory.getLogger(AbstractController.class);
+
+ @Autowired(required=true) protected ApplicationContext applicationContext;
+ @Autowired(required=true) protected IConfiguration authConfig;
+ @Autowired(required=true) protected ITransactionStorage transactionStorage;
+ @Autowired(required=true) protected IRequestStorage requestStorage;
+ @Autowired(required=true) protected IGUIFormBuilder guiBuilder;
+ @Autowired(required=true) protected IGUIBuilderConfigurationFactory guiConfigFactory;
+ @Autowired(required=true) protected IStatusMessager statusMessager;
+
+ @Autowired protected IStatisticLogger statisticLogger;
+ @Autowired protected IRevisionLogger revisionsLogger;
+
+
+
+
+
+ @ExceptionHandler({EAAFException.class})
+ public void MOAIDExceptionHandler(HttpServletRequest req, HttpServletResponse resp, Exception e) throws IOException {
+ log.error(e.getMessage() , e);
+ internalMOAIDExceptionHandler(req, resp, e, true);
+
+ }
+
+ @ExceptionHandler({Exception.class})
+ public void GenericExceptionHandler(HttpServletResponse resp, Exception exception) throws IOException {
+ log.error("Internel Server Error." , exception);
+ resp.setContentType(EAAFConstants.CONTENTTYPE_HTML_UTF8);
+ resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error!" +
+ "(Errorcode=9199"
+ +" | Description="
+ + StringEscapeUtils.escapeHtml4(StringEscapeUtils.escapeEcmaScript(exception.getMessage()))
+ + ")");
+ return;
+
+ }
+
+ @ExceptionHandler({IOException.class})
+ public void IOExceptionHandler(HttpServletResponse resp, Throwable exception) {
+ log.error("Internel Server Error." , exception);
+ resp.setContentType(EAAFConstants.CONTENTTYPE_HTML_UTF8);
+ resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+
+ }
+
+ protected void handleError(String errorMessage, Throwable exceptionThrown,
+ HttpServletRequest req, HttpServletResponse resp, IRequest pendingReq) throws IOException {
+
+ String pendingRequestID = null;
+ if (pendingReq != null)
+ pendingRequestID = pendingReq.getPendingRequestId();
+
+ Throwable loggedException = null;
+ Throwable extractedException = extractOriginalExceptionFromProcessException(exceptionThrown);
+
+ //extract pendingRequestID and originalException if it was a TaskExecutionException
+ if (extractedException instanceof TaskExecutionException) {
+ //set original exception
+ loggedException = ((TaskExecutionException) extractedException).getOriginalException();
+
+ //use TaskExecutionException directly, if no Original Exeception is included
+ if (loggedException == null)
+ loggedException = exceptionThrown;
+
+ //set pending-request ID if it is set
+ String reqID = ((TaskExecutionException) extractedException).getPendingRequestID();
+ if (StringUtils.isNotEmpty(reqID))
+ pendingRequestID = reqID;
+
+ } else
+ loggedException = exceptionThrown;
+
+ try {
+ //switch to protocol-finalize method to generate a protocol-specific error message
+
+ //log error directly in debug mode
+ if (log.isDebugEnabled())
+ log.warn(loggedException.getMessage(), loggedException);
+
+
+ //put exception into transaction store for redirect
+ String key = Random.nextLongRandom();
+ if (pendingReq != null) {
+ revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR);
+ transactionStorage.put(key,
+ new ExceptionContainer(pendingReq, loggedException), -1);
+
+ } else {
+ transactionStorage.put(key,
+ new ExceptionContainer(null, loggedException), -1);
+
+ }
+
+ //build up redirect URL
+ String redirectURL = null;
+ redirectURL = ServletUtils.getBaseUrl(req);
+ redirectURL += "/"+AbstractAuthProtocolModulController.ENDPOINT_ERRORHANDLING
+ + "?" + EAAFConstants.PARAM_HTTP_ERROR_CODE + "=" + key;
+
+// //only add pending-request Id if it exists
+// if (StringUtils.isNotEmpty(pendingRequestID))
+// redirectURL += "&" + EAAFConstants.PARAM_HTTP_TARGET_PENDINGREQUESTID + "=" + pendingRequestID;
+
+ resp.setContentType("text/html");
+ resp.setStatus(302);
+
+ resp.addHeader("Location", redirectURL);
+ log.debug("REDIRECT TO: " + redirectURL);
+
+ return;
+
+ } catch (Exception e) {
+ log.warn("Default error-handling FAILED. Exception can not be stored ....", e);
+ log.info("Switch to generic generic backup error-handling ... ");
+ handleErrorNoRedirect(loggedException, req, resp, true);
+
+ }
+
+ }
+
+ /**
+ * Handles all exceptions with no pending request.
+ * Therefore, the error is written to the users browser
+ *
+ * @param throwable
+ * @param req
+ * @param resp
+ * @throws IOException
+ */
+ protected void handleErrorNoRedirect(Throwable throwable, HttpServletRequest req,
+ HttpServletResponse resp, boolean writeExceptionToStatisticLog) throws IOException {
+
+ //log Exception into statistic database
+ if (writeExceptionToStatisticLog)
+ statisticLogger.logErrorOperation(throwable);
+
+ //write errror to console
+ logExceptionToTechnicalLog(throwable);
+
+ //return error to Web browser
+ if (throwable instanceof EAAFException || throwable instanceof ProcessExecutionException)
+ internalMOAIDExceptionHandler(req, resp, (Exception)throwable, false);
+
+ else {
+ //write generic message for general exceptions
+ String msg = statusMessager.getMessage(IStatusMessager.CODES_INTERNAL_ERROR_GENERIC, null);
+ writeHTMLErrorResponse(req, resp, msg, "9199", (Exception) throwable);
+
+ }
+
+ }
+
+ /**
+ * Write a Exception to the MOA-ID-Auth internal technical log
+ *
+ * @param loggedException Exception to log
+ */
+ protected void logExceptionToTechnicalLog(Throwable loggedException) {
+ if (!( loggedException instanceof EAAFException
+ || loggedException instanceof ProcessExecutionException )) {
+ log.error("Receive an internal error: Message=" + loggedException.getMessage(), loggedException);
+
+ } else {
+ if (log.isDebugEnabled() || log.isTraceEnabled()) {
+ log.warn(loggedException.getMessage(), loggedException);
+
+ } else {
+ log.warn(loggedException.getMessage());
+
+ }
+ }
+ }
+
+ private void writeBadRequestErrorResponse(HttpServletRequest req, HttpServletResponse resp, EAAFException e) throws IOException {
+ String code = statusMessager.mapInternalErrorToExternalError(((InvalidProtocolRequestException)e).getErrorId());
+ String descr = StringEscapeUtils.escapeHtml4(StringEscapeUtils.escapeEcmaScript(e.getMessage()));
+ resp.setContentType(EAAFConstants.CONTENTTYPE_HTML_UTF8);
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Protocol validation FAILED!" +
+ "(Errorcode=" + code +
+ " | Description=" + descr + ")");
+
+ }
+
+ private void writeHTMLErrorResponse(HttpServletRequest req, HttpServletResponse httpResp, String msg, String errorCode, Exception error) throws IOException {
+
+ try {
+ IGUIBuilderConfiguration config
+ = guiConfigFactory.getDefaultErrorGUI(HTTPUtils.extractAuthURLFromRequest(req));
+
+// HTTPUtils.extractAuthURLFromRequest(req),
+// DefaultGUIFormBuilderConfiguration.VIEW_ERRORMESSAGE,
+// null);
+
+ //add errorcode and errormessage
+ if (config instanceof ModifyableGuiBuilderConfiguration) {
+ ((ModifyableGuiBuilderConfiguration)config).putCustomParameter("errorMsg", msg);
+ ((ModifyableGuiBuilderConfiguration)config).putCustomParameter("errorCode", errorCode);
+
+ //add stacktrace if debug is enabled
+ if (log.isTraceEnabled()) {
+ ((ModifyableGuiBuilderConfiguration)config).putCustomParameter("stacktrace", getStacktraceFromException(error));
+
+ }
+
+ } else
+ log.info("Can not ADD error message, because 'GUIBuilderConfiguration' is not modifieable ");
+
+
+
+ guiBuilder.build(httpResp, config, "Error-Message");
+
+ } catch (GUIBuildException e) {
+ log.warn("Can not build error-message GUI.", e);
+ GenericExceptionHandler(httpResp, e);
+
+ }
+
+ }
+
+ private void writeHTMLErrorResponse(HttpServletRequest req, HttpServletResponse httpResp, Exception error) throws IOException {
+ writeHTMLErrorResponse(req, httpResp,
+ error.getMessage(),
+ statusMessager.getResponseErrorCode(error),
+ error);
+ }
+
+
+ private String getStacktraceFromException(Exception ex) {
+ StringWriter errors = new StringWriter();
+ ex.printStackTrace(new PrintWriter(errors));
+ return errors.toString();
+
+ }
+
+ /**
+ * Extracts a TaskExecutionException of a ProcessExecutionExeception Stacktrace.
+ *
+ * @param exception
+ * @return Return the latest TaskExecutionExecption if exists, otherwise the latest ProcessExecutionException
+ */
+ private Throwable extractOriginalExceptionFromProcessException(Throwable exception) {
+ Throwable exholder = exception;
+ TaskExecutionException taskExc = null;
+
+ while(exholder != null
+ && exholder instanceof ProcessExecutionException) {
+ ProcessExecutionException procExc = (ProcessExecutionException) exholder;
+ if (procExc.getCause() != null &&
+ procExc.getCause() instanceof TaskExecutionException) {
+ taskExc = (TaskExecutionException) procExc.getCause();
+ exholder = taskExc.getOriginalException();
+
+ } else
+ break;
+
+ }
+
+ if (taskExc == null)
+ return exholder;
+
+ else
+ return taskExc;
+ }
+
+ private void internalMOAIDExceptionHandler(HttpServletRequest req, HttpServletResponse resp, Exception e, boolean writeExceptionToStatisicLog) throws IOException {
+ if (e instanceof ProtocolNotActiveException) {
+ resp.getWriter().write(e.getMessage());
+ resp.setContentType(EAAFConstants.CONTENTTYPE_HTML_UTF8);
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN,
+ StringEscapeUtils.escapeHtml4(StringEscapeUtils.escapeEcmaScript(e.getMessage())));
+
+ } else if (e instanceof AuthnRequestValidatorException) {
+ AuthnRequestValidatorException ex = (AuthnRequestValidatorException)e;
+ //log Error Message
+ if (writeExceptionToStatisicLog)
+ statisticLogger.logErrorOperation(ex, ex.getErrorRequest());
+
+ //write error message
+ writeBadRequestErrorResponse(req, resp, (EAAFException) e);
+
+ } else if (e instanceof InvalidProtocolRequestException) {
+ //send error response
+ writeBadRequestErrorResponse(req, resp, (EAAFException) e);
+
+ } else if (e instanceof ConfigurationException) {
+ //send HTML formated error message
+ writeHTMLErrorResponse(req, resp, (EAAFException) e);
+
+ } else if (e instanceof EAAFException) {
+ //send HTML formated error message
+ writeHTMLErrorResponse(req, resp, e);
+
+ } else if (e instanceof ProcessExecutionException) {
+ //send HTML formated error message
+ writeHTMLErrorResponse(req, resp, e);
+
+ }
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractProcessEngineSignalController.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractProcessEngineSignalController.java
new file mode 100644
index 00000000..a4a86ca2
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractProcessEngineSignalController.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.controller;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.text.StringEscapeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.IStatusMessager;
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.idp.process.ProcessEngine;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFIllegalStateException;
+import at.gv.egiz.eaaf.core.impl.utils.TransactionIDUtils;
+
+/**
+ * Servlet that resumes a suspended process (in case of asynchronous tasks).
+ *
+ * @author tknall
+ *
+ */
+public abstract class AbstractProcessEngineSignalController extends AbstractController {
+ private static final Logger log = LoggerFactory.getLogger(AbstractProcessEngineSignalController.class);
+
+ @Autowired protected ProcessEngine processEngine;
+
+ protected void signalProcessManagement(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ String pendingRequestID = StringEscapeUtils.escapeHtml4(getPendingRequestId(req));
+ IRequest pendingReq = null;
+ try {
+ if (pendingRequestID == null) {
+ new EAAFException(
+ IStatusMessager.CODES_INTERNAL_ERROR_AUTH_NOPENDIGREQID,
+ null,
+ "NO PendingRequestId found"
+ );
+
+ }
+
+ pendingReq = requestStorage.getPendingRequest(pendingRequestID);
+ if (pendingReq == null) {
+ log.info("No PendingRequest with Id: " + pendingRequestID + " Maybe, a transaction timeout occure.");
+ throw new EAAFException(IStatusMessager.CODES_INTERNAL_ERROR_AUTH_TIMEOUT, new Object[]{pendingRequestID},
+ "No PendingRequest with Id: \" + pendingRequestID + \" Maybe, a transaction timeout occure.\"");
+
+ }
+
+ //change pending-request ID
+ requestStorage.changePendingRequestID(pendingReq);
+ pendingRequestID = pendingReq.getPendingRequestId();
+
+ // process instance is mandatory
+ if (pendingReq.getProcessInstanceId() == null) {
+ throw new EAAFIllegalStateException(new Object[]{"MOA session does not provide process instance id."},
+ "No execution environemnt found for this pending request");
+
+ }
+
+ // wake up next task
+ processEngine.signal(pendingReq);
+
+ } catch (Exception ex) {
+ handleError(null, ex, req, resp, pendingReq);
+
+ } finally {
+ //MOASessionDBUtils.closeSession();
+ TransactionIDUtils.removeAllLoggingVariables();
+
+ }
+
+
+ }
+
+ /**
+ * Retrieves the current pending-request id from the HttpServletRequest parameter
+ *
+ * <p/>
+ * Note that this class/method can be overwritten by modules providing their own strategy of retrieving the
+ * respective pending-request id.
+ *
+ * @param request
+ * The unterlying HttpServletRequest.
+ * @return The current pending-request id.
+ */
+ public String getPendingRequestId(HttpServletRequest request) {
+ return StringEscapeUtils.escapeHtml4(request.getParameter(EAAFConstants.PARAM_HTTP_TARGET_PENDINGREQUESTID));
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/ProtocolFinalizationController.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/ProtocolFinalizationController.java
new file mode 100644
index 00000000..3659ff4f
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/ProtocolFinalizationController.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.controller;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.text.StringEscapeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import at.gv.egiz.components.eventlog.api.EventConstants;
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.IStatusMessager;
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.data.ExceptionContainer;
+import at.gv.egiz.eaaf.core.exceptions.EAAFAuthenticationException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+
+/**
+ * @author tlenz
+ *
+ */
+@Controller
+public class ProtocolFinalizationController extends AbstractAuthProtocolModulController {
+ private static final Logger log = LoggerFactory.getLogger(ProtocolFinalizationController.class);
+
+ @RequestMapping(value = ENDPOINT_ERRORHANDLING, method = {RequestMethod.GET})
+ public void errorHandling(HttpServletRequest req, HttpServletResponse resp) throws EAAFException, IOException {
+ //receive an authentication error
+ String errorid = StringEscapeUtils.escapeHtml4(req.getParameter(EAAFConstants.PARAM_HTTP_ERROR_CODE));
+ if (errorid != null) {
+ IRequest pendingReq = null;
+ try {
+ //load stored exception from database
+ ExceptionContainer container = transactionStorage.get(errorid, ExceptionContainer.class);
+ if (container != null) {
+ //remove exception if it was found
+ transactionStorage.remove(errorid);
+
+ Throwable throwable = container.getExceptionThrown();
+ pendingReq = container.getPendingRequest();
+
+ if (pendingReq != null) {
+ //build protocol-specific error message if possible
+ buildProtocolSpecificErrorResponse(throwable, req, resp, pendingReq);
+
+ //remove active user-session
+ transactionStorage.remove(pendingReq.getPendingRequestId());
+
+ return;
+
+ } else {
+ handleErrorNoRedirect(throwable, req, resp, true);
+
+ }
+ } else {
+ handleErrorNoRedirect(
+ new EAAFException(
+ IStatusMessager.CODES_INTERNAL_ERROR_AUTH_NOPENDIGREQID,
+ null,
+ "NO Error with this Id found"
+ ), req, resp, false);
+
+ }
+
+ } catch (Throwable e) {
+ log.error(e.getMessage(), e);
+ handleErrorNoRedirect(e, req, resp, false);
+
+ } finally {
+ //remove pending-request
+ if (pendingReq != null) {
+ requestStorage.removePendingRequest(pendingReq.getPendingRequestId());
+ revisionsLogger.logEvent(EventConstants.TRANSACTION_DESTROYED, pendingReq.getUniqueTransactionIdentifier());
+
+ }
+
+ }
+
+ } else {
+ log.debug("Request contains NO ErrorId");
+ handleErrorNoRedirect(
+ new EAAFException(
+ IStatusMessager.CODES_INTERNAL_ERROR_AUTH_NOPENDIGREQID,
+ null,
+ "Request containts NO error id."
+ ), req, resp, false);
+
+ }
+
+ }
+
+
+ @RequestMapping(value = ENDPOINT_FINALIZEPROTOCOL, method = {RequestMethod.GET})
+ public void finalizeAuthProtocol(HttpServletRequest req, HttpServletResponse resp) throws EAAFException, IOException {
+
+ //read pendingRequest from http request
+ Object idObject = StringEscapeUtils.escapeHtml4(req.getParameter(EAAFConstants.PARAM_HTTP_TARGET_PENDINGREQUESTID));
+ IRequest pendingReq = null;
+ String pendingRequestID = null;
+ if (idObject != null && (idObject instanceof String)) {
+ pendingRequestID = (String) idObject;
+ pendingReq = requestStorage.getPendingRequest(pendingRequestID);
+
+ }
+
+ if (pendingReq == null) {
+ log.error("No PendingRequest with ID " + pendingRequestID + " found.!");
+ handleErrorNoRedirect(
+ new EAAFException(
+ IStatusMessager.CODES_INTERNAL_ERROR_AUTH_TIMEOUT,
+ new Object[]{pendingRequestID,
+ },
+ "No pendigReq with Id: " + pendingRequestID), req, resp, false);
+
+ } else {
+ try {
+ log.debug("Finalize PendingRequest with ID " + pendingRequestID);
+
+ //check if pending-request has 'abortedByUser' flag set
+ if (pendingReq.isAbortedByUser()) {
+ //send authentication aborted error to Service Provider
+ buildProtocolSpecificErrorResponse(
+ new EAAFAuthenticationException(
+ IStatusMessager.CODES_INTERNAL_ERROR_AUTH_USERSTOP,
+ new Object[] {},
+ "User stops authentication process"),
+ req, resp, pendingReq);
+
+ //do not remove the full active SSO-Session
+ // in case of only one Service-Provider authentication request is aborted
+ if ( !pendingReq.needSingleSignOnFunctionality()) {
+ transactionStorage.remove(pendingReq.getPendingRequestId());
+
+ }
+
+ //check if pending-request are authenticated
+ } else if (pendingReq.isAuthenticated()) {
+ finalizeAuthenticationProcess(req, resp, pendingReq);
+
+ } else {
+ //suspect state: pending-request is not aborted but also are not authenticated
+ log.error("PendingRequest is NOT authenticated --> Abort authentication process!");
+ handleErrorNoRedirect(
+ new EAAFException(
+ "auth.20",
+ null,
+ "PendingRequest is NOT authenticated --> Abort authentication process!"
+ ), req, resp, true);
+
+ }
+
+ } catch (Exception e) {
+ log.error("Finalize authentication protocol FAILED." , e);
+ buildProtocolSpecificErrorResponse(e, req, resp, pendingReq);
+
+ if (pendingReq != null)
+ transactionStorage.remove(pendingReq.getPendingRequestId());
+
+ }
+ }
+
+ //remove pending-request
+ if (pendingReq != null) {
+ requestStorage.removePendingRequest(pendingReq.getPendingRequestId());
+ revisionsLogger.logEvent(EventConstants.TRANSACTION_DESTROYED, pendingReq.getUniqueTransactionIdentifier());
+
+ }
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java
new file mode 100644
index 00000000..6a7f4440
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java
@@ -0,0 +1,386 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.controller.protocols;
+
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.naming.ConfigurationException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
+import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration;
+import at.gv.egiz.eaaf.core.exceptions.EAAFAuthenticationException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException;
+import at.gv.egiz.eaaf.core.impl.utils.HTTPUtils;
+import at.gv.egiz.eaaf.core.impl.utils.Random;
+import at.gv.egiz.eaaf.core.impl.utils.TransactionIDUtils;
+
+public abstract class RequestImpl implements IRequest, Serializable{
+
+ private static final Logger log = LoggerFactory.getLogger(RequestImpl.class);
+
+ public static final String DATAID_REQUESTER_IP_ADDRESS = "reqestImpl_requesterIPAddr";
+
+ private static final long serialVersionUID = 1L;
+
+ private String module = null;
+ private String action = null;
+
+ private String pendingRequestId;
+ private String processInstanceId;
+ private String ssoSessionId;
+
+ private String uniqueTransactionIdentifer;
+ private String uniqueSessionIdentifer;
+
+ private String requestedServiceProviderIdentifer;
+ private String idpAuthURL = null;
+
+ private ISPConfiguration spConfiguration = null;
+
+ private boolean passiv = false;
+ private boolean force = false;
+ private boolean isAbortedByUser = false;
+
+ //every request needs authentication by default
+ private boolean needAuthentication = true;
+
+ //every request is not authenticated by default
+ private boolean isAuthenticated = false;
+
+ //every request needs no SSO by default
+ private boolean needSSO = false;
+
+ private boolean needUserConsent = false;
+
+ private Map<String, Object> genericDataStorage = new HashMap<String, Object>();
+
+
+
+ /**
+ * @throws ConfigurationException
+ *
+ */
+ public final void initialize(HttpServletRequest req, IConfiguration authConfig) throws EAAFException {
+ //set pendingRequestId
+ pendingRequestId = Random.nextLongRandom();
+
+ //set unique transaction identifier for logging
+ uniqueTransactionIdentifer = Random.nextLongRandom();
+ TransactionIDUtils.setTransactionId(uniqueTransactionIdentifer);
+
+ //initialize session object
+ genericDataStorage.put(EAAFConstants.AUTH_DATA_CREATED, new Date());
+ //genericDataStorage.put(EAAFConstants.VALUE_SESSIONID, Random.nextLongRandom());
+
+ //check if End-Point is valid
+ String authURLString = HTTPUtils.extractAuthURLFromRequest(req);
+ URL authReqURL;
+ try {
+ authReqURL = new URL(authURLString);
+
+ } catch (MalformedURLException e) {
+ log.error("IDP AuthenticationServiceURL Prefix is not a valid URL." + authURLString, e);
+ throw new EAAFAuthenticationException("errorId", new Object[]{authURLString},
+ "IDP AuthenticationServiceURL Prefix is not a valid URL.", e);
+
+ }
+ this.idpAuthURL = authConfig.validateIDPURL(authReqURL);
+ if (this.idpAuthURL == null) {
+ log.warn("Extract AuthenticationServiceURL: " + authReqURL + " is NOT found in configuration.");
+ throw new EAAFAuthenticationException("errorId", new Object[]{authURLString},
+ "Extract AuthenticationServiceURL: " + authReqURL + " is NOT found in configuration.");
+
+ }
+
+ //set unique session identifier
+ String uniqueID = (String) req.getAttribute(EAAFConstants.UNIQUESESSIONIDENTIFIER);
+ if (StringUtils.isNotEmpty(uniqueID))
+ this.uniqueSessionIdentifer = uniqueID;
+
+ else {
+ log.debug("Create new sessionIdentifier for this pendingRequest ... ");
+ this.uniqueSessionIdentifer = Random.nextLongRandom();
+
+ }
+
+ //set requester's IP address
+ try {
+ setGenericDataToSession(DATAID_REQUESTER_IP_ADDRESS, req.getRemoteAddr());
+
+ } catch (EAAFStorageException e) {
+ log.info("Can NOT store remote IP address into 'pendingRequest'." , e);
+
+ }
+
+ }
+
+// /**
+// * This method map the protocol specific requested attributes to PVP 2.1 attributes.
+// *
+// * @return List of PVP 2.1 attribute names with maps all protocol specific attributes
+// */
+// public abstract Collection<String> getRequestedAttributes(MetadataProvider metadataProvider);
+
+ public final void setSPEntityId(String spIdentifier) {
+ this.requestedServiceProviderIdentifer = spIdentifier;
+ }
+
+ public final String getSPEntityId() {
+ return this.requestedServiceProviderIdentifer;
+ }
+
+ public final boolean isPassiv() {
+ return passiv;
+ }
+
+ public final boolean forceAuth() {
+ return force;
+ }
+
+ public final void setPassiv(boolean passiv) {
+ this.passiv = passiv;
+ }
+
+ public final void setForce(boolean force) {
+ this.force = force;
+ }
+
+ public final String requestedAction() {
+ return action;
+ }
+
+ public final void setAction(String action) {
+ this.action = action;
+ }
+
+ public final String requestedModule() {
+ return module;
+ }
+
+ public final void setModule(String module) {
+ this.module = module;
+ }
+
+ public final void setPendingRequestId(String pendingReqId) {
+ this.pendingRequestId = pendingReqId;
+
+ }
+
+ public final String getPendingRequestId() {
+ return pendingRequestId;
+ }
+
+ public final String getSSOSessionIdentifier() {
+ return this.ssoSessionId;
+ }
+
+ public final void setSSOSessionIdentifier(String internalSSOSessionId) {
+ this.ssoSessionId = internalSSOSessionId;
+
+ }
+
+ public final Map<String, Object> genericFullDataStorage() {
+ return this.genericDataStorage;
+
+ }
+
+ public final ISPConfiguration getServiceProviderConfiguration() {
+ return this.spConfiguration;
+
+
+ }
+
+ public <T> T getServiceProviderConfiguration(final Class<T> decorator) {
+ if (this.spConfiguration != null) {
+ if (decorator.isAssignableFrom(this.spConfiguration.getClass())) {
+ return (T) this.spConfiguration;
+
+ } else
+ log.error("Can not decorate SP configuration by '" + decorator.getName() + "'.");
+ throw new RuntimeException("Can not decorate SP configuration by '" + decorator.getName() + "'.");
+
+ }
+
+ return null;
+
+ }
+
+ public void setOnlineApplicationConfiguration(ISPConfiguration spConfig) {
+ this.spConfiguration = spConfig;
+
+ }
+
+ public final String getUniqueTransactionIdentifier() {
+ return this.uniqueTransactionIdentifer;
+
+ }
+
+ public final String getUniqueSessionIdentifier() {
+ return this.uniqueSessionIdentifer;
+
+ }
+
+ public final String getProcessInstanceId() {
+ return this.processInstanceId;
+
+ }
+
+ public final void setUniqueTransactionIdentifier(String id) {
+ this.uniqueTransactionIdentifer = id;
+
+ }
+
+ public final void setUniqueSessionIdentifier(String id) {
+ this.uniqueSessionIdentifer = id;
+
+ }
+
+ public void setProcessInstanceId(String id) {
+ this.processInstanceId = id;
+
+ }
+
+ public final String getAuthURL() {
+ return this.idpAuthURL;
+ }
+
+ public final String getAuthURLWithOutSlash() {
+ if (this.idpAuthURL.endsWith("/"))
+ return this.idpAuthURL.substring(0, this.idpAuthURL.length()-1);
+ else
+ return this.idpAuthURL;
+
+ }
+
+ public final boolean isNeedAuthentication() {
+ return needAuthentication;
+ }
+
+ public final void setNeedAuthentication(boolean needAuthentication) {
+ this.needAuthentication = needAuthentication;
+ }
+
+ public final boolean isAuthenticated() {
+ return isAuthenticated;
+ }
+
+ public final void setAuthenticated(boolean isAuthenticated) {
+ this.isAuthenticated = isAuthenticated;
+ }
+
+ public final boolean needSingleSignOnFunctionality() {
+ return needSSO;
+ }
+ public final void setNeedSingleSignOnFunctionality(boolean needSSO) {
+ this.needSSO = needSSO;
+
+ }
+
+ public final boolean isNeedUserConsent() {
+ return this.needUserConsent;
+
+ }
+
+ public final void setNeedUserConsent(boolean needConsent) {
+ this.needUserConsent = needConsent;
+
+ }
+
+ public final boolean isAbortedByUser() {
+ return this.isAbortedByUser;
+ }
+
+ public final void setAbortedByUser(boolean isAborted) {
+ this.isAbortedByUser = isAborted;
+
+ }
+
+ public final Object getGenericData(String key) {
+ if (StringUtils.isNotEmpty(key)) {
+ return genericDataStorage.get(key);
+
+ }
+
+ log.info("Can not load generic request-data with key='null'");
+ return null;
+ }
+
+ public final <T> T getGenericData(String key, final Class<T> clazz) {
+ if (StringUtils.isNotEmpty(key)) {
+ Object data = genericDataStorage.get(key);
+
+ if (data == null)
+ return null;
+
+ try {
+ @SuppressWarnings("unchecked")
+ T test = (T) data;
+ return test;
+
+ } catch (Exception e) {
+ log.warn("Generic request-data object can not be casted to requested type", e);
+ return null;
+
+ }
+
+ }
+
+ log.info("Can not load generic request-data with key='null'");
+ return null;
+
+ }
+
+ @Override
+ public final void setGenericDataToSession(String key, Object object) throws EAAFStorageException {
+ if (StringUtils.isEmpty(key)) {
+ log.info("Generic request-data can not be stored with a 'null' key");
+ throw new EAAFStorageException("Generic request-data can not be stored with a 'null' key", null);
+
+ }
+
+ if (object != null) {
+ if (!Serializable.class.isInstance(object)) {
+ log.warn("Generic request-data can only store objects which implements the 'Seralizable' interface");
+ throw new EAAFStorageException("Generic request-data can only store objects which implements the 'Seralizable' interface", null);
+
+ }
+ }
+
+ if (genericDataStorage.containsKey(key))
+ log.trace("Overwrite generic request-data with key:" + key);
+ else
+ log.trace("Add generic request-data with key:" + key + " to session.");
+
+ genericDataStorage.put(key, object);
+
+ }
+
+ @Override
+ public final void setGenericDataToSession(Map<String, Object> map) throws EAAFStorageException {
+ if (map == null) {
+ log.info("Generic request-data can not be stored with a 'null' map");
+ throw new EAAFStorageException("Generic request-data can not be stored with a 'null' map", null);
+
+ }
+
+ //validate and store values
+ for (Entry<String, Object> el : map.entrySet())
+ setGenericDataToSession(el.getKey(), el.getValue());
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/tasks/FinalizeAuthenticationTask.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/tasks/FinalizeAuthenticationTask.java
new file mode 100644
index 00000000..4ab63503
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/tasks/FinalizeAuthenticationTask.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.controller.tasks;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;
+import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask;
+
+/**
+ * @author tlenz
+ *
+ */
+@Component("FinalizeAuthenticationTask")
+public class FinalizeAuthenticationTask extends AbstractAuthServletTask {
+
+ private static final Logger log = LoggerFactory.getLogger(FinalizeAuthenticationTask.class);
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.process.springweb.MoaIdTask#execute(at.gv.egovernment.moa.id.process.api.ExecutionContext, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ public void execute(ExecutionContext executionContext,
+ HttpServletRequest request, HttpServletResponse response)
+ throws TaskExecutionException {
+
+ try {
+ //set pending request to authenticated
+ pendingReq.setAuthenticated(true);
+ requestStoreage.storePendingRequest(pendingReq);
+
+ log.info("AuthProcess finished. Redirect to Protocol Dispatcher.");
+ performRedirectToProtocolFinialization(pendingReq, response);
+
+ } catch (EAAFException e) {
+ throw new TaskExecutionException(pendingReq, e.getMessage(), e);
+
+ } catch (Exception e) {
+ log.warn("FinalizeAuthenticationTask has an internal error", e);
+ throw new TaskExecutionException(pendingReq, e.getMessage(), e);
+
+ } finally {
+ executionContext.remove(EAAFConstants.PROCESS_ENGINE_PENDINGREQUESTID);
+
+ }
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/tasks/RestartAuthProzessManagement.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/tasks/RestartAuthProzessManagement.java
new file mode 100644
index 00000000..2e5f49ed
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/tasks/RestartAuthProzessManagement.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.controller.tasks;
+
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.api.idp.process.ProcessEngine;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;
+import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask;
+import at.gv.egiz.eaaf.core.impl.idp.auth.modules.ModuleRegistration;
+import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl;
+import at.gv.egiz.eaaf.core.impl.idp.process.ExecutionContextImpl;
+
+/**
+ * @author tlenz
+ *
+ */
+@Component("RestartAuthProzessManagement")
+public class RestartAuthProzessManagement extends AbstractAuthServletTask {
+ private static final Logger log = LoggerFactory.getLogger(RestartAuthProzessManagement.class);
+
+ @Autowired ProcessEngine processEngine;
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.process.springweb.MoaIdTask#execute(at.gv.egovernment.moa.id.process.api.ExecutionContext, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response)
+ throws TaskExecutionException {
+ try {
+ //create a new execution context and copy all elements to new context
+ ExecutionContext newec = new ExecutionContextImpl();
+ Set<String> entries = executionContext.keySet();
+ for (String key : entries) {
+ newec.put(key, executionContext.get(key));
+
+ }
+
+ log.debug("Select new auth.-process and restart restart process-engine ... ");
+
+ // select and create new process instance
+ String processDefinitionId = ModuleRegistration.getInstance().selectProcess(newec);
+ if (processDefinitionId == null) {
+ log.warn("No suitable authentication process found for SessionID " + pendingReq.getPendingRequestId());
+ throw new EAAFException("process.02", new Object[] { pendingReq.getPendingRequestId()},
+ "No suitable authentication process found for SessionID \" + pendingReq.getPendingRequestId()");
+ }
+
+ String processInstanceId = processEngine.createProcessInstance(processDefinitionId, newec);
+
+ // keep process instance id in moa session
+ ((RequestImpl)pendingReq).setProcessInstanceId(processInstanceId);
+
+ // make sure pending request has been persisted before running the process
+ try {
+ requestStoreage.storePendingRequest(pendingReq);
+
+ } catch (EAAFException e) {
+ log.error("Database Error! MOASession is not stored!");
+ throw new EAAFException("init.04", new Object[] { pendingReq.getPendingRequestId() },
+ "Database Error! MOASession is not stored!");
+
+ }
+
+ log.info("Restart process-engine with auth.process:" + processDefinitionId);
+
+ // start process
+ processEngine.start(pendingReq);
+
+
+ } catch (EAAFException e) {
+ throw new TaskExecutionException(pendingReq, e.getMessage(), e);
+
+ } catch (Exception e) {
+ log.warn("RestartAuthProzessManagement has an internal error", e);
+ throw new TaskExecutionException(pendingReq, e.getMessage(), e);
+
+ }
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ExecutionContextImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ExecutionContextImpl.java
new file mode 100644
index 00000000..05fe030d
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ExecutionContextImpl.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+
+/**
+ * ExecutionContext implementation, related to a certain process instance.
+ *
+ * @author tknall
+ *
+ */
+public class ExecutionContextImpl implements ExecutionContext {
+
+ private static final long serialVersionUID = 1L;
+
+ private Map<String, Serializable> ctxData = Collections.synchronizedMap(new HashMap<String, Serializable>());
+
+ private String processInstanceId;
+
+ /**
+ * Creates a new instance.
+ */
+ public ExecutionContextImpl() {
+ }
+
+ /**
+ * Creates a new instance and associated it with a certain process instance.
+ */
+ public ExecutionContextImpl(String processInstanceId) {
+ this.processInstanceId = processInstanceId;
+ }
+
+ @Override
+ public void setProcessInstanceId(String processInstanceId) {
+ this.processInstanceId = processInstanceId;
+ }
+
+ @Override
+ public String getProcessInstanceId() {
+ return processInstanceId;
+ }
+
+ @Override
+ public Serializable get(String key) {
+ return ctxData.get(key);
+ }
+
+ @Override
+ public Serializable remove(String key) {
+ return ctxData.remove(key);
+ }
+
+ @Override
+ public void put(String key, Serializable object) {
+ ctxData.put(key, object);
+ }
+
+ @Override
+ public Set<String> keySet() {
+ return Collections.unmodifiableSet(ctxData.keySet());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("ExecutionContextImpl [");
+ builder.append("id=").append(processInstanceId);
+ builder.append(", variables=");
+ builder.append(ctxData.keySet());
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ExpressionEvaluationContextImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ExpressionEvaluationContextImpl.java
new file mode 100644
index 00000000..5a85487f
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ExpressionEvaluationContextImpl.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.api.idp.process.ExpressionEvaluationContext;
+
+/**
+ * Context implementation used for expression evaluation only.
+ *
+ * @author tknall
+ *
+ */
+public class ExpressionEvaluationContextImpl implements ExpressionEvaluationContext {
+
+ private static final long serialVersionUID = 1L;
+
+ private Map<String, Serializable> ctxData;
+
+ /**
+ * Creates a new instance and initializes it with data from a given process instance.
+ *
+ * @param processInstance
+ * The process instance.
+ */
+ ExpressionEvaluationContextImpl(ProcessInstance processInstance) {
+ ExecutionContext executionContext = processInstance.getExecutionContext();
+ Set<String> keys = executionContext.keySet();
+ ctxData = Collections.synchronizedMap(new HashMap<String, Serializable>(keys.size()));
+ for (String key : keys) {
+ ctxData.put(key, executionContext.get(key));
+ }
+ }
+
+ @Override
+ public Map<String, Serializable> getCtx() {
+ return Collections.unmodifiableMap(ctxData);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessDefinitionParser.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessDefinitionParser.java
new file mode 100644
index 00000000..2c715b94
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessDefinitionParser.java
@@ -0,0 +1,226 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.stream.util.EventReaderDelegate;
+import javax.xml.transform.stax.StAXSource;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.SAXException;
+
+import at.gv.egiz.eaaf.core.impl.idp.process.model.EndEvent;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.ProcessDefinition;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.ProcessNode;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.StartEvent;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.TaskInfo;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.Transition;
+
+/**
+ * Parses an XML representation of a process definition as defined by the respective XML schema.
+ * <p/
+ * The parser is thread-safe.
+ * @author tknall
+ *
+ */
+public class ProcessDefinitionParser {
+
+ private static final String NS = "http://reference.e-government.gv.at/namespace/moa/process/definition/v1";
+
+ private static Logger log = LoggerFactory.getLogger(ProcessDefinitionParser.class);
+
+ private static class LazyProcessDefinitionSchemaHolder {
+ private static final Schema PD_SCHEMA_INSTANCE;
+ static {
+ try (InputStream in = ProcessDefinitionParser.class.getResourceAsStream("/process/ProcessDefinition.xsd")) {
+ log.trace("Compiling process definition schema.");
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ // schema is thread-safe
+ PD_SCHEMA_INSTANCE = factory.newSchema(new StreamSource(in));
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to compile process definition schema.", e);
+ }
+ }
+ }
+
+ /**
+ * Parses an XML representation of a process definition. The representation is being validated in order to suffice
+ * the related XML schema.
+ *
+ * @param processDefinitionInputStream
+ * The process definition.
+ * @return A new process definition.
+ * @throws ProcessDefinitionParserException
+ * Thrown in case of error parsing the process definition.
+ */
+ public ProcessDefinition parse(InputStream processDefinitionInputStream) throws ProcessDefinitionParserException {
+ XMLEventReader reader = null;
+ final ProcessDefinition pd = new ProcessDefinition();
+ log.debug("Parsing and validating process definition.");
+ try {
+
+ // Standard implementation of XMLInputFactory seems not to be thread-safe
+ XMLInputFactory inputFactory = XMLInputFactory.newInstance();
+ reader = inputFactory.createXMLEventReader(processDefinitionInputStream);
+
+ final List<StartElement> transitionElements = new ArrayList<>();
+ final List<StartEvent> startEvents = new ArrayList<>();
+
+ reader = new EventReaderDelegate(reader) {
+
+ @Override
+ public XMLEvent nextEvent() throws XMLStreamException {
+ XMLEvent event = super.nextEvent();
+
+ switch (event.getEventType()) {
+ case XMLStreamConstants.START_ELEMENT:
+ StartElement element = event.asStartElement();
+ QName qname = element.getName();
+
+ if (NS.equals(qname.getNamespaceURI())) {
+ log.trace("Found process description element '{}'.", qname.getLocalPart());
+ Attribute id = element.getAttributeByName(new QName("id"));
+
+ switch (qname.getLocalPart()) {
+ case "ProcessDefinition":
+ if (id != null) {
+ pd.setId(id.getValue());
+ }
+ break;
+ case "StartEvent":
+ StartEvent startEvent = new StartEvent();
+ if (id != null) {
+ startEvent.setId(id.getValue());
+ }
+ startEvents.add(startEvent);
+ break;
+ case "EndEvent":
+ EndEvent endEvent = new EndEvent();
+ if (id != null) {
+ endEvent.setId(id.getValue());
+ pd.getEndEvents().put(id.getValue(), endEvent);
+ }
+ break;
+ case "Transition":
+ transitionElements.add(element);
+ break;
+ case "Task":
+ TaskInfo taskInfo = new TaskInfo();
+ if (id != null) {
+ taskInfo.setId(id.getValue());
+ pd.getTaskInfos().put(id.getValue(), taskInfo);
+ }
+ Attribute async = element.getAttributeByName(new QName("async"));
+ if (async != null) {
+ taskInfo.setAsync(Boolean.valueOf(async.getValue()));
+ }
+ Attribute implementingClass = element.getAttributeByName(new QName("class"));
+ if (implementingClass != null) {
+ taskInfo.setTaskImplementingClass(implementingClass.getValue());
+ }
+ break;
+ }
+
+ }
+
+ break;
+ }
+
+ return event;
+ }
+
+ };
+
+ // validator is not thread-safe
+ Validator validator = LazyProcessDefinitionSchemaHolder.PD_SCHEMA_INSTANCE.newValidator();
+ validator.validate(new StAXSource(reader));
+ log.trace("Process definition successfully schema validated.");
+
+ // perform some basic checks
+ log.trace("Building model and performing some plausibility checks.");
+ if (startEvents.size() != 1) {
+ throw new ProcessDefinitionParserException("A ProcessDefinition must contain exactly one single StartEvent.");
+ }
+ pd.setStartEvent(startEvents.get(0));
+
+ // link transitions
+ Iterator<StartElement> transitions = transitionElements.iterator();
+ while (transitions.hasNext()) {
+ StartElement element = transitions.next();
+ Transition transition = new Transition();
+ Attribute id = element.getAttributeByName(new QName("id"));
+ if (id != null) {
+ transition.setId(id.getValue());
+ }
+ Attribute conditionExpression = element.getAttributeByName(new QName("conditionExpression"));
+ if (conditionExpression != null) {
+ transition.setConditionExpression(conditionExpression.getValue());
+ }
+ Attribute from = element.getAttributeByName(new QName("from"));
+ if (from != null) {
+ ProcessNode fromNode = pd.getProcessNode(from.getValue());
+ if (fromNode == null) {
+ throw new ProcessDefinitionParserException("Transition's 'from'-attribute refers to a non-existing event or task '" + from.getValue() + '.');
+ }
+ if (fromNode instanceof EndEvent) {
+ throw new ProcessDefinitionParserException("Transition cannot start from end event.");
+ }
+ transition.setFrom(fromNode);
+ fromNode.getOutgoingTransitions().add(transition);
+ }
+ Attribute to = element.getAttributeByName(new QName("to"));
+ if (to != null) {
+ ProcessNode toNode = pd.getProcessNode(to.getValue());
+ if (toNode == null) {
+ throw new ProcessDefinitionParserException("Transition's 'to'-attribute refers to a non-existing event or task '" + to.getValue() + '.');
+ }
+ transition.setTo(toNode);
+ toNode.getIncomingTransitions().add(transition);
+ }
+ if (transition.getConditionExpression() == null && Objects.equals(transition.getFrom(), transition.getTo())) {
+ throw new ProcessDefinitionParserException("Transition's 'from' equals its 'to'. Since no 'conditionExpression' has been set this will cause a loop.");
+ }
+ }
+ log.debug("Process definition '{}' successfully parsed.", pd.getId());
+ return pd;
+
+ } catch (ProcessDefinitionParserException e) {
+ throw e;
+ } catch (XMLStreamException|IOException e) {
+ throw new ProcessDefinitionParserException("Unable to read process definition from inputstream.", e);
+ } catch (SAXException e) {
+ throw new ProcessDefinitionParserException("Schema validation of process description failed.", e);
+ } catch (Exception e) {
+ throw new ProcessDefinitionParserException("Internal error creating process definition from inputstream.", e);
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (XMLStreamException e) {
+ // error freeing resources
+ }
+ }
+ }
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessDefinitionParserException.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessDefinitionParserException.java
new file mode 100644
index 00000000..1ea811c6
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessDefinitionParserException.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process;
+
+/**
+ * Exception thrown in case of error parsing a process definition.
+ *
+ * @author tknall
+ *
+ */
+public class ProcessDefinitionParserException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new parser exception providing a {@code message} describing the reason and the {@code cause}.
+ *
+ * @param message
+ * The message.
+ * @param cause
+ * The cause.
+ */
+ public ProcessDefinitionParserException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Creates a new parser exception providing a {@code message} describing the reason.
+ *
+ * @param message
+ * The message.
+ */
+ public ProcessDefinitionParserException(String message) {
+ super(message);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessEngineImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessEngineImpl.java
new file mode 100644
index 00000000..b5028542
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessEngineImpl.java
@@ -0,0 +1,424 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process;
+
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Predicate;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.api.idp.process.ExpressionEvaluationContext;
+import at.gv.egiz.eaaf.core.api.idp.process.ExpressionEvaluator;
+import at.gv.egiz.eaaf.core.api.idp.process.ProcessEngine;
+import at.gv.egiz.eaaf.core.api.idp.process.ProcessInstanceStoreDAO;
+import at.gv.egiz.eaaf.core.api.idp.process.Task;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+import at.gv.egiz.eaaf.core.exceptions.ProcessExecutionException;
+import at.gv.egiz.eaaf.core.impl.idp.process.dao.ProcessInstanceStore;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.EndEvent;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.ProcessDefinition;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.ProcessNode;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.StartEvent;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.TaskInfo;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.Transition;
+
+/**
+ * Process engine implementation allowing starting and continuing processes as well as providing means for cleanup actions.
+ */
+public class ProcessEngineImpl implements ProcessEngine {
+
+ private Logger log = LoggerFactory.getLogger(getClass());
+
+ @Autowired ProcessInstanceStoreDAO piStoreDao;
+ @Autowired ApplicationContext context;
+
+ private ProcessDefinitionParser pdp = new ProcessDefinitionParser();
+
+ private Map<String, ProcessDefinition> processDefinitions = new ConcurrentHashMap<String, ProcessDefinition>();
+
+ private final static String MDC_CTX_PI_NAME = "processInstanceId";
+ private final static String MDC_CTX_TASK_NAME = "taskId";
+
+ private ExpressionEvaluator transitionConditionExpressionEvaluator;
+
+ @Override
+ public void registerProcessDefinition(ProcessDefinition processDefinition) {
+ log.info("Registering process definition '{}'.", processDefinition.getId());
+ processDefinitions.put(processDefinition.getId(), processDefinition);
+ }
+
+ @Override
+ public String registerProcessDefinition(InputStream processDefinitionInputStream) throws ProcessDefinitionParserException{
+ ProcessDefinition pd = pdp.parse(processDefinitionInputStream);
+ registerProcessDefinition(pd);
+ return pd.getId();
+ }
+
+ /**
+ * Sets the process definitions.
+ *
+ * @param processDefinitions
+ * The process definitions.
+ * @throws IllegalArgumentException
+ * In case the process definitions contain definitions with the same identifier.
+ */
+ public void setProcessDefinitions(Iterable<ProcessDefinition> processDefinitions) {
+ this.processDefinitions.clear();
+ for (ProcessDefinition pd : processDefinitions) {
+ if (this.processDefinitions.containsKey(pd.getId())) {
+ throw new IllegalArgumentException("Duplicate process definition identifier '" + pd.getId() + "'.");
+ }
+ registerProcessDefinition(pd);
+ }
+ }
+
+ /**
+ * Sets an expression evaluator that should be used to process transition condition expressions.
+ * @param transitionConditionExpressionEvaluator The expression evaluator.
+ */
+ public void setTransitionConditionExpressionEvaluator(
+ ExpressionEvaluator transitionConditionExpressionEvaluator) {
+ this.transitionConditionExpressionEvaluator = transitionConditionExpressionEvaluator;
+ }
+
+
+ @Override
+ public String createProcessInstance(String processDefinitionId, ExecutionContext executionContext) throws ProcessExecutionException {
+ // look for respective process definition
+ ProcessDefinition pd = processDefinitions.get(processDefinitionId);
+ if (pd == null) {
+ throw new ProcessExecutionException("Unable to find process definition for process '" + processDefinitionId + "'.");
+ }
+ // create and keep process instance
+ ProcessInstance pi = new ProcessInstance(pd, executionContext);
+ log.info("Creating process instance from process definition '{}': {}", processDefinitionId, pi.getId());
+
+ try {
+ saveOrUpdateProcessInstance(pi);
+
+ } catch (EAAFException e) {
+ throw new ProcessExecutionException("Unable to persist process instance.", e);
+ }
+
+ return pi.getId();
+ }
+
+ @Override
+ public String createProcessInstance(String processDefinitionId) throws ProcessExecutionException {
+ return createProcessInstance(processDefinitionId, null);
+ }
+
+ @Override
+ public void start(IRequest pendingReq) throws ProcessExecutionException {
+ try {
+ if (StringUtils.isEmpty(pendingReq.getProcessInstanceId())) {
+ log.error("Pending-request with id:" + pendingReq.getPendingRequestId()
+ + " includes NO 'ProcessInstanceId'");
+ throw new ProcessExecutionException("Pending-request with id:" + pendingReq.getPendingRequestId()
+ + " includes NO 'ProcessInstanceId'");
+ }
+
+ ProcessInstance pi = loadProcessInstance(pendingReq.getProcessInstanceId());
+
+ if (pi == null ) {
+ throw new ProcessExecutionException("Process instance '" + pendingReq.getProcessInstanceId() + "' does not exist.");
+
+ }
+
+ MDC.put(MDC_CTX_PI_NAME, pi.getId());
+
+ if (!ProcessInstanceState.NOT_STARTED.equals(pi.getState())) {
+ throw new ProcessExecutionException("Process instance '" + pi.getId() + "' has already been started (current state is " + pi.getState() + ").");
+ }
+ log.info("Starting process instance '{}'.", pi.getId());
+ // execute process
+ pi.setState(ProcessInstanceState.STARTED);
+ execute(pi, pendingReq);
+
+ //store ProcessInstance if it is not already ended
+ if (!ProcessInstanceState.ENDED.equals(pi.getState()))
+ saveOrUpdateProcessInstance(pi);
+
+ } catch (EAAFException e) {
+ throw new ProcessExecutionException("Unable to load/save process instance.", e);
+
+ } finally {
+ MDC.remove(MDC_CTX_PI_NAME);
+ }
+ }
+
+ @Override
+ public void signal(IRequest pendingReq) throws ProcessExecutionException {
+
+ try {
+ if (StringUtils.isEmpty(pendingReq.getProcessInstanceId())) {
+ log.error("Pending-request with id:" + pendingReq.getPendingRequestId()
+ + " includes NO 'ProcessInstanceId'");
+ throw new ProcessExecutionException("Pending-request with id:" + pendingReq.getPendingRequestId()
+ + " includes NO 'ProcessInstanceId'");
+ }
+
+ ProcessInstance pi = loadProcessInstance(pendingReq.getProcessInstanceId());
+
+ if (pi == null ) {
+ throw new ProcessExecutionException("Process instance '" + pendingReq.getProcessInstanceId() + "' does not exist.");
+
+ }
+
+ MDC.put(MDC_CTX_PI_NAME, pi.getId());
+
+ if (!ProcessInstanceState.SUSPENDED.equals(pi.getState())) {
+ throw new ProcessExecutionException("Process instance '" + pi.getId() + "' has not been suspended (current state is " + pi.getState() + ").");
+ }
+
+ log.info("Waking up process instance '{}'.", pi.getId());
+ pi.setState(ProcessInstanceState.STARTED);
+
+ //put pending-request ID on execution-context because it could be changed
+ pi.getExecutionContext().put(EAAFConstants.PARAM_HTTP_TARGET_PENDINGREQUESTID, pendingReq.getPendingRequestId());
+
+ execute(pi, pendingReq);
+
+ //store ProcessInstance if it is not already ended
+ if (!ProcessInstanceState.ENDED.equals(pi.getState()))
+ saveOrUpdateProcessInstance(pi);
+
+ } catch (EAAFException e) {
+ throw new ProcessExecutionException("Unable to load/save process instance.", e);
+
+ } finally {
+ MDC.remove(MDC_CTX_PI_NAME);
+ }
+ }
+
+
+ /**
+ * Instantiates a task implementation given by a {@link TaskInfo}.
+ * @param ti The task info.
+ * @return A Task implementation or {@code null} if the task info does not reference any task implementing classes.
+ * @throws ProcessExecutionException Thrown in case of error (when the referenced class does not implement {@link Task} for instance).
+ */
+ private Task createTaskInstance(TaskInfo ti) throws ProcessExecutionException {
+ String clazz = StringUtils.trimToNull(ti.getTaskImplementingClass());
+ Task task = null;
+
+ if (clazz != null) {
+ log.debug("Instantiating task implementing class '{}'.", clazz);
+ Object instanceClass = null;
+ try {
+ instanceClass = context.getBean(clazz);
+
+ } catch (Exception e) {
+ throw new ProcessExecutionException("Unable to get class '" + clazz + "' associated with task '" + ti.getId() + "' .", e);
+
+ }
+ if (instanceClass == null || !(instanceClass instanceof Task)) {
+ throw new ProcessExecutionException("Class '" + clazz + "' associated with task '" + ti.getId() + "' is not assignable to " + Task.class.getName() + ".");
+
+ }
+ try {
+ task = (Task) instanceClass;
+
+ } catch (Exception e) {
+ throw new ProcessExecutionException("Unable to instantiate class '" + clazz + "' associated with task '" + ti.getId() + "' .", e);
+ }
+ }
+
+ return task;
+ }
+
+ /**
+ * Starts/executes a given process instance.
+ * @param pi The process instance.
+ * @param pendingReq
+ * @throws ProcessExecutionException Thrown in case of error.
+ */
+ private void execute(final ProcessInstance pi, IRequest pendingReq) throws ProcessExecutionException {
+ if (ProcessInstanceState.ENDED.equals(pi.getState())) {
+ throw new ProcessExecutionException("Process for instance '" + pi.getId() + "' has already been ended.");
+ }
+ ProcessDefinition pd = pi.getProcessDefinition();
+ ProcessNode processNode = pd.getProcessNode(pi.getNextId());
+ log.debug("Processing node '{}'.", processNode.getId());
+
+ // distinguish process node types StartEvent, TaskInfo and EndEvent
+
+ if (processNode instanceof TaskInfo) {
+ // TaskInfo types need to be executed
+ TaskInfo ti = (TaskInfo) processNode;
+ MDC.put(MDC_CTX_TASK_NAME, ti.getId());
+ try {
+ log.info("Processing task '{}'.", ti.getId());
+ Task task = createTaskInstance(ti);
+ if (task != null) {
+ try {
+ log.info("Executing task implementation for task '{}'.", ti.getId());
+ log.debug("Execution context before task execution: {}", pi.getExecutionContext().keySet());
+ pendingReq = task.execute(pendingReq, pi.getExecutionContext());
+ log.info("Returned from execution of task '{}'.", ti.getId());
+ log.debug("Execution context after task execution: {}", pi.getExecutionContext().keySet());
+ } catch (Throwable t) {
+ throw new ProcessExecutionException("Error executing task '" + ti.getId() + "'.", t);
+ }
+ } else {
+ log.debug("No task implementing class set.");
+ }
+ } finally {
+ MDC.remove(MDC_CTX_TASK_NAME);
+ }
+
+ } else if (processNode instanceof EndEvent) {
+ log.info("Finishing process instance '{}'.", pi.getId());
+
+ try {
+ piStoreDao.remove(pi.getId());
+
+ } catch (EAAFException e) {
+ throw new ProcessExecutionException("Unable to remove process instance.", e);
+
+ }
+ pi.setState(ProcessInstanceState.ENDED);
+ log.debug("Final process context: {}", pi.getExecutionContext().keySet());
+ return;
+ }
+
+ final ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContextImpl(pi);
+
+ // traverse pointer
+ Transition t = CollectionUtils.find(processNode.getOutgoingTransitions(), new Predicate<Transition>() {
+ @Override
+ public boolean evaluate(Transition transition) {
+ if (transitionConditionExpressionEvaluator != null && transition.getConditionExpression() != null) {
+ log.trace("Evaluating transition expression '{}'.", transition.getConditionExpression());
+ return transitionConditionExpressionEvaluator.evaluate(expressionContext, transition.getConditionExpression());
+ }
+ return true;
+ }
+ });
+ if (t == null) {
+ throw new ProcessExecutionException("No valid transition starting from process node '" + processNode.getId()+ "'.");
+ }
+ log.trace("Found suitable transition: {}", t);
+ // update pointer
+ log.trace("Shifting process token from '{}' to '{}'.", pi.getNextId(), t.getTo().getId());
+ pi.setNextId(t.getTo().getId());
+
+ // inspect current task
+ if (t.getTo() instanceof TaskInfo && (((TaskInfo) t.getTo()).isAsync())) {
+ // immediately return in case of asynchonous task
+ log.info("Suspending process instance '{}' for asynchronous task '{}'.", pi.getId(), t.getTo().getId());
+ pi.setState(ProcessInstanceState.SUSPENDED);
+ return;
+ }
+
+ // continue execution in case of StartEvent or Task
+ if (processNode instanceof StartEvent || processNode instanceof TaskInfo) {
+ execute(pi, pendingReq);
+ }
+ }
+
+ @Override
+ public ProcessInstance getProcessInstance(String processInstanceId) {
+
+ ProcessInstance processInstance;
+ try {
+ processInstance = loadProcessInstance(processInstanceId);
+
+ } catch (EAAFException e) {
+ throw new RuntimeException("The process instance '" + processInstanceId + "' could not be retrieved.", e);
+ }
+
+ if (processInstance == null) {
+ throw new IllegalArgumentException("The process instance '" + processInstanceId + "' does not/no longer exist.");
+ }
+
+ return processInstance;
+ }
+
+ /**
+ * Persists a {@link ProcessInstance} to the database.
+ * @param processInstance The object to persist.
+ * @throws MOADatabaseException Thrown if an error occurs while accessing the database.
+ */
+ private void saveOrUpdateProcessInstance(ProcessInstance processInstance) throws EAAFException {
+ ProcessInstanceStore store = new ProcessInstanceStore();
+
+ ExecutionContext ctx = processInstance.getExecutionContext();
+
+ Map<String, Serializable> ctxData = new HashMap<String, Serializable>();
+ for (String key : ctx.keySet()) {
+ ctxData.put(key, ctx.get(key));
+ }
+ store.setExecutionContextData(ctxData);
+
+ store.setNextTaskId(processInstance.getNextId());
+ store.setProcessDefinitionId(processInstance.getProcessDefinition().getId());
+
+ store.setProcessInstanceId(processInstance.getId());
+ store.setProcessState(processInstance.getState());
+
+ piStoreDao.saveOrUpdate(store);
+ }
+
+ /**
+ * Load a {@link ProcessInstance} with a certain id from the database.
+ * @param processInstanceId The process instance id
+ * @return The process instance corresponding to the id or {@code null} if no such object is found.
+ * @throws MOADatabaseException Thrown if an error occurs while accessing the database.
+ */
+ private ProcessInstance loadProcessInstance(String processInstanceId) throws EAAFException {
+
+ ProcessInstanceStore piStore = piStoreDao.load(processInstanceId);
+
+ if (piStore == null) {
+ return null;
+ }
+
+ ExecutionContext executionContext = new ExecutionContextImpl(piStore.getProcessInstanceId());
+
+ Map<String, Serializable> executionContextData = piStore.getExecutionContextData();
+ for (String key : executionContextData.keySet()) {
+ executionContext.put(key, executionContextData.get(key));
+ }
+
+ ProcessInstance pi = new ProcessInstance(processDefinitions.get(piStore.getProcessDefinitionId()), executionContext);
+ pi.setNextId(piStore.getNextTaskId());
+ pi.setState(piStore.getProcessState());
+
+ return pi;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egovernment.moa.id.process.ProcessEngine#deleteProcessInstance(java.lang.String)
+ */
+ @Override
+ public void deleteProcessInstance(String processInstanceId) throws ProcessExecutionException {
+ if (StringUtils.isEmpty(processInstanceId)) {
+ throw new ProcessExecutionException("Unable to remove process instance: ProcessInstanceId is empty");
+
+ }
+
+ try {
+ piStoreDao.remove(processInstanceId);
+
+ } catch (EAAFException e) {
+ throw new ProcessExecutionException("Unable to remove process instance.", e);
+
+ }
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessInstance.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessInstance.java
new file mode 100644
index 00000000..ab6f7916
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessInstance.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.time.DurationFormatUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.ProcessDefinition;
+import at.gv.egiz.eaaf.core.impl.idp.process.support.SecureRandomHolder;
+
+/**
+ * Represents a process being executed. The process instance provides information about the process and its state.
+ *
+ * @author tknall
+ *
+ */
+public class ProcessInstance implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private static final int RND_ID_LENGTH = 22;
+
+ private ProcessDefinition processDefinition;
+ private String nextId;
+ private Date lru;
+ private ExecutionContext executionContext;
+ private ProcessInstanceState state = ProcessInstanceState.NOT_STARTED;
+
+ private Logger log = LoggerFactory.getLogger(getClass());
+
+ /**
+ * Creates a new process instance, based on a given process definition and a
+ * given execution context. If the given execution context is {@code null} a new execution context will be created.<p/>
+ * The process instance id of the execution context will be newly generated if it is {@code null} in the execution context.
+ *
+ * @param processDefinition
+ * The process definition.
+ * @param executionContext
+ * The execution context (may be {@code null}). If {@code null} a new execution context will be created internally.
+ */
+ ProcessInstance(ProcessDefinition processDefinition, ExecutionContext executionContext) {
+ this.processDefinition = processDefinition;
+ nextId = processDefinition.getStartEvent().getId();
+ if (executionContext == null) {
+ executionContext = new ExecutionContextImpl();
+ }
+ if (executionContext.getProcessInstanceId() == null) {
+ String pdIdLocalPart = RandomStringUtils.random(RND_ID_LENGTH, 0, 0, true, true, null,
+ SecureRandomHolder.getInstance());
+ executionContext.setProcessInstanceId(this.processDefinition.getId() + "-" + pdIdLocalPart);
+ } else {
+ log.debug("Using process instance id from execution context.");
+ }
+ log.debug("Creating process instance with id '{}'.", executionContext.getProcessInstanceId());
+ this.executionContext = executionContext;
+ touch();
+ }
+
+ /**
+ * Returns the underlying process definition.
+ *
+ * @return The underlying process definition.
+ */
+ ProcessDefinition getProcessDefinition() {
+ touch();
+ return processDefinition;
+ }
+
+ /**
+ * Returns the id of the process node to be executed next.
+ *
+ * @return The process node pointer indicating the process node to be executed next.
+ */
+ public String getNextId() {
+ touch();
+ return nextId;
+ }
+
+ /**
+ * Sets the internal pointer to the process node to be executed next.
+ *
+ * @param nextId
+ * The process node id to be executed next.
+ */
+ void setNextId(String nextId) {
+ touch();
+ this.nextId = nextId;
+ }
+
+ /**
+ * Returns the current state of the process instance.
+ *
+ * @return The current state.
+ */
+ public ProcessInstanceState getState() {
+ touch();
+ return state;
+ }
+
+ /**
+ * Sets the current state of the process instance.
+ *
+ * @param state
+ * The current state.
+ */
+ void setState(ProcessInstanceState state) {
+ touch();
+ this.state = state;
+ }
+
+ public String getId() {
+ touch();
+ return executionContext.getProcessInstanceId();
+ }
+
+ /**
+ * Updates the last recently used date of the process instance.
+ */
+ private void touch() {
+ lru = new Date();
+ }
+
+ /**
+ * Returns the date the process instance has been accessed last.
+ *
+ * @return The last recently used date.
+ */
+ Date getLru() {
+ return lru;
+ }
+
+ /**
+ * Returns the associated execution context.
+ * @return The execution context (never {@code null}).
+ */
+ public ExecutionContext getExecutionContext() {
+ touch();
+ return executionContext;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("ProcessInstance [");
+ builder.append("id=").append(executionContext.getProcessInstanceId());
+ builder.append(", idle since=").append(
+ DurationFormatUtils.formatDurationWords(new Date().getTime() - this.lru.getTime(), true, true));
+ if (processDefinition != null) {
+ builder.append(", processDefinition.id=");
+ builder.append(processDefinition.getId());
+ }
+ if (nextId != null) {
+ builder.append(", nextId=");
+ builder.append(nextId);
+ }
+ builder.append(", executionContext=").append(executionContext);
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessInstanceState.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessInstanceState.java
new file mode 100644
index 00000000..cfb5479b
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessInstanceState.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process;
+
+/**
+ * Represents a certain process instance state.
+ * @author tknall
+ *
+ */
+public enum ProcessInstanceState {
+
+ /**
+ * Indicates that the process with this process instance has not yet been started.
+ */
+ NOT_STARTED,
+
+ /**
+ * Indicates that the process is currently running.
+ */
+ STARTED,
+
+ /**
+ * Indicates that the process has been suspended until being waken up by someonce calling {@code signal}.
+ */
+ SUSPENDED,
+
+ /**
+ * Indicates that the process has been completed.
+ */
+ ENDED
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/dao/ProcessInstanceStore.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/dao/ProcessInstanceStore.java
new file mode 100644
index 00000000..1ac65e10
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/dao/ProcessInstanceStore.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.dao;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import at.gv.egiz.eaaf.core.impl.idp.process.ProcessInstanceState;
+
+public class ProcessInstanceStore implements Serializable{
+
+ private static final long serialVersionUID = -6147519767313903808L;
+
+ /**
+ * A process instance identifier qualifies as natural primary key by satisfying these requirements
+ * ("unique, constant, required"):
+ * <ul>
+ * <li>unique value</li>
+ * <li>never changes (immutable)</li>
+ * <li>never {@code null}</li>
+ * </ul>
+ */
+
+ private String processInstanceId;
+
+ private String processDefinitionId;
+
+ private String nextTaskId;
+
+ private ProcessInstanceState processState;
+
+ private Map<String, Serializable> executionContextData;
+
+ public String getProcessInstanceId() {
+ return processInstanceId;
+ }
+
+ public String getProcessDefinitionId() {
+ return processDefinitionId;
+ }
+
+ public String getNextTaskId() {
+ return nextTaskId;
+ }
+
+ public ProcessInstanceState getProcessState() {
+ return processState;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Map<String, Serializable> getExecutionContextData() {
+ return executionContextData;
+ }
+
+ public void setProcessInstanceId(String processInstanceId) {
+ this.processInstanceId = processInstanceId;
+ }
+
+ public void setProcessDefinitionId(String processDefinitionId) {
+ this.processDefinitionId = processDefinitionId;
+ }
+
+ public void setNextTaskId(String nextTaskId) {
+ this.nextTaskId = nextTaskId;
+ }
+
+ public void setProcessState(ProcessInstanceState processState) {
+ this.processState = processState;
+ }
+
+ public void setExecutionContextData(Map<String, Serializable> executionContextData) {
+ this.executionContextData = executionContextData;
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/dao/ProcessInstanceStoreDAOImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/dao/ProcessInstanceStoreDAOImpl.java
new file mode 100644
index 00000000..be80a629
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/dao/ProcessInstanceStoreDAOImpl.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.dao;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import at.gv.egiz.eaaf.core.api.idp.process.ProcessInstanceStoreDAO;
+import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+
+/**
+ * Database backed implementation of the {@link ProcessInstanceStoreDAO}
+ * interface.
+ */
+@Service("ProcessInstanceStoreage")
+public class ProcessInstanceStoreDAOImpl implements ProcessInstanceStoreDAO {
+
+ private Logger log = LoggerFactory.getLogger(getClass());
+
+ @Autowired ITransactionStorage transactionStorage;
+
+ @Override
+ public void saveOrUpdate(ProcessInstanceStore pIStore) throws EAAFException {
+ try {
+ transactionStorage.put(pIStore.getProcessInstanceId(), pIStore, -1);
+ log.debug("Store process instance with='{}' in the database.", pIStore.getProcessInstanceId());
+
+ } catch (EAAFException e) {
+ log.warn("ProcessInstanceStore could not be persisted to the database.");
+ throw e;
+ }
+ }
+
+ @Override
+ public ProcessInstanceStore load(String processInstanceId) throws EAAFException {
+ log.debug("Retrieve the ProcessInstanceStore for id='{}' from the database.", processInstanceId);
+ ProcessInstanceStore result = null;
+ try {
+ result = transactionStorage.get(processInstanceId, ProcessInstanceStore.class);
+
+ } catch (Exception e) {
+ log.error("There are multiple persisted processes with the same process instance id '{}'",
+ processInstanceId);
+
+ throw e;
+ }
+
+ if (result != null) {
+ log.debug("Found process instance store for instance '{}'.", processInstanceId);
+
+ } else {
+ log.debug("Unable to find process instance store for instance '{}'.", processInstanceId);
+
+ }
+
+ return result;
+ }
+
+ @Override
+ public void remove(String processInstanceId) throws EAAFException {
+
+ log.debug("Delete the ProcessInstanceStore for id='{}' from the database.", processInstanceId);
+
+ if (transactionStorage.containsKey(processInstanceId))
+ transactionStorage.remove(processInstanceId);
+ else
+ log.trace("ProcessInstanceStore for id='{}' was not found and could therefore not be deleted.", processInstanceId);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/EndEvent.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/EndEvent.java
new file mode 100644
index 00000000..a8b757e3
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/EndEvent.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.model;
+
+import java.io.Serializable;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+/**
+ * Represents an end event. Process execution terminates when an end event is reached.
+ *
+ * @author tknall
+ */
+public class EndEvent extends ProcessNode implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("EndEvent [");
+ if (getId() != null) {
+ builder.append("id=");
+ builder.append(getId());
+ }
+ if (CollectionUtils.isNotEmpty(getIncomingTransitions())) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("incomingTransitions=");
+ builder.append(getIncomingTransitions());
+ }
+ if (CollectionUtils.isNotEmpty(getOutgoingTransitions())) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("outgoingTransitions=");
+ builder.append(getOutgoingTransitions());
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/ProcessDefinition.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/ProcessDefinition.java
new file mode 100644
index 00000000..af4c0a1f
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/ProcessDefinition.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.model;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import at.gv.egiz.eaaf.core.impl.idp.process.ProcessDefinitionParser;
+
+/**
+ * Represents a single process definition containing
+ * <ul>
+ * <li>a {@link StartEvent},</li>
+ * <li>one or more {@linkplain TaskInfo Tasks},</li>
+ * <li>one or more {@linkplain EndEvent EndEvents} and</li>
+ * <li>some {@linkplain Transition Transitions} linking StartEvents, Tasks and EndEvents.
+ * </ul>
+ *
+ * @author tknall
+ *
+ */
+public class ProcessDefinition {
+
+ private String id;
+ private StartEvent startEvent;
+ private Map<String, TaskInfo> taskInfos = new LinkedHashMap<>();
+ private Map<String, EndEvent> endEvents = new LinkedHashMap<>();
+
+ /**
+ * Returns the unique identifier of the process definition.
+ *
+ * @return The unique identifier (never {@code null} if process definition comes from
+ * {@link ProcessDefinitionParser}).
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the unique identifier of the process definition.
+ *
+ * @param id
+ * The unique identifier.
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the start event of the process definition.
+ *
+ * @return The start event (never {@code null} if process definition comes from {@link ProcessDefinitionParser}).
+ */
+ public StartEvent getStartEvent() {
+ return startEvent;
+ }
+
+ /**
+ * Sets the start event of the process definition.
+ *
+ * @param startEvent
+ * The start event.
+ */
+ public void setStartEvent(StartEvent startEvent) {
+ this.startEvent = startEvent;
+ }
+
+ /**
+ * Returns a map containing the tasks of the process definition.
+ *
+ * @return The tasks (map is never {@code null} if process definition comes from {@link ProcessDefinitionParser}).
+ */
+ public Map<String, TaskInfo> getTaskInfos() {
+ return taskInfos;
+ }
+
+ /**
+ * Sets the map containing the tasks.
+ *
+ * @param taskInfos
+ * The map containing the tasks.
+ */
+ public void setTaskInfos(Map<String, TaskInfo> taskInfos) {
+ this.taskInfos = taskInfos;
+ }
+
+ /**
+ * Returns a map containing the end events of the process description.
+ *
+ * @return The map containing the end events (map is never {@code null} if process definition comes from
+ * {@link ProcessDefinitionParser}).
+ */
+ public Map<String, EndEvent> getEndEvents() {
+ return endEvents;
+ }
+
+ /**
+ * Sets a map containing the end events of the process description.
+ *
+ * @param endEvents
+ * The map containing the end events.
+ */
+ public void setEndEvents(Map<String, EndEvent> endEvents) {
+ this.endEvents = endEvents;
+ }
+
+ /**
+ * Returns the process node associated with the given {@code id}.
+ *
+ * @param id
+ * The identifier of the process node.
+ * @return The process node (may be {code null} when no process node with the given {@code id} exists).
+ */
+ public ProcessNode getProcessNode(String id) {
+ Objects.requireNonNull(id, "Identifier must not be null.");
+ if (startEvent != null && id.equals(startEvent.getId())) {
+ return startEvent;
+ }
+ TaskInfo task = taskInfos.get(id);
+ if (task != null) {
+ return task;
+ }
+ return endEvents.get(id);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (id != null) {
+ builder.append("id=");
+ builder.append(id);
+ }
+ if (startEvent != null) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("startEvent=");
+ builder.append(startEvent);
+ }
+ if (taskInfos != null && !taskInfos.isEmpty()) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("tasksInfos=");
+ builder.append(taskInfos.values());
+ }
+ if (endEvents != null && !endEvents.isEmpty()) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("endEvents=");
+ builder.append(endEvents.values());
+ }
+ builder.insert(0, "ProcessDefinition [");
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/ProcessNode.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/ProcessNode.java
new file mode 100644
index 00000000..e1109336
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/ProcessNode.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import at.gv.egiz.eaaf.core.impl.idp.process.ProcessDefinitionParser;
+
+/**
+ * Represents a {@link StartEvent}, an {@link EndEvent} or a {@linkplain TaskInfo Task}.
+ * @author tknall
+ *
+ */
+public abstract class ProcessNode {
+
+ private String id;
+ private List<Transition> outgoingTransitions = new ArrayList<>();
+ private List<Transition> incomingTransitions = new ArrayList<>();
+
+ /**
+ * Returns the unique identifier of the process node.
+ *
+ * @return The unique identifier (never {@code null} if process node comes from a process definition from
+ * {@link ProcessDefinitionParser}).
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the unique identifier of the process node.
+ * @param id The unique identifier.
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns a list of transitions pointing from this process node to another one.
+ * @return A list of transitions (never {@code null} if process node comes from a process definition from {@link ProcessDefinitionParser}).
+ */
+ public List<Transition> getOutgoingTransitions() {
+ return outgoingTransitions;
+ }
+
+ /**
+ * Sets the list of transitions pointing from this process node to another one.
+ * @param outgoingTransitions The list of transitions originating from this process node.
+ */
+ public void setOutgoingTransitions(List<Transition> outgoingTransitions) {
+ this.outgoingTransitions = outgoingTransitions;
+ }
+
+ /**
+ * Returns a list of transitions pointing from another process node to this one.
+ * @return A list of transitions (never {@code null} if process node comes from a process definition from {@link ProcessDefinitionParser}).
+ */
+ public List<Transition> getIncomingTransitions() {
+ return incomingTransitions;
+ }
+
+ /**
+ * Sets the list of transitions pointing from another process node to this one.
+ * @param incomingTransitions A list of transitions pointing to this process node.
+ */
+ public void setIncomingTransitions(List<Transition> incomingTransitions) {
+ this.incomingTransitions = incomingTransitions;
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/StartEvent.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/StartEvent.java
new file mode 100644
index 00000000..3d3111c2
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/StartEvent.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.model;
+
+import java.io.Serializable;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+/**
+ * Represents a start event. Each process description contains a single start event. Process execution starts with a
+ * start event.
+ *
+ * @author tknall
+ *
+ */
+public class StartEvent extends ProcessNode implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("StartEvent [");
+ if (getId() != null) {
+ builder.append("id=");
+ builder.append(getId());
+ }
+ if (CollectionUtils.isNotEmpty(getIncomingTransitions())) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("incomingTransitions=");
+ builder.append(getIncomingTransitions());
+ }
+ if (CollectionUtils.isNotEmpty(getOutgoingTransitions())) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("outgoingTransitions=");
+
+ builder.append(getOutgoingTransitions());
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/TaskInfo.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/TaskInfo.java
new file mode 100644
index 00000000..b7171236
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/TaskInfo.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.model;
+
+import java.io.Serializable;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+import at.gv.egiz.eaaf.core.api.idp.process.Task;
+
+/**
+ * Represents information about a single task to be performed upon process execution.
+ * @author tknall
+ *
+ */
+public class TaskInfo extends ProcessNode implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private static final boolean DEFAULT_ASYNC = false;
+
+ private String taskImplementingClass;
+ private boolean async = DEFAULT_ASYNC;
+
+ /**
+ * Determines if the task is marked asynchronous ({@code true}) or synchronous ({@code false}).
+ * @return A flag indicating if the task should be executed asynchronously or synchronously. (Default: {@code false})
+ */
+ public boolean isAsync() {
+ return async;
+ }
+
+ /**
+ * Marks a task to executed asynchronously ({@code true}) or synchronously ({@code false}).
+ * @param async The flag.
+ */
+ public void setAsync(boolean async) {
+ this.async = async;
+ }
+
+ /**
+ * Returns the class that implements the actual task (must implement {@link Task}).
+ * @return The task implementing class.
+ */
+ public String getTaskImplementingClass() {
+ return taskImplementingClass;
+ }
+
+ /**
+ * Sets the class that implements the actual task (must implement {@link Task}).
+ * @param taskImplementingClass The task implementing class.
+ */
+ public void setTaskImplementingClass(String taskImplementingClass) {
+ this.taskImplementingClass = taskImplementingClass;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (getId() != null) {
+ builder.append("id=");
+ builder.append(getId());
+ }
+ if (async != DEFAULT_ASYNC) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("async=");
+ builder.append(async);
+ }
+ if (taskImplementingClass != null) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("taskImplementingClass=");
+ builder.append(taskImplementingClass);
+ }
+ if (CollectionUtils.isNotEmpty(getIncomingTransitions())) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("incomingTransitions=");
+ builder.append(getIncomingTransitions());
+ }
+ if (CollectionUtils.isNotEmpty(getOutgoingTransitions())) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("outgoingTransitions=");
+ builder.append(getOutgoingTransitions());
+ }
+ builder.insert(0, "TaskInfo [");
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/Transition.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/Transition.java
new file mode 100644
index 00000000..7ac26c20
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/Transition.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.model;
+
+import java.io.Serializable;
+
+import at.gv.egiz.eaaf.core.impl.idp.process.ProcessDefinitionParser;
+
+/**
+ * Represents a single transition from a {@link StartEvent} or {@linkplain TaskInfo Task} to another
+ * {@linkplain TaskInfo Task} or {@link EndEvent}.
+ *
+ * @author tknall
+ *
+ */
+public class Transition implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private String id;
+ private String conditionExpression;
+ private ProcessNode from;
+ private ProcessNode to;
+
+ /**
+ * Returns the process node (effectively a {@link StartEvent} or {@linkplain TaskInfo Task}) the transition is
+ * pointing from.
+ *
+ * @return The transition's source process node (never {@code null} if transition comes from a process definition
+ * from {@link ProcessDefinitionParser}).
+ */
+ public ProcessNode getFrom() {
+ return from;
+ }
+
+ /**
+ * Sets the process node the transition is pointing from.
+ *
+ * @param from
+ * The transition's source process node.
+ */
+ public void setFrom(ProcessNode from) {
+ this.from = from;
+ }
+
+ /**
+ * Returns the process node (effectively a {@linkplain TaskInfo Task} or {@link EndEvent}) the transition is
+ * pointing to.
+ *
+ * @return The transition's destination process node (never {@code null} if transition comes from a process
+ * definition from {@link ProcessDefinitionParser}).
+ */
+ public ProcessNode getTo() {
+ return to;
+ }
+
+ /**
+ * Sets the process node the transition is pointing to.
+ *
+ * @param to
+ * The transition's destination process node.
+ */
+ public void setTo(ProcessNode to) {
+ this.to = to;
+ }
+
+ /**
+ * Returns the unique identifier of the transition.
+ *
+ * @return The unique identifier (may be {@code null}).
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the unique identifier of the transition.
+ *
+ * @param id
+ * The unique identifier.
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the condition expression for this transition.
+ *
+ * @return The condition expression (may be {@code null}).
+ */
+ public String getConditionExpression() {
+ return conditionExpression;
+ }
+
+ /**
+ * Sets the condition expression for this transition.
+ *
+ * @param conditionExpression
+ * The condition expression.
+ */
+ public void setConditionExpression(String conditionExpression) {
+ this.conditionExpression = conditionExpression;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (id != null) {
+ builder.append("id=");
+ builder.append(id);
+ }
+ if (from != null) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("from.id=");
+ builder.append(from.getId());
+ }
+ if (to != null) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("to.id=");
+ builder.append(to.getId());
+ }
+ if (conditionExpression != null) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append("conditionExpression=");
+ builder.append(conditionExpression);
+ }
+ builder.insert(0, "Transition [");
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/spring/SpringExpressionEvaluator.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/spring/SpringExpressionEvaluator.java
new file mode 100644
index 00000000..d301b77e
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/spring/SpringExpressionEvaluator.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.spring;
+
+import java.util.Objects;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.commons.lang3.BooleanUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import at.gv.egiz.eaaf.core.api.idp.process.ExpressionEvaluationContext;
+import at.gv.egiz.eaaf.core.api.idp.process.ExpressionEvaluator;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.Transition;
+
+/**
+ * Expression evaluator for processing {@link Transition} conditions allowing to reference Spring beans from the
+ * application context.
+ *
+ * @author tknall
+ *
+ */
+public class SpringExpressionEvaluator implements ExpressionEvaluator {
+
+ private Logger log = LoggerFactory.getLogger(getClass());
+ private ExpressionParser parser = new SpelExpressionParser();
+ private StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
+
+ @Autowired(required = false)
+ private ApplicationContext ctx;
+
+ @PostConstruct
+ private void init() {
+ if (ctx != null) {
+ evaluationContext.setBeanResolver(new BeanFactoryResolver(ctx));
+ }
+ }
+
+ @Override
+ public boolean evaluate(ExpressionEvaluationContext expressionContext, String expression) {
+ Objects.requireNonNull(expression, "Expression must not be null.");
+ log.trace("Evaluating '{}'.", expression);
+
+ Expression expr = parser.parseExpression(expression);
+ Boolean result = expr.getValue(evaluationContext, expressionContext, Boolean.class);
+ if (result == null) {
+ log.warn("Evaluation of '{}' results in null-value.", expression);
+ } else {
+ log.debug("Expression '{}' -> {}", expression, result);
+ }
+
+ return BooleanUtils.isTrue(result);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/AbstractAuthSourceServlet.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/AbstractAuthSourceServlet.java
new file mode 100644
index 00000000..095c3439
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/AbstractAuthSourceServlet.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.springweb;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.api.idp.process.ProcessEngine;
+import at.gv.egiz.eaaf.core.impl.idp.process.ProcessInstance;
+
+/**
+ * Abstract HttpServlet that provides means for retrieving the process engine (Spring Web required) as well as
+ * retrieving the underlying process instance and execution context evaluating a certain request parameter.
+ *
+ * @author tknall
+ *
+ */
+public abstract class AbstractAuthSourceServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private ProcessEngine processEngine;
+
+ /**
+ * Returns the name of the request parameter representing the respective instance id.
+ * <p/>Default is {@code processInstanceId}.
+ * @return The request parameter name.
+ */
+ public String getProcessInstanceIdParameterName() {
+ return "processInstanceId";
+ }
+
+ /**
+ * Returns the underlying process engine instance.
+ *
+ * @return The process engine (never {@code null}).
+ * @throws NoSuchBeanDefinitionException
+ * if no {@link ProcessEngine} bean was found.
+ * @throws NoUniqueBeanDefinitionException
+ * if more than one {@link ProcessEngine} bean was found.
+ * @throws BeansException
+ * if a problem getting the {@link ProcessEngine} bean occurred.
+ * @throws IllegalStateException
+ * if the Spring WebApplicationContext was not found, which means that the servlet is used outside a
+ * Spring web environment.
+ */
+ public synchronized ProcessEngine getProcessEngine() {
+ if (processEngine == null) {
+ WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
+ if (ctx == null) {
+ throw new IllegalStateException(
+ "Unable to find Spring WebApplicationContext. Servlet needs to be executed within a Spring web environment.");
+ }
+ processEngine = ctx.getBean(ProcessEngine.class);
+ }
+ return processEngine;
+ }
+
+ /**
+ * Retrieves the process instance referenced by the request parameter {@link #getProcessInstanceIdParameterName()}.
+ *
+ * @param request
+ * The HttpServletRequest.
+ * @return The process instance (never {@code null}).
+ * @throws NoSuchBeanDefinitionException
+ * if no {@link ProcessEngine} bean was found.
+ * @throws NoUniqueBeanDefinitionException
+ * if more than one {@link ProcessEngine} bean was found.
+ * @throws BeansException
+ * if a problem getting the {@link ProcessEngine} bean occurred.
+ * @throws IllegalStateException
+ * if the Spring WebApplicationContext was not found, which means that the servlet is used outside a
+ * Spring web environment.
+ * @throws IllegalArgumentException
+ * in case the process instance id referenced by the request parameter
+ * {@link #getProcessInstanceIdParameterName()} does not exist.
+ */
+ public ProcessInstance getProcessInstance(HttpServletRequest request) {
+ String processInstanceId = StringUtils.trimToNull(request.getParameter(getProcessInstanceIdParameterName()));
+ if (processInstanceId == null) {
+ throw new IllegalArgumentException("Missing request parameter '" + getProcessInstanceIdParameterName() + "'.");
+ }
+ return getProcessEngine().getProcessInstance(processInstanceId);
+ }
+
+ /**
+ * Retrieves the execution context for the respective process instance referenced by the request parameter
+ * {@link #getProcessInstanceIdParameterName()}.
+ *
+ * @param request
+ * The HttpServletRequest.
+ * @return The execution context (never {@code null}).
+ * @throws NoSuchBeanDefinitionException
+ * if no {@link ProcessEngine} bean was found.
+ * @throws NoUniqueBeanDefinitionException
+ * if more than one {@link ProcessEngine} bean was found.
+ * @throws BeansException
+ * if a problem getting the {@link ProcessEngine} bean occurred.
+ * @throws IllegalStateException
+ * if the Spring WebApplicationContext was not found, which means that the servlet is used outside a
+ * Spring web environment.
+ * @throws IllegalArgumentException
+ * in case the process instance id referenced by the request parameter
+ * {@link #getProcessInstanceIdParameterName()} does not exist.
+ */
+ public ExecutionContext getExecutionContext(HttpServletRequest request) {
+ return getProcessInstance(request).getExecutionContext();
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/AbstractTask.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/AbstractTask.java
new file mode 100644
index 00000000..446bf690
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/AbstractTask.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.springweb;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.filter.RequestContextFilter;
+
+import at.gv.egiz.eaaf.core.api.IRequest;
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.api.idp.process.Task;
+import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;
+
+/**
+ * Abstract task implementation providing {@link HttpServletRequest} and {@link HttpServletResponse}.
+ * <p/>
+ * Note that this abstract task requires the Spring (web) framework including a {@link RequestContextFilter} to be set
+ * within {@code web.xml}.
+ *
+ * <pre>
+ * ...
+ * &lt;filter&gt;
+ * &lt;filter-name&gt;requestContextFilter&lt;/filter-name&gt;
+ * &lt;filter-class&gt;org.springframework.web.filter.RequestContextFilter&lt;/filter-class&gt;
+ * &lt;/filter&gt;
+ * &lt;filter-mapping&gt;
+ * &lt;filter-name&gt;requestContextFilter&lt;/filter-name&gt;
+ * &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+ * &lt;/filter-mapping&gt;
+ * ...
+ * </pre>
+ *
+ * @author tknall
+ * @author tlenz
+ *
+ */
+public abstract class AbstractTask implements Task {
+
+ /**
+ * Executes the task providing the underlying {@link ExecutionContext} {@code executionContext} as well as the
+ * respective {@link HttpServletRequest} and {@link HttpServletResponse}.
+ *
+ * @param executionContext
+ * The execution context (never {@code null}).
+ * @param request
+ * The HttpServletRequest (never {@code null}).
+ * @param response
+ * The HttpServletResponse (never {@code null}).
+ * @throws IllegalStateException
+ * Thrown in case the task is nur being run within the required environment. Refer to javadoc for
+ * further information.
+ * @throws Exception
+ * Thrown in case of error executing the task.
+ */
+ public abstract void execute(ExecutionContext executionContext, HttpServletRequest request,
+ HttpServletResponse response) throws TaskExecutionException;
+
+ /**
+ * Executes the task providing the underlying {@link ExecutionContext} {@code executionContext}
+ * and the {@link IRequest} {@code pendingReq }as well as the
+ * respective {@link HttpServletRequest} and {@link HttpServletResponse}.
+ *
+ * This method sets the pending-request object of the task implementation and starts the
+ * {@code execute} method of the task
+ *
+ * @param pendingReq The pending-request object (never {@code null}).
+ * @param executionContext The execution context (never {@code null}).
+ * @param request The HttpServletRequest (never {@code null}).
+ * @param response The HttpServletResponse (never {@code null}).
+ * @return The pending-request object, because Process-management works recursive
+ *
+ * @throws IllegalStateException
+ * Thrown in case the task is being run within the required environment. Refer to javadoc for
+ * further information.
+ * @throws Exception
+ * Thrown in case of error executing the task.
+ */
+ protected abstract IRequest internalExecute(IRequest pendingReq, ExecutionContext executionContext, HttpServletRequest request,
+ HttpServletResponse response) throws TaskExecutionException;
+
+ @Override
+ public IRequest execute(IRequest pendingReq, ExecutionContext executionContext) throws TaskExecutionException {
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ if (requestAttributes != null && requestAttributes instanceof ServletRequestAttributes) {
+ HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
+ HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
+ if (request == null || response == null) {
+ throw new IllegalStateException(
+ "Spring's RequestContextHolder did not provide HttpServletResponse. Did you forget to set the required org.springframework.web.filter.RequestContextFilter in your web.xml.");
+ }
+ return internalExecute(pendingReq, executionContext, request, response);
+ } else {
+ throw new IllegalStateException("Task needs to be executed within a Spring web environment.");
+ }
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/SpringWebExpressionEvaluator.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/SpringWebExpressionEvaluator.java
new file mode 100644
index 00000000..229735e3
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/SpringWebExpressionEvaluator.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.springweb;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.BooleanUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;
+import at.gv.egiz.eaaf.core.api.idp.process.ExpressionEvaluationContext;
+import at.gv.egiz.eaaf.core.api.idp.process.ExpressionEvaluator;
+import at.gv.egiz.eaaf.core.impl.idp.process.model.Transition;
+
+/**
+ * Expression evaluator for processing {@link Transition} conditions allowing to
+ * <ul>
+ * <li>reference Spring beans from the application context using {@code @myBeanName...},</li>
+ * <li>{@link ExecutionContext} properties using {@code ctx['property']},</li>
+ * <li>Multi valued {@link HttpServletRequest} parameters using {@code requestParameters['foo']} (keep in mind that this
+ * expression returns an array of String values) and</li>
+ * <li>Single valued {@link HttpServletRequest} parameters using {@code requestParameter['foo']}</li>
+ * </ul>
+ *
+ * @author tknall
+ *
+ */
+public class SpringWebExpressionEvaluator implements ExpressionEvaluator {
+
+ private Logger log = LoggerFactory.getLogger(getClass());
+ private ExpressionParser parser = new SpelExpressionParser();
+ private StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
+
+ @Autowired(required = false)
+ private ApplicationContext ctx;
+
+ @Autowired(required = false)
+ private HttpServletRequest request;
+
+ @PostConstruct
+ private void init() {
+ if (ctx != null) {
+ evaluationContext.setBeanResolver(new BeanFactoryResolver(ctx));
+ }
+ }
+
+ /**
+ * Evaluation context that provides access to {@link HttpServletRequest} parameters using
+ * {@code requestParameter['foo']} for single value parameters or {@code requestParameters['foo']} for multi value
+ * parameters. Basic calls to {@code ctx} will be delegated.
+ *
+ * @author tknall
+ *
+ */
+ private class SpringWebExpressionEvaluationContext implements ExpressionEvaluationContext {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new expression evaluation context, providing access to HttpServletRequest parameter(s).
+ *
+ * @param delegate
+ * The original {@link ExpressionEvaluationContext} to be delegated to for {@code ctx['foo']}
+ * expressions.
+ */
+ public SpringWebExpressionEvaluationContext(ExpressionEvaluationContext delegate) {
+ this.delegate = delegate;
+ }
+
+ private ExpressionEvaluationContext delegate;
+
+ @Override
+ public Map<String, Serializable> getCtx() {
+ return delegate.getCtx();
+ }
+
+ @SuppressWarnings("unused")
+ public Map<String, String> getRequestParameter() {
+ if (request != null) {
+ Map<String, String> singleValueMap = new HashMap<String, String>();
+ Iterator<Entry<String, String[]>> it = request.getParameterMap().entrySet().iterator();
+ while (it.hasNext()) {
+ Entry<String, String[]> entry = it.next();
+ if (ArrayUtils.isNotEmpty(entry.getValue())) {
+ singleValueMap.put(entry.getKey(), entry.getValue()[0]);
+ }
+ }
+ return singleValueMap;
+ } else {
+ return Collections.<String, String> emptyMap();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public Map<String, String[]> getRequestParameters() {
+ if (request != null) {
+ return request.getParameterMap();
+ } else {
+ return Collections.<String, String[]> emptyMap();
+ }
+ }
+
+ }
+
+ @Override
+ public boolean evaluate(ExpressionEvaluationContext expressionContext, String expression) {
+ Objects.requireNonNull(expression, "Expression must not be null.");
+ log.trace("Evaluating '{}'.", expression);
+
+ Expression expr = parser.parseExpression(expression);
+ Boolean result = null;
+ try {
+ result = expr.getValue(evaluationContext, new SpringWebExpressionEvaluationContext(expressionContext),
+ Boolean.class);
+ if (result == null) {
+ log.warn("Evaluation of '{}' results in null-value.", expression);
+ } else {
+ log.debug("Expression '{}' -> {}", expression, result);
+ }
+ } catch (Exception e) {
+ log.warn("Expression '{}' could not be processed.", expression, e);
+ }
+
+ return BooleanUtils.isTrue(result);
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/support/SecureRandomHolder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/support/SecureRandomHolder.java
new file mode 100644
index 00000000..0f581dae
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/support/SecureRandomHolder.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ *******************************************************************************/
+package at.gv.egiz.eaaf.core.impl.idp.process.support;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+/**
+ * Holder for a secure random instance following the initialization on demand holder design pattern. The secure random
+ * instance is a singleton that is initialized on first usage.
+ *
+ * @author tknall
+ *
+ */
+public class SecureRandomHolder {
+
+ private SecureRandomHolder() {
+ }
+
+ private static final SecureRandom SRND_INSTANCE;
+ static {
+ try {
+ SRND_INSTANCE = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Unable to instantiate SHA1PRNG.", e);
+ }
+ }
+
+ /**
+ * Returns a secure random generator instance.
+ * @return The secure random instance.
+ */
+ public static SecureRandom getInstance() {
+ return SecureRandomHolder.SRND_INSTANCE;
+ }
+
+} \ No newline at end of file