From 05fe69dd180761dc306192c56250cfbbf648be2d Mon Sep 17 00:00:00 2001
From: Michael Simon <simon@kit.edu>
Date: Fri, 27 Sep 2024 09:12:48 +0200
Subject: [PATCH] ISSUE-196 add user update for attribute queries

---
 .../entity/attribute/value/ValueEntity.java   | 12 +++
 .../src/main/webapp/user/show-identity.xhtml  |  2 +
 .../attribute/IncomingAttributesHandler.java  |  4 +
 .../proc/SingleStringMergeValueProcessor.java |  3 +
 .../service/attribute/proc/ValueUpdater.java  |  5 +-
 .../saml/idp/AttributeAuthorityService.java   | 83 ++++++++++++++-----
 .../saml/idp/SamlAttributeTranscoder.java     |  6 ++
 7 files changed, 93 insertions(+), 22 deletions(-)

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 a37d8e317..e976a36d7 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
@@ -1,5 +1,6 @@
 package edu.kit.scc.webreg.entity.attribute.value;
 
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -44,6 +45,9 @@ public class ValueEntity extends AbstractBaseEntity {
 	@ManyToMany(mappedBy = "nextValues")
 	private Set<ValueEntity> prevValues = new HashSet<>();
 	
+	@Column(name = "last_update")
+	protected Date lastUpdate;
+
 	@Transient
 	private Boolean changed;
 
@@ -102,4 +106,12 @@ public class ValueEntity extends AbstractBaseEntity {
 	public void setChanged(Boolean changed) {
 		this.changed = changed;
 	}
+
+	public Date getLastUpdate() {
+		return lastUpdate;
+	}
+
+	public void setLastUpdate(Date lastUpdate) {
+		this.lastUpdate = lastUpdate;
+	}
 }
diff --git a/bwreg-webapp/src/main/webapp/user/show-identity.xhtml b/bwreg-webapp/src/main/webapp/user/show-identity.xhtml
index a61c97925..1feee5db9 100644
--- a/bwreg-webapp/src/main/webapp/user/show-identity.xhtml
+++ b/bwreg-webapp/src/main/webapp/user/show-identity.xhtml
@@ -49,6 +49,7 @@
 						<li><h:outputText value="#{prev.id} (#{prev.attributeSet.user.id})" /></li>
 					</ui:repeat>
 					</ul>
+					<div style="font-size: 0.7rem;"><h:outputText value="#{value.lastUpdate}" /></div>
 				</p:column>
 			</p:dataTable>
 
@@ -73,6 +74,7 @@
 										<li><h:outputText value="#{item}"/></li>
 									</ui:repeat></ul>
 								</p:outputPanel>
+								<div style="font-size: 0.7rem;"><h:outputText value="#{value.lastUpdate}" /></div>
 							</p:column>
 						</p:dataTable>
 					</p:panel>
diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/IncomingAttributesHandler.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/IncomingAttributesHandler.java
index 7652a0930..35a67b547 100644
--- a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/IncomingAttributesHandler.java
+++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/IncomingAttributesHandler.java
@@ -3,6 +3,7 @@ package edu.kit.scc.webreg.service.attribute;
 import static edu.kit.scc.webreg.dao.ops.RqlExpressions.equal;
 
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
@@ -133,6 +134,7 @@ public abstract class IncomingAttributesHandler<T extends IncomingAttributeEntit
 		}
 
 		((StringValueEntity) value).setValueString(attributeValue);
+		value.setLastUpdate(new Date());
 		return ((StringValueEntity) value);
 	}
 
@@ -157,6 +159,7 @@ public abstract class IncomingAttributesHandler<T extends IncomingAttributeEntit
 		}
 
 		((LongValueEntity) value).setValueLong(attributeValue);
+		value.setLastUpdate(new Date());
 		return ((LongValueEntity) value);
 	}
 
@@ -191,6 +194,7 @@ public abstract class IncomingAttributesHandler<T extends IncomingAttributeEntit
 			}
 		}
 
+		value.setLastUpdate(new Date());
 		return listValue;
 	}
 
diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/SingleStringMergeValueProcessor.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/SingleStringMergeValueProcessor.java
index 36cb36379..b2d0f6065 100644
--- a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/SingleStringMergeValueProcessor.java
+++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/SingleStringMergeValueProcessor.java
@@ -4,6 +4,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -57,6 +58,7 @@ public class SingleStringMergeValueProcessor extends AbstractListProcessor {
 		
 		if (valueMap.isEmpty()) {
 			NullValueEntity targetValue = (NullValueEntity) getValueUpdater().resolveValue(attribute, attributeSet, NullValueEntity.class);
+			targetValue.setLastUpdate(new Date());
 			targetValue.setEndValue(true);
 		}
 		else {
@@ -65,6 +67,7 @@ public class SingleStringMergeValueProcessor extends AbstractListProcessor {
 			StringValueEntity targetValue = (StringValueEntity) getValueUpdater().resolveValue(attribute, attributeSet, StringValueEntity.class);
 			targetValue.setValueString(name);
 			valueMap.get(name).stream().forEach(v -> v.getNextValues().add(targetValue));
+			targetValue.setLastUpdate(new Date());
 			targetValue.setEndValue(true);
 		}
 	}
diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/ValueUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/ValueUpdater.java
index 402ee5dd5..a2f0977ca 100644
--- a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/ValueUpdater.java
+++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/attribute/proc/ValueUpdater.java
@@ -7,6 +7,7 @@ import java.lang.reflect.InvocationTargetException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -65,6 +66,7 @@ public class ValueUpdater {
 			targetValue.setValueList(new ArrayList<>());
 		targetValue.getValueList().clear();
 		targetValue.getValueList().addAll(values);
+		targetValue.setLastUpdate(new Date());
 		targetValue.setEndValue(true);
 	}
 
@@ -119,6 +121,7 @@ public class ValueUpdater {
 			value.getNextValues().add(targetValue);
 		}
 		targetValue.setValueList(new ArrayList<>(values));
+		targetValue.setLastUpdate(new Date());
 		targetValue.setEndValue(true);
 	}
 
@@ -169,7 +172,7 @@ public class ValueUpdater {
 			copyStringListValue((StringListValueEntity) in, (StringListValueEntity) out);
 		else if (in instanceof LongValueEntity)
 			((LongValueEntity) out).setValueLong(((LongValueEntity) in).getValueLong());
-
+		out.setLastUpdate(in.getLastUpdate());
 	}
 
 	private void copyStringValue(StringValueEntity in, StringValueEntity out) {
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 813da7f17..82ac68e2b 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
@@ -31,12 +31,20 @@ import edu.kit.scc.webreg.dao.SamlSpMetadataDao;
 import edu.kit.scc.webreg.dao.ScriptDao;
 import edu.kit.scc.webreg.entity.SamlAAConfigurationEntity;
 import edu.kit.scc.webreg.entity.SamlSpMetadataEntity;
+import edu.kit.scc.webreg.entity.SamlUserEntity;
 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.entity.identity.IdentityEntity;
+import edu.kit.scc.webreg.entity.oauth.OAuthUserEntity;
+import edu.kit.scc.webreg.entity.oidc.OidcUserEntity;
+import edu.kit.scc.webreg.exc.UserUpdateException;
 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.impl.OAuthUserUpdater;
+import edu.kit.scc.webreg.service.impl.OidcUserUpdater;
+import edu.kit.scc.webreg.service.impl.SamlUserUpdater;
 import edu.kit.scc.webreg.service.saml.Saml2ResponseValidationService;
 import edu.kit.scc.webreg.service.saml.SamlHelper;
 import edu.kit.scc.webreg.service.saml.SsoHelper;
@@ -77,6 +85,15 @@ public class AttributeAuthorityService {
 	@Inject
 	private SamlAttributeTranscoder attributeTranscoder;
 
+	@Inject
+	private SamlUserUpdater samlUserUpdater;
+
+	@Inject
+	private OidcUserUpdater oidcUserUpdater;
+
+	@Inject
+	private OAuthUserUpdater oauthUserUpdater;
+
 	public Envelope processAttributeQuery(SamlAAConfigurationEntity aaConfig, AttributeQuery query)
 			throws SamlAuthenticationException {
 
@@ -133,12 +150,18 @@ 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);
-				if (user != null) {
+				final UserEntity user = resolveUser(invocable, nameIdFormat, nameIdValue, spEntity, aaConfig);
+				final IdentityEntity identity = resolveIdentity(user, invocable, nameIdFormat, nameIdValue, spEntity,
+						aaConfig);
+				if (identity != null) {
+
+					// only update all user objects when configured, because this can take some time
+					if (spEntity.getGenericStore().containsKey("update_identity")
+							&& spEntity.getGenericStore().get("update_identity").equalsIgnoreCase("true"))
+						identity.getUsers().stream().forEach(u -> updateUser(u));
 
 					AttributeReleaseEntity attributeRelease = attributeBuilder.requestAttributeRelease(spEntity,
-							user.getIdentity());
+							identity);
 
 					script = scriptDao.findByName(resolveAttributeScript);
 					if (script == null)
@@ -210,26 +233,44 @@ public class AttributeAuthorityService {
 		return envelope;
 	}
 
-	private AttributeStatement buildAttributeStatement(UserEntity user) {
-		AttributeStatement attributeStatement = samlHelper.create(AttributeStatement.class,
-				AttributeStatement.DEFAULT_ELEMENT_NAME);
-		attributeStatement.getAttributes().add(buildAttribute("urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
-				"eduPersonPrincipalName", Attribute.URI_REFERENCE, user.getEppn()));
-		return attributeStatement;
-	}
+	private IdentityEntity resolveIdentity(UserEntity user, Invocable invocable, String nameIdFormat,
+			String nameIdValue, SamlSpMetadataEntity spEntity, SamlAAConfigurationEntity aaConfig)
+			throws ScriptException {
+		if (user != null) {
+			return user.getIdentity();
+		}
+
+		try {
+			return (IdentityEntity) invocable.invokeFunction("resolveIdentity", scriptingEnv, nameIdFormat, nameIdValue,
+					logger, spEntity, aaConfig);
+		} catch (NoSuchMethodException e) {
+			// Ignore exception. Better would be a check for method existance
+			return null;
+		}
 
-	private 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);
+	private UserEntity resolveUser(Invocable invocable, String nameIdFormat, String nameIdValue,
+			SamlSpMetadataEntity spEntity, SamlAAConfigurationEntity aaConfig) throws ScriptException {
+		try {
+			return (UserEntity) invocable.invokeFunction("resolveUser", scriptingEnv, nameIdFormat, nameIdValue, logger,
+					spEntity, aaConfig);
+		} catch (NoSuchMethodException e) {
+			// Ignore exception. Better would be a check for method existance
+			return null;
 		}
 
-		return attribute;
+	}
+
+	private void updateUser(UserEntity user) {
+		try {
+			if (user instanceof SamlUserEntity)
+				samlUserUpdater.updateUserFromHomeOrg((SamlUserEntity) user, null, "attribue-query", null);
+			else if (user instanceof OidcUserEntity)
+				oidcUserUpdater.updateUserFromHomeOrg((OidcUserEntity) user, null, "attribue-query", null);
+			else if (user instanceof OAuthUserEntity)
+				oauthUserUpdater.updateUserFromHomeOrg((OAuthUserEntity) user, null, "attribue-query", null);
+		} catch (UserUpdateException e) {
+		}
 	}
 }
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
index 5a0267b7a..8f3f254ab 100644
--- 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
@@ -43,10 +43,16 @@ public class SamlAttributeTranscoder {
 				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("email",
+				new SingleStringValueTranscoder(samlHelper, "urn:oid:0.9.2342.19200300.100.1.3", "email", 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));
+		transcoderMap.put("eduperson_assurance", new SingleStringValueTranscoder(samlHelper,
+				"urn:oid:1.3.6.1.4.1.5923.1.1.1.11", "edupersonAssurance", Attribute.BASIC));
+		transcoderMap.put("voperson_external_affiliation", new SingleStringValueTranscoder(samlHelper,
+				"urn:oid:1.3.6.1.4.1.25178.4.1.11", "voPersonExternalAffiliation", Attribute.BASIC));
 	}
 
 	public Assertion convertAttributes(AttributeReleaseEntity attributeRelease, SamlAAConfigurationEntity aaConfig,
-- 
GitLab