From 6f20166cf353b777da949d64445521ebf56a5cfd Mon Sep 17 00:00:00 2001 From: Michael Simon <simon@kit.edu> Date: Wed, 25 Sep 2024 11:58:24 +0200 Subject: [PATCH] ISSUE-196 implement first attribute builder and simple transcoder --- .../attribute/AttributeReleaseEntity.java | 23 +++ .../entity/attribute/value/ValueEntity.java | 12 ++ .../oidc/OidcOpScopeLoginProcessor.java | 8 +- .../webapp/user/show-attribute-release.xhtml | 3 + .../webapp/user/show-attribute-releases.xhtml | 3 + .../proc/IdentityValuesProcessor.java | 1 + .../attribute/release/AttributeBuilder.java | 173 ++++++++++++++++++ .../release/AttributeReleaseHandler.java | 29 +-- regapp-saml-idp/pom.xml | 5 + .../saml/idp/AbstractValueTranscoder.java | 32 ++++ .../saml/idp/AttributeAuthorityService.java | 61 ++++-- .../saml/idp/SamlAttributeTranscoder.java | 81 ++++++++ .../saml/idp/SingleStringValueTranscoder.java | 33 ++++ .../scc/webreg/saml/idp/ValueTranscoder.java | 9 + regapp-saml-sp/pom.xml | 5 + .../service/impl/AttributeMapHelper.java | 5 +- 16 files changed, 439 insertions(+), 44 deletions(-) create mode 100644 regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/release/AttributeBuilder.java create mode 100644 regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AbstractValueTranscoder.java create mode 100644 regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/SamlAttributeTranscoder.java create mode 100644 regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/SingleStringValueTranscoder.java create mode 100644 regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/ValueTranscoder.java diff --git a/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/attribute/AttributeReleaseEntity.java b/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/attribute/AttributeReleaseEntity.java index 7a92e5ba2..976fde746 100644 --- a/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/attribute/AttributeReleaseEntity.java +++ b/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/attribute/AttributeReleaseEntity.java @@ -14,6 +14,7 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; +import jakarta.persistence.Transient; @Entity(name = "AttributeReleaseEntity") @Table(name = "attribute_release") @@ -40,6 +41,12 @@ public class AttributeReleaseEntity extends AbstractBaseEntity { @OneToMany(mappedBy = "attributeRelease") private Set<ValueEntity> values = new HashSet<>(); + @Transient + private Boolean changed; + + @Transient + private Set<ValueEntity> valuesToDelete; + public AttributeConsumerEntity getAttributeConsumer() { return attributeConsumer; } @@ -87,4 +94,20 @@ public class AttributeReleaseEntity extends AbstractBaseEntity { public void setValues(Set<ValueEntity> values) { this.values = values; } + + public Boolean getChanged() { + return changed; + } + + public void setChanged(Boolean changed) { + this.changed = changed; + } + + public Set<ValueEntity> getValuesToDelete() { + return valuesToDelete; + } + + public void setValuesToDelete(Set<ValueEntity> valuesToDelete) { + this.valuesToDelete = valuesToDelete; + } } diff --git a/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/attribute/value/ValueEntity.java b/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/attribute/value/ValueEntity.java index 9a212a82f..a37d8e317 100644 --- a/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/attribute/value/ValueEntity.java +++ b/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/attribute/value/ValueEntity.java @@ -16,6 +16,7 @@ import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import jakarta.persistence.Transient; @Entity(name = "ValueEntity") @Table(name = "value") @@ -42,6 +43,9 @@ public class ValueEntity extends AbstractBaseEntity { @ManyToMany(mappedBy = "nextValues") private Set<ValueEntity> prevValues = new HashSet<>(); + + @Transient + private Boolean changed; public AttributeEntity getAttribute() { return attribute; @@ -90,4 +94,12 @@ public class ValueEntity extends AbstractBaseEntity { public void setEndValue(Boolean endValue) { this.endValue = endValue; } + + public Boolean getChanged() { + return changed; + } + + public void setChanged(Boolean changed) { + this.changed = changed; + } } diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/OidcOpScopeLoginProcessor.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/OidcOpScopeLoginProcessor.java index 1a34aabe6..c61205c83 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/OidcOpScopeLoginProcessor.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/OidcOpScopeLoginProcessor.java @@ -24,6 +24,7 @@ import edu.kit.scc.webreg.entity.oidc.OidcOpConfigurationEntity; import edu.kit.scc.webreg.entity.oidc.ProjectOidcClientConfigurationEntity; import edu.kit.scc.webreg.entity.project.ProjectEntity; import edu.kit.scc.webreg.entity.project.ProjectMembershipEntity; +import edu.kit.scc.webreg.service.attribute.release.AttributeBuilder; import edu.kit.scc.webreg.service.attribute.release.AttributeReleaseHandler; import edu.kit.scc.webreg.service.saml.exc.OidcAuthenticationException; import jakarta.enterprise.context.ApplicationScoped; @@ -42,6 +43,9 @@ public class OidcOpScopeLoginProcessor extends AbstractOidcOpLoginProcessor { @Inject private AttributeReleaseHandler attributeReleaseHandler; + @Inject + private AttributeBuilder attributeBuilder; + @Inject private ProjectDao projectDao; @@ -79,7 +83,7 @@ public class OidcOpScopeLoginProcessor extends AbstractOidcOpLoginProcessor { } } - AttributeReleaseEntity attributeRelease = attributeReleaseHandler.requestAttributeRelease(clientConfig, + AttributeReleaseEntity attributeRelease = attributeBuilder.requestAttributeRelease(clientConfig, identity); flowState.setAttributeRelease(attributeRelease); flowState.setIdentity(identity); @@ -143,7 +147,7 @@ public class OidcOpScopeLoginProcessor extends AbstractOidcOpLoginProcessor { public JSONObject buildUserInfo(OidcFlowStateEntity flowState, OidcOpConfigurationEntity opConfig, OidcClientConsumerEntity clientConfig, HttpServletResponse response) throws OidcAuthenticationException { IdentityEntity identity = flowState.getIdentity(); - AttributeReleaseEntity attributeRelease = attributeReleaseHandler.requestAttributeRelease(clientConfig, + AttributeReleaseEntity attributeRelease = attributeBuilder.requestAttributeRelease(clientConfig, identity); if (! ReleaseStatusType.GOOD.equals(attributeRelease.getReleaseStatus())) { diff --git a/bwreg-webapp/src/main/webapp/user/show-attribute-release.xhtml b/bwreg-webapp/src/main/webapp/user/show-attribute-release.xhtml index 4a9aab1cd..27226113d 100644 --- a/bwreg-webapp/src/main/webapp/user/show-attribute-release.xhtml +++ b/bwreg-webapp/src/main/webapp/user/show-attribute-release.xhtml @@ -41,6 +41,9 @@ <p:outputPanel rendered="#{showAttributeReleaseBean.release.attributeConsumer.class.simpleName == 'ProjectOidcClientConfigurationEntity'}"> <b><h:outputText value="#{showAttributeReleaseBean.release.attributeConsumer.displayName}" /></b> </p:outputPanel> + <p:outputPanel rendered="#{showAttributeReleaseBean.release.attributeConsumer.class.simpleName == 'SamlSpMetadataEntity'}"> + <b><h:outputText value="#{showAttributeReleaseBean.release.attributeConsumer.entityId}" /></b> + </p:outputPanel> <h:outputText value="#{messages['attribute_release.actual_release_status']}: " /> <p:outputPanel> diff --git a/bwreg-webapp/src/main/webapp/user/show-attribute-releases.xhtml b/bwreg-webapp/src/main/webapp/user/show-attribute-releases.xhtml index 7805c5917..f32a77e6a 100644 --- a/bwreg-webapp/src/main/webapp/user/show-attribute-releases.xhtml +++ b/bwreg-webapp/src/main/webapp/user/show-attribute-releases.xhtml @@ -49,6 +49,9 @@ <p:outputPanel rendered="#{release.attributeConsumer.class.simpleName == 'ProjectOidcClientConfigurationEntity'}"> <h:outputText value="#{release.attributeConsumer.displayName}"/> </p:outputPanel> + <p:outputPanel rendered="#{release.attributeConsumer.class.simpleName == 'SamlSpMetadataEntity'}"> + <h:outputText value="#{release.attributeConsumer.entityId}"/> + </p:outputPanel> </p:column> <p:column width="10%"> <p:link value="#{messages['details']}" href="./show-attribute-release.xhtml"> diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/IdentityValuesProcessor.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/IdentityValuesProcessor.java index e95628c88..21b8f57c3 100644 --- a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/IdentityValuesProcessor.java +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/IdentityValuesProcessor.java @@ -29,6 +29,7 @@ public class IdentityValuesProcessor { new StringListMergeValueProcessor("voperson_external_affiliation", "eduperson_affiliation"), new StringListMergeValueProcessor("eduperson_assurance", "eduperson_assurance"), new StringListMergeAuthorityValueProcessor("eduperson_entitlement", "eduperson_entitlement"), + new SingleStringMergeValueProcessor("eduperson_principal_name", "eduperson_principal_name"), new SingleStringMergeValueProcessor("family_name", "family_name"), new SingleStringMergeValueProcessor("given_name", "given_name")); } diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/release/AttributeBuilder.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/release/AttributeBuilder.java new file mode 100644 index 000000000..998cf37f2 --- /dev/null +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/release/AttributeBuilder.java @@ -0,0 +1,173 @@ +package edu.kit.scc.webreg.service.attribute.release; + +import static edu.kit.scc.webreg.dao.ops.RqlExpressions.and; +import static edu.kit.scc.webreg.dao.ops.RqlExpressions.equal; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import org.slf4j.Logger; + +import edu.kit.scc.webreg.dao.jpa.attribute.AttributeReleaseDao; +import edu.kit.scc.webreg.dao.jpa.attribute.OutgoingAttributeDao; +import edu.kit.scc.webreg.dao.jpa.attribute.ValueDao; +import edu.kit.scc.webreg.entity.attribute.AttributeConsumerEntity; +import edu.kit.scc.webreg.entity.attribute.AttributeEntity; +import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity; +import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity_; +import edu.kit.scc.webreg.entity.attribute.OutgoingAttributeEntity; +import edu.kit.scc.webreg.entity.attribute.OutgoingAttributeEntity_; +import edu.kit.scc.webreg.entity.attribute.ReleaseStatusType; +import edu.kit.scc.webreg.entity.attribute.value.StringListValueEntity; +import edu.kit.scc.webreg.entity.attribute.value.StringValueEntity; +import edu.kit.scc.webreg.entity.attribute.value.ValueEntity; +import edu.kit.scc.webreg.entity.attribute.value.ValueEntity_; +import edu.kit.scc.webreg.entity.identity.IdentityEntity; +import edu.kit.scc.webreg.service.identity.IdentityAttributeResolver; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class AttributeBuilder { + + @Inject + private Logger logger; + + @Inject + private AttributeReleaseDao attributeReleaseDao; + + @Inject + private OutgoingAttributeDao outgoingAttributeDao; + + @Inject + private IdentityAttributeResolver attributeResolver; + + @Inject + private ValueDao valueDao; + + public AttributeReleaseEntity requestAttributeRelease(AttributeConsumerEntity attributeConsumer, + IdentityEntity identity) { + final AttributeReleaseEntity attributeRelease = resolveAttributeRelease(attributeConsumer, identity); + return attributeRelease; + } + + public void addSingleStringAttribute(AttributeReleaseEntity attributeRelease, String name, + IdentityEntity identity) { + addSingleStringAttribute(attributeRelease, name, attributeResolver.resolveSingleStringValue(identity, name)); + } + + public void addSingleStringAttribute(AttributeReleaseEntity attributeRelease, String name, String value) { + if (value != null) + setSingleStringValue(attributeRelease, name, value); + } + + public void addStringListAttribute(AttributeReleaseEntity attributeRelease, String name, IdentityEntity identity) { + setStringListValue(attributeRelease, name, attributeResolver.resolveStringListValue(identity, name)); + } + + public void deleteValue(ValueEntity value) { + value.getAttributeRelease().setChanged(true); + value.getAttributeRelease().getValues().remove(value); + for (ValueEntity v : value.getPrevValues()) { + v.getNextValues().remove(value); + } + valueDao.delete(value); + } + + private void setStringListValue(AttributeReleaseEntity attributeRelease, String name, List<String> valueList) { + final OutgoingAttributeEntity attribute = findOrCreateOutgroingAttribute(name); + StringListValueEntity value = (StringListValueEntity) resolveValue(attributeRelease, attribute, + StringListValueEntity.class); + // Null check, because with a new value it will be null + // or the value changed + if (value.getValueList() == null || !(new HashSet<>(value.getValueList())).equals(new HashSet<>(valueList))) { + value.setValueList(new ArrayList<>(valueList)); + value.setChanged(true); + attributeRelease.setChanged(true); + attributeRelease.getValuesToDelete().remove(value); + } + // The value exists, but stays the same + else { + attributeRelease.getValuesToDelete().remove(value); + } + } + + private void setSingleStringValue(AttributeReleaseEntity attributeRelease, String name, String valueString) { + final OutgoingAttributeEntity attribute = findOrCreateOutgroingAttribute(name); + StringValueEntity value = (StringValueEntity) resolveValue(attributeRelease, attribute, + StringValueEntity.class); + // Null check, because with a new value it will be null + // or the value changed + if ((value.getValueString() == null && valueString != null) + || !value.getValueString().equals(valueString)) { + // The value differs from the old value. Set the new value and set the changed + // attribute + value.setValueString(valueString); + value.setChanged(true); + attributeRelease.setChanged(true); + attributeRelease.getValuesToDelete().remove(value); + } + // The value exists, but stays the same + else { + attributeRelease.getValuesToDelete().remove(value); + } + } + + private ValueEntity resolveValue(AttributeReleaseEntity attributeRelease, AttributeEntity attribute, + Class<? extends ValueEntity> desiredClass) { + ValueEntity value = valueDao.find( + and(equal(ValueEntity_.attribute, attribute), equal(ValueEntity_.attributeRelease, attributeRelease))); + if (value != null && !(desiredClass.isInstance(value))) { + logger.info("Value type has change for attribute {}: {} -> {}", attribute.getName(), + value.getClass().getSimpleName(), desiredClass.getSimpleName()); + for (ValueEntity v : value.getPrevValues()) { + v.getNextValues().remove(value); + } + valueDao.delete(value); + value = null; + } + + if (value == null) { + try { + value = desiredClass.getConstructor().newInstance(); + value.setAttribute(attribute); + value.setAttributeRelease(attributeRelease); + value = valueDao.persist(value); + attributeRelease.getValues().add(value); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | NoSuchMethodException + | InvocationTargetException e) { + logger.error("Cannot create instance of class {}: {}", desiredClass.getName(), e.getMessage()); + } + } + return value; + } + + private OutgoingAttributeEntity findOrCreateOutgroingAttribute(String name) { + OutgoingAttributeEntity attribute = outgoingAttributeDao.find(equal(OutgoingAttributeEntity_.name, name)); + if (attribute == null) { + attribute = outgoingAttributeDao.createNew(); + attribute.setName(name); + attribute = outgoingAttributeDao.persist(attribute); + } + return attribute; + } + + private AttributeReleaseEntity resolveAttributeRelease(AttributeConsumerEntity attributeConsumer, + IdentityEntity identity) { + AttributeReleaseEntity attributeRelease = attributeReleaseDao + .find(and(equal(AttributeReleaseEntity_.attributeConsumer, attributeConsumer), + equal(AttributeReleaseEntity_.identity, identity))); + + if (attributeRelease == null) { + attributeRelease = attributeReleaseDao.createNew(); + attributeRelease.setIdentity(identity); + attributeRelease.setAttributeConsumer(attributeConsumer); + attributeRelease.setReleaseStatus(ReleaseStatusType.NEW); + attributeRelease = attributeReleaseDao.persist(attributeRelease); + } + + return attributeRelease; + } +} diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/release/AttributeReleaseHandler.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/release/AttributeReleaseHandler.java index b9e1b82bb..15584d8a8 100644 --- a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/release/AttributeReleaseHandler.java +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/release/AttributeReleaseHandler.java @@ -13,16 +13,12 @@ import java.util.UUID; import org.slf4j.Logger; import edu.kit.scc.webreg.bootstrap.ApplicationConfig; -import edu.kit.scc.webreg.dao.jpa.attribute.AttributeReleaseDao; import edu.kit.scc.webreg.dao.jpa.attribute.OutgoingAttributeDao; import edu.kit.scc.webreg.dao.jpa.attribute.ValueDao; -import edu.kit.scc.webreg.entity.attribute.AttributeConsumerEntity; import edu.kit.scc.webreg.entity.attribute.AttributeEntity; import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity; -import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity_; import edu.kit.scc.webreg.entity.attribute.OutgoingAttributeEntity; import edu.kit.scc.webreg.entity.attribute.OutgoingAttributeEntity_; -import edu.kit.scc.webreg.entity.attribute.ReleaseStatusType; import edu.kit.scc.webreg.entity.attribute.value.PairwiseIdentifierValueEntity; import edu.kit.scc.webreg.entity.attribute.value.StringListValueEntity; import edu.kit.scc.webreg.entity.attribute.value.StringValueEntity; @@ -42,7 +38,7 @@ public class AttributeReleaseHandler { private Logger logger; @Inject - private AttributeReleaseDao attributeReleaseDao; + private AttributeBuilder attributeBuilder; @Inject private OutgoingAttributeDao outgoingAttributeDao; @@ -139,29 +135,6 @@ public class AttributeReleaseHandler { } } - public AttributeReleaseEntity requestAttributeRelease(AttributeConsumerEntity attributeConsumer, - IdentityEntity identity) { - final AttributeReleaseEntity attributeRelease = resolveAttributeRelease(attributeConsumer, identity); - return attributeRelease; - } - - private AttributeReleaseEntity resolveAttributeRelease(AttributeConsumerEntity attributeConsumer, - IdentityEntity identity) { - AttributeReleaseEntity attributeRelease = attributeReleaseDao - .find(and(equal(AttributeReleaseEntity_.attributeConsumer, attributeConsumer), - equal(AttributeReleaseEntity_.identity, identity))); - - if (attributeRelease == null) { - attributeRelease = attributeReleaseDao.createNew(); - attributeRelease.setIdentity(identity); - attributeRelease.setAttributeConsumer(attributeConsumer); - attributeRelease.setReleaseStatus(ReleaseStatusType.NEW); - attributeRelease = attributeReleaseDao.persist(attributeRelease); - } - - return attributeRelease; - } - private ValueEntity resolveValue(AttributeReleaseEntity attributeRelease, AttributeEntity attribute, Class<? extends ValueEntity> desiredClass) { ValueEntity value = valueDao.find( diff --git a/regapp-saml-idp/pom.xml b/regapp-saml-idp/pom.xml index dcef700fa..e7fdbffc7 100644 --- a/regapp-saml-idp/pom.xml +++ b/regapp-saml-idp/pom.xml @@ -41,6 +41,11 @@ <artifactId>regapp-saml</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>edu.kit.scc</groupId> + <artifactId>regapp-idty</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>edu.kit.scc</groupId> <artifactId>regapp-drools</artifactId> diff --git a/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AbstractValueTranscoder.java b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AbstractValueTranscoder.java new file mode 100644 index 000000000..1c6b48e1e --- /dev/null +++ b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AbstractValueTranscoder.java @@ -0,0 +1,32 @@ +package edu.kit.scc.webreg.saml.idp; + +import org.opensaml.core.xml.schema.XSString; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.AttributeValue; + +import edu.kit.scc.webreg.service.saml.SamlHelper; + +public abstract class AbstractValueTranscoder { + + protected SamlHelper samlHelper; + + public AbstractValueTranscoder(SamlHelper samlHelper) { + super(); + this.samlHelper = samlHelper; + } + + protected Attribute buildAttribute(String name, String friendlyName, String nameFormat, String... values) { + Attribute attribute = samlHelper.create(Attribute.class, Attribute.DEFAULT_ELEMENT_NAME); + attribute.setName(name); + attribute.setFriendlyName(friendlyName); + attribute.setNameFormat(nameFormat); + + for (String value : values) { + XSString xsany = samlHelper.create(XSString.class, XSString.TYPE_NAME, AttributeValue.DEFAULT_ELEMENT_NAME); + xsany.setValue(value); + attribute.getAttributeValues().add(xsany); + } + + return attribute; + } +} diff --git a/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AttributeAuthorityService.java b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AttributeAuthorityService.java index 448fb979f..813da7f17 100644 --- a/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AttributeAuthorityService.java +++ b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AttributeAuthorityService.java @@ -1,6 +1,7 @@ package edu.kit.scc.webreg.saml.idp; import java.time.Instant; +import java.util.HashSet; import javax.script.Invocable; import javax.script.ScriptEngine; @@ -32,7 +33,10 @@ import edu.kit.scc.webreg.entity.SamlAAConfigurationEntity; import edu.kit.scc.webreg.entity.SamlSpMetadataEntity; import edu.kit.scc.webreg.entity.ScriptEntity; import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity; import edu.kit.scc.webreg.script.ScriptingEnv; +import edu.kit.scc.webreg.service.attribute.release.AttributeBuilder; +import edu.kit.scc.webreg.service.identity.IdentityAttributeResolver; import edu.kit.scc.webreg.service.saml.Saml2ResponseValidationService; import edu.kit.scc.webreg.service.saml.SamlHelper; import edu.kit.scc.webreg.service.saml.SsoHelper; @@ -54,16 +58,25 @@ public class AttributeAuthorityService { @Inject private ScriptDao scriptDao; - + @Inject private ScriptingEnv scriptingEnv; - + @Inject private SamlHelper samlHelper; @Inject private SsoHelper ssoHelper; + @Inject + private AttributeBuilder attributeBuilder; + + @Inject + private IdentityAttributeResolver attributeResolver; + + @Inject + private SamlAttributeTranscoder attributeTranscoder; + public Envelope processAttributeQuery(SamlAAConfigurationEntity aaConfig, AttributeQuery query) throws SamlAuthenticationException { @@ -101,9 +114,16 @@ public class AttributeAuthorityService { throw new SamlAuthenticationException( "NameId Resolver not configured, cannot resolve account. This is a server side error, contact the server administrator"); String resolveUserScript = spEntity.getGenericStore().get("aq_resolve_user_scipt"); + + if (!spEntity.getGenericStore().containsKey("aq_resolve_attribute_scipt")) + throw new SamlAuthenticationException( + "Attribute Resolver not configured, cannot resolve attributes. This is a server side error, contact the server administrator"); + String resolveAttributeScript = spEntity.getGenericStore().get("aq_resolve_attribute_scipt"); + ScriptEntity script = scriptDao.findByName(resolveUserScript); if (script == null) - throw new SamlAuthenticationException("NameId Resolver not configured correctly. Script not found: " + resolveUserScript); + throw new SamlAuthenticationException( + "NameId Resolver not configured correctly. Script not found: " + resolveUserScript); try { ScriptEngine engine = (new ScriptEngineManager()).getEngineByName(script.getScriptEngine()); @@ -113,23 +133,38 @@ public class AttributeAuthorityService { String nameIdValue = query.getSubject().getNameID().getValue(); String nameIdFormat = query.getSubject().getNameID().getFormat(); - UserEntity user = (UserEntity) invocable.invokeFunction("resolveUser", scriptingEnv, nameIdFormat, nameIdValue, logger, - spEntity, aaConfig); + UserEntity user = (UserEntity) invocable.invokeFunction("resolveUser", scriptingEnv, nameIdFormat, + nameIdValue, logger, spEntity, aaConfig); if (user != null) { - Assertion assertion = samlHelper.create(Assertion.class, Assertion.DEFAULT_ELEMENT_NAME); - assertion.setIssueInstant(Instant.now()); - assertion.setIssuer(ssoHelper.buildIssuser(aaConfig.getEntityId())); - assertion.setSubject( - ssoHelper.buildAQSubject(aaConfig, spEntity, nameIdValue, NameID.UNSPECIFIED, query.getID())); - assertion.getAttributeStatements().add(buildAttributeStatement(user)); + + AttributeReleaseEntity attributeRelease = attributeBuilder.requestAttributeRelease(spEntity, + user.getIdentity()); + + script = scriptDao.findByName(resolveAttributeScript); + if (script == null) + throw new SamlAuthenticationException( + "Attribute Resolver not configured correctly. Script not found: " + resolveUserScript); + engine = (new ScriptEngineManager()).getEngineByName(script.getScriptEngine()); + engine.eval(script.getScript()); + invocable = (Invocable) engine; + + attributeRelease.setValuesToDelete(new HashSet<>(attributeRelease.getValues())); + invocable.invokeFunction("resolveAttributes", scriptingEnv, attributeBuilder, attributeResolver, + attributeRelease, user.getIdentity(), logger, spEntity, aaConfig); + attributeRelease.getValuesToDelete().stream().forEach(v -> attributeBuilder.deleteValue(v)); + + Assertion assertion = attributeTranscoder.convertAttributes(attributeRelease, aaConfig, spEntity, + query); samlResponse.getAssertions().add(assertion); } } catch (NoSuchMethodException e) { logger.warn("Method is missing in script: {}", e.getMessage()); - throw new SamlAuthenticationException("NameId Resolver not configured correctly. Method resolveUser not found in " + resolveUserScript); + throw new SamlAuthenticationException( + "NameId Resolver not configured correctly. Method resolveUser not found in " + + resolveUserScript); } catch (ScriptException e) { logger.warn("Script contains errors: {}", e.getMessage()); - throw new SamlAuthenticationException("NameId Resolver "+ resolveUserScript + " contains errors"); + throw new SamlAuthenticationException("NameId Resolver " + resolveUserScript + " contains errors"); } } else { throw new SamlAuthenticationException("Subject or Subject Name ID is missing in request"); diff --git a/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/SamlAttributeTranscoder.java b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/SamlAttributeTranscoder.java new file mode 100644 index 000000000..5a0267b7a --- /dev/null +++ b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/SamlAttributeTranscoder.java @@ -0,0 +1,81 @@ +package edu.kit.scc.webreg.saml.idp; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.AttributeQuery; +import org.opensaml.saml.saml2.core.AttributeStatement; +import org.opensaml.saml.saml2.core.NameID; +import org.slf4j.Logger; + +import edu.kit.scc.webreg.entity.SamlAAConfigurationEntity; +import edu.kit.scc.webreg.entity.SamlSpMetadataEntity; +import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity; +import edu.kit.scc.webreg.entity.attribute.value.StringValueEntity; +import edu.kit.scc.webreg.entity.attribute.value.ValueEntity; +import edu.kit.scc.webreg.service.saml.SamlHelper; +import edu.kit.scc.webreg.service.saml.SsoHelper; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class SamlAttributeTranscoder { + + @Inject + private Logger logger; + + @Inject + private SamlHelper samlHelper; + + @Inject + private SsoHelper ssoHelper; + + private Map<String, ValueTranscoder> transcoderMap; + + @PostConstruct + public void init() { + transcoderMap = new HashMap<>(); + transcoderMap.put("family_name", + new SingleStringValueTranscoder(samlHelper, "urn:oid:2.5.4.4", "sn", Attribute.BASIC)); + transcoderMap.put("given_name", + new SingleStringValueTranscoder(samlHelper, "urn:oid:2.5.4.42", "givenName", Attribute.BASIC)); + transcoderMap.put("eduperson_principal_name", new SingleStringValueTranscoder(samlHelper, + "urn:oid:1.3.6.1.4.1.5923.1.1.1.6", "eduPersonPrincipalName", Attribute.URI_REFERENCE)); + transcoderMap.put("eduperson_entitlement", new SingleStringValueTranscoder(samlHelper, + "urn:oid:1.3.6.1.4.1.5923.1.1.1.7", "eduPersonEntitlement", Attribute.BASIC)); + } + + public Assertion convertAttributes(AttributeReleaseEntity attributeRelease, SamlAAConfigurationEntity aaConfig, + SamlSpMetadataEntity spEntity, AttributeQuery query) { + Assertion assertion = samlHelper.create(Assertion.class, Assertion.DEFAULT_ELEMENT_NAME); + assertion.setIssueInstant(Instant.now()); + assertion.setIssuer(ssoHelper.buildIssuser(aaConfig.getEntityId())); + + AttributeStatement attributeStatement = buildAttributeStatement(); + assertion.getAttributeStatements().add(attributeStatement); + + for (ValueEntity value : attributeRelease.getValues()) { + if (value.getAttribute().getName().equals("sub")) { + assertion.setSubject(ssoHelper.buildAQSubject(aaConfig, spEntity, + ((StringValueEntity) value).getValueString(), NameID.UNSPECIFIED, query.getID())); + } else if (transcoderMap.containsKey(value.getAttribute().getName())) { + attributeStatement.getAttributes() + .add(transcoderMap.get(value.getAttribute().getName()).transcode(value)); + } else { + logger.debug("No SAML Transcoder for attribute {}", value.getAttribute().getName()); + } + } + + return assertion; + } + + private AttributeStatement buildAttributeStatement() { + AttributeStatement attributeStatement = samlHelper.create(AttributeStatement.class, + AttributeStatement.DEFAULT_ELEMENT_NAME); + return attributeStatement; + } +} diff --git a/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/SingleStringValueTranscoder.java b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/SingleStringValueTranscoder.java new file mode 100644 index 000000000..7cdd76993 --- /dev/null +++ b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/SingleStringValueTranscoder.java @@ -0,0 +1,33 @@ +package edu.kit.scc.webreg.saml.idp; + +import org.opensaml.saml.saml2.core.Attribute; + +import edu.kit.scc.webreg.entity.attribute.value.StringListValueEntity; +import edu.kit.scc.webreg.entity.attribute.value.StringValueEntity; +import edu.kit.scc.webreg.entity.attribute.value.ValueEntity; +import edu.kit.scc.webreg.service.saml.SamlHelper; + +public class SingleStringValueTranscoder extends AbstractValueTranscoder implements ValueTranscoder { + + private String outgoingName; + private String outgoingFriendlyName; + private String nameFormat; + + public SingleStringValueTranscoder(SamlHelper samlHelper, String outgoingName, String outgoingFriendlyName, String nameFormat) { + super(samlHelper); + this.outgoingName = outgoingName; + this.outgoingFriendlyName = outgoingFriendlyName; + this.nameFormat = nameFormat; + } + + public Attribute transcode(ValueEntity value) { + if (value instanceof StringValueEntity) + return buildAttribute(outgoingName, outgoingFriendlyName, nameFormat, + ((StringValueEntity) value).getValueString()); + else if (value instanceof StringListValueEntity) + return buildAttribute(outgoingName, outgoingFriendlyName, nameFormat, + ((StringListValueEntity) value).getValueList().toArray(new String[] {})); + else + throw new IllegalArgumentException("Cannot transcaode value of type " + value.getClass().getSimpleName()); + } +} diff --git a/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/ValueTranscoder.java b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/ValueTranscoder.java new file mode 100644 index 000000000..6bb37b659 --- /dev/null +++ b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/ValueTranscoder.java @@ -0,0 +1,9 @@ +package edu.kit.scc.webreg.saml.idp; + +import org.opensaml.saml.saml2.core.Attribute; + +import edu.kit.scc.webreg.entity.attribute.value.ValueEntity; + +public interface ValueTranscoder { + Attribute transcode(ValueEntity value); +} diff --git a/regapp-saml-sp/pom.xml b/regapp-saml-sp/pom.xml index 8b3aa3ca4..f86d32d02 100644 --- a/regapp-saml-sp/pom.xml +++ b/regapp-saml-sp/pom.xml @@ -46,6 +46,11 @@ <artifactId>regapp-saml</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>edu.kit.scc</groupId> + <artifactId>regapp-idty</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>edu.kit.scc</groupId> <artifactId>regapp-session</artifactId> diff --git a/regapp-tools/src/main/java/edu/kit/scc/webreg/service/impl/AttributeMapHelper.java b/regapp-tools/src/main/java/edu/kit/scc/webreg/service/impl/AttributeMapHelper.java index 64e786fb9..e5fbd508b 100644 --- a/regapp-tools/src/main/java/edu/kit/scc/webreg/service/impl/AttributeMapHelper.java +++ b/regapp-tools/src/main/java/edu/kit/scc/webreg/service/impl/AttributeMapHelper.java @@ -36,7 +36,10 @@ public class AttributeMapHelper implements Serializable { if (o instanceof String) { return (String) o; } else { - return o.toString(); + if (o == null) + return null; + else + return o.toString(); } } -- GitLab