From 2e42913eb932db62bfd96249649e6d39926e2598 Mon Sep 17 00:00:00 2001
From: Michael Simon <simon@kit.edu>
Date: Fri, 11 Oct 2024 14:25:18 +0200
Subject: [PATCH] ISSUE-196 abstract attributes for oidc op functions

---
 .../oidc/OidcOpStaticLoginProcessor.java      | 139 ++++++++++++++----
 .../service/saml/SamlIdpServiceImpl.java      |   6 +-
 .../bean/ar/SamlAttributeReleaseBean.java     |   4 -
 3 files changed, 118 insertions(+), 31 deletions(-)

diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/OidcOpStaticLoginProcessor.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/OidcOpStaticLoginProcessor.java
index d1e82e62c..e8a0c6397 100644
--- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/OidcOpStaticLoginProcessor.java
+++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/OidcOpStaticLoginProcessor.java
@@ -2,6 +2,7 @@ package edu.kit.scc.webreg.service.oidc;
 
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
 
 import javax.script.Invocable;
@@ -17,6 +18,7 @@ import com.nimbusds.openid.connect.sdk.claims.UserInfo;
 
 import edu.kit.scc.webreg.bootstrap.ApplicationConfig;
 import edu.kit.scc.webreg.dao.RegistryDao;
+import edu.kit.scc.webreg.dao.UserDao;
 import edu.kit.scc.webreg.dao.oidc.ServiceOidcClientDao;
 import edu.kit.scc.webreg.drools.OverrideAccess;
 import edu.kit.scc.webreg.drools.UnauthorizedUser;
@@ -27,6 +29,8 @@ import edu.kit.scc.webreg.entity.RegistryStatus;
 import edu.kit.scc.webreg.entity.ScriptEntity;
 import edu.kit.scc.webreg.entity.ServiceEntity;
 import edu.kit.scc.webreg.entity.UserEntity;
+import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity;
+import edu.kit.scc.webreg.entity.attribute.ReleaseStatusType;
 import edu.kit.scc.webreg.entity.identity.IdentityEntity;
 import edu.kit.scc.webreg.entity.oidc.OidcClientConfigurationEntity;
 import edu.kit.scc.webreg.entity.oidc.OidcClientConsumerEntity;
@@ -34,6 +38,8 @@ import edu.kit.scc.webreg.entity.oidc.OidcFlowStateEntity;
 import edu.kit.scc.webreg.entity.oidc.OidcOpConfigurationEntity;
 import edu.kit.scc.webreg.entity.oidc.ServiceOidcClientEntity;
 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.exc.OidcAuthenticationException;
 import edu.kit.scc.webreg.session.SessionManager;
 import jakarta.enterprise.context.ApplicationScoped;
@@ -58,9 +64,18 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 	@Inject
 	private RegistryDao registryDao;
 
+	@Inject
+	private UserDao userDao;
+	
 	@Inject
 	private ServiceOidcClientDao serviceOidcClientDao;
 
+	@Inject
+	private AttributeBuilder attributeBuilder;
+
+	@Inject
+	private IdentityAttributeResolver attributeResolver;
+
 	@Inject
 	private ApplicationConfig appConfig;
 
@@ -81,6 +96,14 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 			throw new OidcAuthenticationException("no script is connected to client configuration");
 		}
 
+		// User pref user
+		// TODO Change to something more correct. Script must choose user from identity
+		// ie.
+		List<UserEntity> userList = userDao.findByIdentity(identity);
+		UserEntity user = identity.getPrefUser();
+		if (user == null)
+			user = userList.get(0);
+
 		Boolean wantsElevation = false;
 		RegistryEntity registry = null;
 
@@ -130,8 +153,8 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 						logger.info(
 								"No active registration for identity {} and service {}, redirecting to register page",
 								identity.getId(), service.getName());
-						session.setOriginalRequestPath(
-								"/oidc/realms/" + flowState.getOpConfiguration().getRealm() + "/protocol/openid-connect/auth/return");
+						session.setOriginalRequestPath("/oidc/realms/" + flowState.getOpConfiguration().getRealm()
+								+ "/protocol/openid-connect/auth/return");
 						return "/user/register-service.xhtml?serviceId=" + service.getId();
 					}
 				}
@@ -173,43 +196,66 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 					|| (System.currentTimeMillis() - session.getTwoFaElevation().toEpochMilli()) > elevationTime) {
 				// second factor is active for this service and web login
 				// and user is not elevated yet
-				session.setOriginalRequestPath(
-						"/oidc/realms/" + flowState.getOpConfiguration().getRealm() + "/protocol/openid-connect/auth/return");
+				session.setOriginalRequestPath("/oidc/realms/" + flowState.getOpConfiguration().getRealm()
+						+ "/protocol/openid-connect/auth/return");
 				return "/user/twofa-login.xhtml";
 			}
 
 		}
 
+		/*
+		 * need to calculate attributes here to show release data
+		 */
+
+		if (registry != null) {
+			// Redefine user to match registry
+			user = registry.getUser();
+		}
+
+		final AttributeReleaseEntity attributeRelease = attributeBuilder.requestAttributeRelease(clientConfig, identity);
+
 		flowState.setValidUntil(new Date(System.currentTimeMillis() + (10L * 60L * 1000L)));
 		flowState.setIdentity(identity);
 		flowState.setRegistry(registry);
-
+		flowState.setAttributeRelease(attributeRelease);
+
+		resolveAttributes(attributeRelease, serviceOidcClientList, identity, user, registry, flowState, flowState.getOpConfiguration(), clientConfig);
+		if (clientConfig.getGenericStore().containsKey("show_consent")
+				&& clientConfig.getGenericStore().get("show_consent").equalsIgnoreCase("true")) {
+			if (!ReleaseStatusType.GOOD.equals(attributeRelease.getReleaseStatus())) {
+				// send client to attribute release page
+				logger.debug("Attribute Release is not good, sending user to constent page");
+				return "/user/attribute-release-oidc.xhtml?id=" + attributeRelease.getId();
+			}
+		} else {
+			attributeRelease.setReleaseStatus(null);
+		}
+		
 		String red = flowState.getRedirectUri() + "?code=" + flowState.getCode() + "&state=" + flowState.getState();
 		logger.debug("Sending client to {}", red);
-		return red;			
+		return red;
 	}
-	
+
 	public JSONObject buildAccessToken(OidcFlowStateEntity flowState, OidcOpConfigurationEntity opConfig,
 			OidcClientConsumerEntity consumerConfig, HttpServletResponse response) throws OidcAuthenticationException {
 
 		if (!(consumerConfig instanceof OidcClientConfigurationEntity))
 			throw new OidcAuthenticationException("This flow only supports legacy OidcClientConfigurationEntity");
-		
+
 		OidcClientConfigurationEntity clientConfig = (OidcClientConfigurationEntity) consumerConfig;
 
-		IdentityEntity identity = flowState.getIdentity();
+		final IdentityEntity identity = flowState.getIdentity();
+		final AttributeReleaseEntity attributeRelease = flowState.getAttributeRelease();
 
 		if (identity == null) {
 			throw new OidcAuthenticationException("No identity attached to flow state.");
 		}
 
-		UserEntity user;
-		if (identity.getUsers().size() == 1) {
+		UserEntity user = identity.getPrefUser();
+		if (user == null) {
 			user = identity.getUsers().iterator().next();
-		} else {
-			user = identity.getPrefUser();
-		}
-
+		} 
+		
 		RegistryEntity registry = flowState.getRegistry();
 
 		/*
@@ -222,11 +268,13 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 		List<ServiceOidcClientEntity> serviceOidcClientList = serviceOidcClientDao.findByClientConfig(clientConfig);
 
 		JWTClaimsSet.Builder claimsBuilder = initClaimsBuilder(flowState).subject(user.getEppn());
-
+		
 		if (flowState.getScope() != null) {
 			claimsBuilder.claim("scope", flowState.getScope());
 		}
 
+		buildStatement(claimsBuilder, "buildTokenStatement", serviceOidcClientList, identity, user, registry);
+
 		for (ServiceOidcClientEntity serviceOidcClient : serviceOidcClientList) {
 			ScriptEntity scriptEntity = serviceOidcClient.getScript();
 			if (scriptEntity.getScriptType().equalsIgnoreCase("javascript")) {
@@ -257,7 +305,7 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 
 		Boolean shortIdTokenHeader = Boolean.TRUE;
 		if (clientConfig.getGenericStore().containsKey("short_id_token_header"))
-				shortIdTokenHeader = Boolean.parseBoolean(clientConfig.getGenericStore().get("long_access_token"));
+			shortIdTokenHeader = Boolean.parseBoolean(clientConfig.getGenericStore().get("long_access_token"));
 
 		SignedJWT jwt = signClaims(opConfig, clientConfig, claims, shortIdTokenHeader);
 
@@ -268,7 +316,7 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 			OidcClientConsumerEntity consumerConfig, HttpServletResponse response) throws OidcAuthenticationException {
 		if (!(consumerConfig instanceof OidcClientConfigurationEntity))
 			throw new OidcAuthenticationException("This flow only supports legacy OidcClientConfigurationEntity");
-		
+
 		OidcClientConfigurationEntity clientConfig = (OidcClientConfigurationEntity) consumerConfig;
 		List<ServiceOidcClientEntity> serviceOidcClientList = serviceOidcClientDao.findByClientConfig(clientConfig);
 
@@ -288,6 +336,19 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 		RegistryEntity registry = flowState.getRegistry();
 
 		JWTClaimsSet.Builder claimsBuilder = new JWTClaimsSet.Builder();
+		buildStatement(claimsBuilder, "buildClaimsStatement", serviceOidcClientList, identity, user, registry);
+
+		UserInfo userInfo = new UserInfo(claimsBuilder.build());
+		logger.debug("[OidcOpStaticLoginProcessor] userInfo Response: " + userInfo.toJSONObject());
+		return userInfo.toJSONObject();
+	}
+
+	private void resolveAttributes(AttributeReleaseEntity attributeRelease,
+			List<ServiceOidcClientEntity> serviceOidcClientList, IdentityEntity identity, UserEntity user,
+			RegistryEntity registry, OidcFlowStateEntity flowState, OidcOpConfigurationEntity opConfig,
+			OidcClientConsumerEntity consumerConfig) throws OidcAuthenticationException {
+
+		attributeRelease.setValuesToDelete(new HashSet<>(attributeRelease.getValues()));
 
 		for (ServiceOidcClientEntity serviceOidcClient : serviceOidcClientList) {
 			ScriptEntity scriptEntity = serviceOidcClient.getScript();
@@ -303,7 +364,38 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 
 					Invocable invocable = (Invocable) engine;
 
-					invocable.invokeFunction("buildClaimsStatement", scriptingEnv, claimsBuilder, user, registry,
+					invocable.invokeFunction("resolveAttributes", scriptingEnv, attributeBuilder, attributeResolver,
+							attributeRelease, identity, user, registry, logger, flowState, consumerConfig, opConfig);
+				} catch (NoSuchMethodException | ScriptException e) {
+					logger.warn("Script execution failed. Continue with other scripts.", e);
+				}
+			} else {
+				throw new OidcAuthenticationException("unkown script type: " + scriptEntity.getScriptType());
+			}
+		}
+
+		attributeRelease.getValuesToDelete().stream().forEach(v -> attributeBuilder.deleteValue(v));
+	}
+
+	private void buildStatement(JWTClaimsSet.Builder claimsBuilder, String methodName,
+			List<ServiceOidcClientEntity> serviceOidcClientList, IdentityEntity identity, UserEntity user,
+			RegistryEntity registry) throws OidcAuthenticationException {
+
+		for (ServiceOidcClientEntity serviceOidcClient : serviceOidcClientList) {
+			ScriptEntity scriptEntity = serviceOidcClient.getScript();
+			if (scriptEntity.getScriptType().equalsIgnoreCase("javascript")) {
+				ScriptEngine engine = (new ScriptEngineManager()).getEngineByName(scriptEntity.getScriptEngine());
+
+				if (engine == null)
+					throw new OidcAuthenticationException(
+							"service not configured properly. engine not found: " + scriptEntity.getScriptEngine());
+
+				try {
+					engine.eval(scriptEntity.getScript());
+
+					Invocable invocable = (Invocable) engine;
+
+					invocable.invokeFunction(methodName, scriptingEnv, claimsBuilder, user, registry,
 							serviceOidcClient.getService(), logger, identity);
 				} catch (NoSuchMethodException | ScriptException e) {
 					logger.warn("Script execution failed. Continue with other scripts.", e);
@@ -312,11 +404,8 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 				throw new OidcAuthenticationException("unkown script type: " + scriptEntity.getScriptType());
 			}
 		}
-		UserInfo userInfo = new UserInfo(claimsBuilder.build());
-		logger.debug("[OidcOpStaticLoginProcessor] userInfo Response: " + userInfo.toJSONObject());
-		return userInfo.toJSONObject();
 	}
-	
+
 	private List<Object> checkRules(UserEntity user, ServiceEntity service, RegistryEntity registry) {
 		return knowledgeSessionSingleton.checkServiceAccessRule(user, service, registry, "user-self", false);
 	}
@@ -348,7 +437,7 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 
 		return returnList;
 	}
-	
+
 	private boolean evalTwoFa(ScriptEntity scriptEntity, IdentityEntity identity, RegistryEntity registry,
 			OidcFlowStateEntity flowState) {
 		ScriptEngine engine = (new ScriptEngineManager()).getEngineByName(scriptEntity.getScriptEngine());
@@ -373,5 +462,5 @@ public class OidcOpStaticLoginProcessor extends AbstractOidcOpLoginProcessor {
 			logger.debug("No evalTwoFa method in script. Assuming match false");
 		}
 		return false;
-	}	
+	}
 }
diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlIdpServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlIdpServiceImpl.java
index 1a09087a8..00f764f18 100644
--- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlIdpServiceImpl.java
+++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlIdpServiceImpl.java
@@ -184,11 +184,13 @@ public class SamlIdpServiceImpl implements SamlIdpService {
 		IdentityEntity identity = identityDao.fetch(identityId);
 		logger.debug("Identity loaded: {}", identity.getId());
 
-		// First user object picked for now
+		// User pref user
 		// TODO Change to something more correct. Script must choose user from identity
 		// ie.
 		List<UserEntity> userList = userDao.findByIdentity(identity);
-		UserEntity user = userList.get(0);
+		UserEntity user = identity.getPrefUser();
+		if (user == null)
+			user = userList.get(0);
 
 		SamlAuthnRequestEntity authnRequestEntity = samlAuthnRequestDao.fetch(authnRequestId);
 		AuthnRequest authnRequest = samlHelper.unmarshal(authnRequestEntity.getAuthnrequestData(), AuthnRequest.class);
diff --git a/bwreg-webapp/src/main/java/edu/kit/scc/webreg/bean/ar/SamlAttributeReleaseBean.java b/bwreg-webapp/src/main/java/edu/kit/scc/webreg/bean/ar/SamlAttributeReleaseBean.java
index dd5be0b3c..c8190339a 100644
--- a/bwreg-webapp/src/main/java/edu/kit/scc/webreg/bean/ar/SamlAttributeReleaseBean.java
+++ b/bwreg-webapp/src/main/java/edu/kit/scc/webreg/bean/ar/SamlAttributeReleaseBean.java
@@ -10,20 +10,16 @@ import java.util.Arrays;
 import java.util.List;
 
 import edu.kit.scc.webreg.entity.SamlAuthnRequestEntity;
-import edu.kit.scc.webreg.entity.SamlAuthnRequestEntity_;
 import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity;
 import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity_;
 import edu.kit.scc.webreg.entity.attribute.value.StringListValueEntity_;
 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.entity.oidc.OidcFlowStateEntity;
-import edu.kit.scc.webreg.entity.oidc.OidcFlowStateEntity_;
 import edu.kit.scc.webreg.service.SamlAuthnRequestService;
 import edu.kit.scc.webreg.service.attributes.AttributeReleaseService;
 import edu.kit.scc.webreg.service.attributes.ValueService;
 import edu.kit.scc.webreg.service.identity.IdentityService;
-import edu.kit.scc.webreg.service.oidc.OidcFlowStateService;
 import edu.kit.scc.webreg.session.SessionManager;
 import jakarta.faces.context.FacesContext;
 import jakarta.faces.event.ComponentSystemEvent;
-- 
GitLab