From bee5dd259a4438d45ecd1bcc26dfba12875236d6 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Tue, 26 Jun 2018 11:03:48 +0200 Subject: initial commit --- .../eaaf/core/impl/idp/AuthenticationData.java | 416 ++++++++++++++++++ .../impl/idp/EAAFCoreSpringResourceProvider.java | 30 ++ .../idp/auth/AbstractAuthenticationManager.java | 341 +++++++++++++++ .../eaaf/core/impl/idp/auth/RequestStorage.java | 119 ++++++ .../builder/AbstractAuthenticationDataBuilder.java | 467 +++++++++++++++++++++ .../core/impl/idp/auth/builder/BPKBuilder.java | 302 +++++++++++++ .../impl/idp/auth/data/AuthProcessDataWrapper.java | 219 ++++++++++ .../eaaf/core/impl/idp/auth/data/IdentityLink.java | 312 ++++++++++++++ .../data/SimpleIdentityLinkAssertionParser.java | 326 ++++++++++++++ .../idp/auth/modules/AbstractAuthServletTask.java | 220 ++++++++++ .../impl/idp/auth/modules/ModuleRegistration.java | 151 +++++++ .../builder/attributes/BPKAttributeBuilder.java | 55 +++ .../attributes/BirthdateAttributeBuilder.java | 40 ++ .../builder/attributes/EIDIdentityLinkBuilder.java | 54 +++ .../EIDIssuingNationAttributeBuilder.java | 35 ++ .../attributes/EIDSectorForIDAttributeBuilder.java | 36 ++ .../impl/idp/builder/attributes/EIDSourcePIN.java | 39 ++ .../idp/builder/attributes/EIDSourcePINType.java | 33 ++ .../EIDeIDASQAALevelAttributeBuilder.java | 31 ++ .../attributes/GivenNameAttributeBuilder.java | 26 ++ .../attributes/PVPVersionAttributeBuilder.java | 26 ++ .../attributes/PrincipalNameAttributeBuilder.java | 26 ++ .../impl/idp/conf/AbstractConfigurationImpl.java | 185 ++++++++ .../core/impl/idp/conf/SPConfigurationImpl.java | 163 +++++++ .../AbstractAuthProtocolModulController.java | 225 ++++++++++ .../impl/idp/controller/AbstractController.java | 354 ++++++++++++++++ .../AbstractProcessEngineSignalController.java | 97 +++++ .../controller/ProtocolFinalizationController.java | 178 ++++++++ .../impl/idp/controller/protocols/RequestImpl.java | 386 +++++++++++++++++ .../tasks/FinalizeAuthenticationTask.java | 57 +++ .../tasks/RestartAuthProzessManagement.java | 92 ++++ .../impl/idp/process/ExecutionContextImpl.java | 81 ++++ .../process/ExpressionEvaluationContextImpl.java | 46 ++ .../impl/idp/process/ProcessDefinitionParser.java | 226 ++++++++++ .../process/ProcessDefinitionParserException.java | 37 ++ .../core/impl/idp/process/ProcessEngineImpl.java | 424 +++++++++++++++++++ .../core/impl/idp/process/ProcessInstance.java | 166 ++++++++ .../impl/idp/process/ProcessInstanceState.java | 32 ++ .../impl/idp/process/dao/ProcessInstanceStore.java | 75 ++++ .../process/dao/ProcessInstanceStoreDAOImpl.java | 73 ++++ .../eaaf/core/impl/idp/process/model/EndEvent.java | 44 ++ .../impl/idp/process/model/ProcessDefinition.java | 160 +++++++ .../core/impl/idp/process/model/ProcessNode.java | 71 ++++ .../core/impl/idp/process/model/StartEvent.java | 47 +++ .../eaaf/core/impl/idp/process/model/TaskInfo.java | 96 +++++ .../core/impl/idp/process/model/Transition.java | 138 ++++++ .../process/spring/SpringExpressionEvaluator.java | 63 +++ .../springweb/AbstractAuthSourceServlet.java | 118 ++++++ .../impl/idp/process/springweb/AbstractTask.java | 101 +++++ .../springweb/SpringWebExpressionEvaluator.java | 145 +++++++ .../idp/process/support/SecureRandomHolder.java | 37 ++ 51 files changed, 7221 insertions(+) create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/AuthenticationData.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/EAAFCoreSpringResourceProvider.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/AuthProcessDataWrapper.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/IdentityLink.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/SimpleIdentityLinkAssertionParser.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/BPKAttributeBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/BirthdateAttributeBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDIdentityLinkBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDIssuingNationAttributeBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSectorForIDAttributeBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSourcePIN.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDSourcePINType.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/EIDeIDASQAALevelAttributeBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/GivenNameAttributeBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/PVPVersionAttributeBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/builder/attributes/PrincipalNameAttributeBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/AbstractConfigurationImpl.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/conf/SPConfigurationImpl.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractAuthProtocolModulController.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractProcessEngineSignalController.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/ProtocolFinalizationController.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/tasks/FinalizeAuthenticationTask.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/tasks/RestartAuthProzessManagement.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ExecutionContextImpl.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ExpressionEvaluationContextImpl.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessDefinitionParser.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessDefinitionParserException.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessEngineImpl.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessInstance.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/ProcessInstanceState.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/dao/ProcessInstanceStore.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/dao/ProcessInstanceStoreDAOImpl.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/EndEvent.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/ProcessDefinition.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/ProcessNode.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/StartEvent.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/TaskInfo.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/model/Transition.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/spring/SpringExpressionEvaluator.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/AbstractAuthSourceServlet.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/AbstractTask.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/springweb/SpringWebExpressionEvaluator.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/process/support/SecureRandomHolder.java (limited to 'eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp') 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 genericDataStorate = new HashedMap(); + + 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 getGenericData(String key, final Class 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 reqParameterWhiteListeForModules = new ArrayList(); + private static List reqHeaderWhiteListeForModules = new ArrayList(); + + 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 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 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 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(); + + //#################################################### + //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 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 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 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 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 getEncryptedbPKFromPVPAttribute(IAuthProcessDataContainer authProcessDataContainer, + AuthenticationData authData, ISPConfiguration spConfig) throws EAAFBuilderException; + + //request baseId from SRZ + protected abstract Pair getbaseIDFromSZR(AuthenticationData authData, String notValidbPK, + String notValidbPKType); + + + protected Pair buildOAspecificbPK(IRequest pendingReq, AuthenticationData authData) throws EAAFBuilderException { + ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration(); + + String baseID = authData.getIdentificationValue(); + String baseIDType = authData.getIdentificationType(); + Pair 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
+ * If bPK-Type is null the result is false. + * + * @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 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'
session.getGenericDataFromSession(PVPConstants.BPK_NAME, String.class)
+ * + * @param session MOASession, but never null + * @return bPK, which was received by PVP-Attribute, or null 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'
session.getGenericDataFromSession(PVPConstants.EID_SECTOR_FOR_IDENTIFIER_NAME, String.class)
+ * + * @param session MOASession, but never null + * @return bPKType, which was received by PVP-Attribute, or null 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 + * "Ableitung f¨r die bereichsspezifische Personenkennzeichnung" + * version 1.0.1 from "reference.e-government.gv.at". + * + */ +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 but never null + * @throws EAAFBuilderException if some input data are not valid + */ + public Pair 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 but never null + * @throws EAAFBuilderException if some input data are not valid + */ + public Pair 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 in a BASE64 encoding + * @throws EAAFBuilderException if some input data are not valid + */ + private Pair 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 authProcessData; + + public AuthProcessDataWrapper(Map 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 getGenericSessionDataStorage() { + Map result = new HashMap(); + 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 getGenericDataFromSession(String key, Class 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 wrapStringObject(String key, Object defaultValue, Class 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. + *
"IdentityLink" is the translation of "Personenbindung". + * + * @author Paul Ivancsics + * @version $Id$ + */ +public class IdentityLink implements Serializable, IIdentityLink{ + + private static final long serialVersionUID = 1L; + + /** + * "identificationValue" is the translation of "Stammzahl". + */ + private String identificationValue; + /** + * "identificationType" 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 <saml:Assertion> + *
+ * This IDL parser extract NO key information! + + */ +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 IdentityLinkAssertionParser. + * A DOM-representation of the incoming String will be created + * @param xmlAssertion <saml:Assertion> 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 IdentityLinkAssertionParser. + * A DOM-representation of the incoming Inputstream will be created + * @param xmlAssertion <saml:Assertion> 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 <saml:Assertion> + * @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 ' + givenname = givenname.replaceAll("'", "'"); + familyname = familyname.replaceAll("'", "'"); + + 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 <InfoboxReadResponse> 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.).

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 getParameters(HttpServletRequest req) throws IOException, + FileUploadException { + + Map parameters = new HashMap(); + + 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 = ""; + String endS = "urn:publicid:gv.at:baseid"; + 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> requestParamIt = req.getParameterMap().entrySet().iterator(); + while (requestParamIt.hasNext()) { + Entry 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 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 loader = ServiceLoader.load(AuthModule.class); + Iterator 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 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() { + @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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator 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 buildEmpty(IAttributeGenerator 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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator 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 buildEmpty(IAttributeGenerator 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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator 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 buildEmpty(IAttributeGenerator 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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator 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 buildEmpty(IAttributeGenerator 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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator 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 buildEmpty(IAttributeGenerator 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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator 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 buildEmpty(IAttributeGenerator 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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator 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 buildEmpty(IAttributeGenerator 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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator g) throws AttributeBuilderException { + + return g.buildStringAttribute(EID_CITIZEN_EIDAS_QAA_LEVEL_FRIENDLY_NAME, + EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, authData.getEIDASQAALevel()); + } + + + public ATT buildEmpty(IAttributeGenerator 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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator g) throws AttributeBuilderException { + return g.buildStringAttribute(GIVEN_NAME_FRIENDLY_NAME, GIVEN_NAME_NAME, authData.getGivenName()); + } + + public ATT buildEmpty(IAttributeGenerator 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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator g) throws AttributeBuilderException { + return g.buildStringAttribute(PVP_VERSION_FRIENDLY_NAME, PVP_VERSION_NAME, PVP_VERSION_2_1); + } + + public ATT buildEmpty(IAttributeGenerator 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 build(ISPConfiguration oaParam, IAuthData authData, + IAttributeGenerator g) throws AttributeBuilderException { + return g.buildStringAttribute(PRINCIPAL_NAME_FRIENDLY_NAME, PRINCIPAL_NAME_NAME, authData.getFamilyName()); + } + + public ATT buildEmpty(IAttributeGenerator 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 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 spConfiguration; + private final List targetAreasWithNoInteralBaseIdRestriction; + private final List targetAreasWithNoBaseIdTransmissionRestriction; + + + public SPConfigurationImpl(final Map 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 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 getTargetsWithNoBaseIdInternalProcessingRestriction() { + return this.targetAreasWithNoInteralBaseIdRestriction; + } + + + @Override + public final List 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 + * + *

+ * 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 genericDataStorage = new HashMap(); + + + + /** + * @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 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 genericFullDataStorage() { + return this.genericDataStorage; + + } + + public final ISPConfiguration getServiceProviderConfiguration() { + return this.spConfiguration; + + + } + + public T getServiceProviderConfiguration(final Class 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 getGenericData(String key, final Class 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 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 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 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 ctxData = Collections.synchronizedMap(new HashMap()); + + 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 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 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 keys = executionContext.keySet(); + ctxData = Collections.synchronizedMap(new HashMap(keys.size())); + for (String key : keys) { + ctxData.put(key, executionContext.get(key)); + } + } + + @Override + public Map 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. + *

transitionElements = new ArrayList<>(); + final List 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 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 processDefinitions = new ConcurrentHashMap(); + + 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 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() { + @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 ctxData = new HashMap(); + 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 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.

+ * 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"): + *

    + *
  • unique value
  • + *
  • never changes (immutable)
  • + *
  • never {@code null}
  • + *
+ */ + + private String processInstanceId; + + private String processDefinitionId; + + private String nextTaskId; + + private ProcessInstanceState processState; + + private Map 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 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 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 + *
    + *
  • a {@link StartEvent},
  • + *
  • one or more {@linkplain TaskInfo Tasks},
  • + *
  • one or more {@linkplain EndEvent EndEvents} and
  • + *
  • some {@linkplain Transition Transitions} linking StartEvents, Tasks and EndEvents. + *
+ * + * @author tknall + * + */ +public class ProcessDefinition { + + private String id; + private StartEvent startEvent; + private Map taskInfos = new LinkedHashMap<>(); + private Map 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 getTaskInfos() { + return taskInfos; + } + + /** + * Sets the map containing the tasks. + * + * @param taskInfos + * The map containing the tasks. + */ + public void setTaskInfos(Map 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 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 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 outgoingTransitions = new ArrayList<>(); + private List 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 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 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 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 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. + *

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}. + *

+ * Note that this abstract task requires the Spring (web) framework including a {@link RequestContextFilter} to be set + * within {@code web.xml}. + * + *

+ * ...
+ * <filter>
+ *   <filter-name>requestContextFilter</filter-name>
+ *   <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
+ * </filter>
+ * <filter-mapping>
+ *   <filter-name>requestContextFilter</filter-name>
+ *   <url-pattern>/*</url-pattern>
+ * </filter-mapping>
+ * ...
+ * 
+ * + * @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 + *
    + *
  • reference Spring beans from the application context using {@code @myBeanName...},
  • + *
  • {@link ExecutionContext} properties using {@code ctx['property']},
  • + *
  • Multi valued {@link HttpServletRequest} parameters using {@code requestParameters['foo']} (keep in mind that this + * expression returns an array of String values) and
  • + *
  • Single valued {@link HttpServletRequest} parameters using {@code requestParameter['foo']}
  • + *
+ * + * @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 getCtx() { + return delegate.getCtx(); + } + + @SuppressWarnings("unused") + public Map getRequestParameter() { + if (request != null) { + Map singleValueMap = new HashMap(); + Iterator> it = request.getParameterMap().entrySet().iterator(); + while (it.hasNext()) { + Entry entry = it.next(); + if (ArrayUtils.isNotEmpty(entry.getValue())) { + singleValueMap.put(entry.getKey(), entry.getValue()[0]); + } + } + return singleValueMap; + } else { + return Collections. emptyMap(); + } + } + + @SuppressWarnings("unused") + public Map getRequestParameters() { + if (request != null) { + return request.getParameterMap(); + } else { + return Collections. 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 -- cgit v1.2.3