diff --git a/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/UserEntity.java b/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/UserEntity.java index f60d9d395fc91e982f5b556b804def8258547a78..5075f5cc15e89ae7575742219ef7ef230a013c01 100644 --- a/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/UserEntity.java +++ b/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/UserEntity.java @@ -102,6 +102,12 @@ public class UserEntity extends AbstractBaseEntity { @Column(name = "last_failed_update") private Date lastFailedUpdate; + @Column(name = "expires_warn_sent_at") + private Date expireWarningSent; + + @Column(name = "expired_sent_at") + private Date expiredSent; + @Column(name = "theme", length = 128) private String theme; @@ -307,5 +313,21 @@ public class UserEntity extends AbstractBaseEntity { public void setName(String name) { this.name = name; + } + + public Date getExpireWarningSent() { + return expireWarningSent; + } + + public void setExpireWarningSent(Date expireWarningSent) { + this.expireWarningSent = expireWarningSent; + } + + public Date getExpiredSent() { + return expiredSent; + } + + public void setExpiredSent(Date expiredSent) { + this.expiredSent = expiredSent; } } diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/job/UserExpire.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/job/UserExpire.java new file mode 100644 index 0000000000000000000000000000000000000000..a8708c383fe1a9dbc89ad33df1120f0e129dfeb3 --- /dev/null +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/job/UserExpire.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2014 Michael Simon. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Michael Simon - initial + ******************************************************************************/ +package edu.kit.scc.webreg.job; + +import java.util.List; + +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.service.UserService; + + +public class UserExpire extends AbstractExecutableJob { + + private static final long serialVersionUID = 1L; + + @Override + public void execute() { + Logger logger = LoggerFactory.getLogger(UserExpire.class); + + Integer limit, days; + String emailTemplateName = "user_expiry"; + + if (getJobStore().containsKey("user_expiry_template")) { + emailTemplateName = getJobStore().get("user_expiry_template"); + } + + if (getJobStore().containsKey("limit")) { + limit = Integer.parseInt(getJobStore().get("limit")); + } + else { + limit = 1; + } + + if (getJobStore().containsKey("days")) { + days = Integer.parseInt(getJobStore().get("days")); + } + else { + days = 14; + } + + try { + InitialContext ic = new InitialContext(); + + UserService service = (UserService) ic.lookup("global/bwreg/bwreg-service/UserServiceImpl!edu.kit.scc.webreg.service.UserService"); + List<UserEntity> userList = service.findUsersForExpiry(limit, days); + + logger.debug("Found {} users suitable for expiry", userList.size()); + + for (UserEntity user : userList) { + logger.debug("Inspecting user {} - {} - {} - {} - {}", user.getId(), user.getEppn(), user.getEmail(), user.getUserStatus(), user.getLastStatusChange()); + service.expireUser(user, emailTemplateName); + } + + } catch (NamingException e) { + logger.warn("Could not expire users: {}", e); + } + } +} diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/job/UserExpireSendWarning.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/job/UserExpireSendWarning.java new file mode 100644 index 0000000000000000000000000000000000000000..d1c2d76f5234ddb7a182dfecfd7a46137b0c7184 --- /dev/null +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/job/UserExpireSendWarning.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2014 Michael Simon. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Michael Simon - initial + ******************************************************************************/ +package edu.kit.scc.webreg.job; + +import java.util.List; + +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.service.UserService; + + +public class UserExpireSendWarning extends AbstractExecutableJob { + + private static final long serialVersionUID = 1L; + + @Override + public void execute() { + Logger logger = LoggerFactory.getLogger(UserExpireSendWarning.class); + + Integer limit, days; + String emailTemplateName = "user_expiry_warning"; + + if (getJobStore().containsKey("user_expiry_warning_template")) { + emailTemplateName = getJobStore().get("user_expiry_warning_template"); + } + + if (getJobStore().containsKey("limit")) { + limit = Integer.parseInt(getJobStore().get("limit")); + } + else { + limit = 1; + } + + if (getJobStore().containsKey("days")) { + days = Integer.parseInt(getJobStore().get("days")); + } + else { + days = 90; + } + + try { + InitialContext ic = new InitialContext(); + + UserService service = (UserService) ic.lookup("global/bwreg/bwreg-service/UserServiceImpl!edu.kit.scc.webreg.service.UserService"); + List<UserEntity> userList = service.findUsersForExpiryWarning(limit, days); + + logger.debug("Found {} users suitable for sending expire warning", userList.size()); + + for (UserEntity user : userList) { + logger.debug("Inspecting user {} - {} - {} - {} - {}", user.getId(), user.getEppn(), user.getEmail(), user.getUserStatus(), user.getLastStatusChange()); + service.sendUserExpiryWarning(user, emailTemplateName); + } + + } catch (NamingException e) { + logger.warn("Could not send expire warning to user: {}", e); + } + } +} diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/UserService.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/UserService.java index 8db0a548270421a57cb487a35b301f1961bb2329..94b7ab30382d82077f9f60c5614986bb54d73e45 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/UserService.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/UserService.java @@ -57,4 +57,12 @@ public interface UserService extends BaseService<UserEntity> { SamlUserEntity findByPersistent(String spId, String idpId, String persistentId); + List<UserEntity> findUsersForExpiryWarning(int limit, int days); + + void sendUserExpiryWarning(UserEntity user, String emailTemplateName); + + List<UserEntity> findUsersForExpiry(int limit, int daysSinceWarning); + + void expireUser(UserEntity user, String emailTemplateName); + } diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserCreateServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserCreateServiceImpl.java index 98b70a99b6db0d5635f9a5361caadd66e2e5b66f..9bf83262a087b150eea818d3f7d6809a88265bdb 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserCreateServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserCreateServiceImpl.java @@ -51,7 +51,7 @@ import edu.kit.scc.webreg.event.exc.EventSubmitException; import edu.kit.scc.webreg.exc.UserUpdateException; import edu.kit.scc.webreg.service.SamlIdpMetadataService; import edu.kit.scc.webreg.service.UserCreateService; -import edu.kit.scc.webreg.service.group.HomeOrgGroupUpdater; +import edu.kit.scc.webreg.service.group.SamlGroupUpdater; import edu.kit.scc.webreg.service.identity.IdentityCreater; import edu.kit.scc.webreg.service.saml.Saml2AssertionService; import edu.kit.scc.webreg.service.saml.SamlIdentifier; @@ -78,10 +78,10 @@ public class UserCreateServiceImpl implements UserCreateService { private SamlUserDao samlUserDao; @Inject - private UserUpdater userUpdater; + private SamlUserUpdater userUpdater; @Inject - private HomeOrgGroupUpdater homeOrgGroupUpdater; + private SamlGroupUpdater homeOrgGroupUpdater; @Inject private RoleDao roleDao; diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserLoginServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserLoginServiceImpl.java index 0fce336ef8b5fcce9af102b97820ae522eaca2c1..7c61d32790c9aa142baa980facf7d2a3e579f0c4 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserLoginServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserLoginServiceImpl.java @@ -113,7 +113,7 @@ public class UserLoginServiceImpl implements UserLoginService, Serializable { private SamlUserDao samlUserDao; @Inject - private UserUpdater userUpdater; + private SamlUserUpdater userUpdater; @Inject private KnowledgeSessionSingleton knowledgeSessionService; @@ -751,7 +751,7 @@ public class UserLoginServiceImpl implements UserLoginService, Serializable { } else { logger.info("Performing user update for {} with id {}", new Object[] { user.getEppn(), user.getId() }); - user = userUpdater.updateUserFromIdp(user, service, executor, null); + user = userUpdater.updateUserFromHomeOrg(user, service, executor, null); } } catch (UserUpdateException e) { logger.warn("Could not update user (attrq is optional, continue with login process) {}: {}", @@ -771,7 +771,7 @@ public class UserLoginServiceImpl implements UserLoginService, Serializable { logger.info("Performing user update for {} with id {}", new Object[] { user.getEppn(), user.getId() }); - user = userUpdater.updateUserFromIdp(user, service, executor, null); + user = userUpdater.updateUserFromHomeOrg(user, service, executor, null); } } catch (UserUpdateException e) { logger.warn("Could not update user {}: {} (attrq is mandatory, denying access)", e.getMessage(), diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserServiceImpl.java index 70b29aea6ff4ed3695dbd51dca4010f886bdce93..9de0dfc285d6612b8bb7945b59dc661609f9fe93 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserServiceImpl.java @@ -13,9 +13,14 @@ package edu.kit.scc.webreg.service.impl; import static edu.kit.scc.webreg.dao.ops.PaginateBy.withLimit; import static edu.kit.scc.webreg.dao.ops.RqlExpressions.and; import static edu.kit.scc.webreg.dao.ops.RqlExpressions.equal; +import static edu.kit.scc.webreg.dao.ops.RqlExpressions.isNotNull; +import static edu.kit.scc.webreg.dao.ops.RqlExpressions.isNull; +import static edu.kit.scc.webreg.dao.ops.RqlExpressions.lessThan; import static edu.kit.scc.webreg.dao.ops.RqlExpressions.lessThanOrEqualTo; import static edu.kit.scc.webreg.dao.ops.SortBy.ascendingBy; +import static java.time.temporal.ChronoUnit.DAYS; +import java.time.Instant; import java.util.Date; import java.util.List; @@ -28,6 +33,7 @@ import edu.kit.scc.webreg.dao.SamlUserDao; import edu.kit.scc.webreg.dao.UserDao; import edu.kit.scc.webreg.dao.UserLoginInfoDao; import edu.kit.scc.webreg.dao.identity.IdentityDao; +import edu.kit.scc.webreg.dao.ops.SortBy; import edu.kit.scc.webreg.entity.GroupEntity; import edu.kit.scc.webreg.entity.RegistryEntity; import edu.kit.scc.webreg.entity.RegistryStatus; @@ -41,6 +47,7 @@ import edu.kit.scc.webreg.entity.UserStatus; import edu.kit.scc.webreg.entity.identity.IdentityEntity; import edu.kit.scc.webreg.exc.UserUpdateException; import edu.kit.scc.webreg.service.UserService; +import edu.kit.scc.webreg.service.user.UserLifecycleManager; import edu.kit.scc.webreg.session.HttpRequestContext; import jakarta.ejb.Stateless; import jakarta.inject.Inject; @@ -56,11 +63,14 @@ public class UserServiceImpl extends BaseServiceImpl<UserEntity> implements User @Inject private UserDao dao; + @Inject + private UserLifecycleManager userLifecycleManager; + @Inject private SamlUserDao samlUserDao; @Inject - private UserUpdater userUpdater; + private SamlUserUpdater userUpdater; @Inject private RegistryDao registryDao; @@ -103,6 +113,39 @@ public class UserServiceImpl extends BaseServiceImpl<UserEntity> implements User return loginInfo; } + @Override + public List<UserEntity> findUsersForExpiryWarning(int limit, int days) { + Date dateBeforeNDays = Date.from(Instant.now().minus(days, DAYS)); + // Find all users, where + // expireWarningSent is null: No expiry warning sent until now + // lastUpdate was before the specified days + // scheduledUpdate is not null: External API users have no scheduled upadte. Only update users, that + // have update schedule set + return dao.findAll(withLimit(limit), SortBy.ascendingBy(UserEntity_.lastUpdate), + and(equal(UserEntity_.userStatus, UserStatus.ACTIVE), isNull(UserEntity_.expireWarningSent), + lessThan(UserEntity_.lastUpdate, dateBeforeNDays), isNotNull(UserEntity_.scheduledUpdate))); + } + + @Override + public List<UserEntity> findUsersForExpiry(int limit, int daysSinceWarning) { + Date dateBeforeNDays = Date.from(Instant.now().minus(daysSinceWarning, DAYS)); + return dao.findAll(withLimit(limit), SortBy.ascendingBy(UserEntity_.expireWarningSent), + and(equal(UserEntity_.userStatus, UserStatus.ACTIVE), isNotNull(UserEntity_.expireWarningSent), + lessThan(UserEntity_.expireWarningSent, dateBeforeNDays), isNotNull(UserEntity_.scheduledUpdate))); + } + + @Override + public void sendUserExpiryWarning(UserEntity user, String emailTemplateName) { + user = dao.fetch(user.getId()); + userLifecycleManager.sendUserExpiryWarning(user, emailTemplateName); + } + + @Override + public void expireUser(UserEntity user, String emailTemplateName) { + user = dao.fetch(user.getId()); + userLifecycleManager.expireUser(user, emailTemplateName); + } + @Override public SamlUserEntity findByPersistent(String spId, String idpId, String persistentId) { return samlUserDao.findByPersistent(spId, idpId, persistentId); @@ -154,7 +197,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserEntity> implements User public SamlUserEntity updateUserFromIdp(SamlUserEntity user, String executor, StringBuffer debugLog) throws UserUpdateException { user = samlUserDao.fetch(user.getId()); - return userUpdater.updateUserFromIdp(user, null, executor, debugLog); + return userUpdater.updateUserFromHomeOrg(user, null, executor, debugLog); } @Override diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdateFromHomeOrgServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdateFromHomeOrgServiceImpl.java index b29800244ff93ad26065dd5a68716722d13a2049..f4447873157ef2e7a0bf0ff103028c61448b7455 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdateFromHomeOrgServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdateFromHomeOrgServiceImpl.java @@ -26,7 +26,6 @@ import edu.kit.scc.webreg.entity.UserStatus; import edu.kit.scc.webreg.entity.oidc.OidcUserEntity; import edu.kit.scc.webreg.exc.UserUpdateException; import edu.kit.scc.webreg.service.UserUpdateFromHomeOrgService; -import edu.kit.scc.webreg.service.oidc.client.OidcUserUpdater; @Stateless public class UserUpdateFromHomeOrgServiceImpl implements UserUpdateFromHomeOrgService, Serializable { @@ -40,7 +39,7 @@ public class UserUpdateFromHomeOrgServiceImpl implements UserUpdateFromHomeOrgSe private UserDao userDao; @Inject - private UserUpdater userUpdater; + private SamlUserUpdater userUpdater; @Inject private OidcUserUpdater oidcUserUpdate; diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdateServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdateServiceImpl.java index 17273094b9f2837621c94f44e80be963c3b0881a..48d47fefbb5ed0caa066dea953ceccad5489cfe5 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdateServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdateServiceImpl.java @@ -52,7 +52,7 @@ public class UserUpdateServiceImpl implements UserUpdateService, Serializable { private UserDao userDao; @Inject - private UserUpdater userUpdater; + private SamlUserUpdater userUpdater; @Inject private KnowledgeSessionSingleton knowledgeSessionService; @@ -276,7 +276,7 @@ public class UserUpdateServiceImpl implements UserUpdateService, Serializable { // TODO check for OIDC user entity (refresh token?) if (user instanceof SamlUserEntity) - user = userUpdater.updateUserFromIdp((SamlUserEntity) user, service, executor, null); + user = userUpdater.updateUserFromHomeOrg((SamlUserEntity) user, service, executor, null); } } catch (UserUpdateException e) { logger.warn("Could not update user (attrq is optional, continue with login process) {}: {}", @@ -298,7 +298,7 @@ public class UserUpdateServiceImpl implements UserUpdateService, Serializable { // TODO check for OIDC user entity (refresh token?) if (user instanceof SamlUserEntity) - user = userUpdater.updateUserFromIdp((SamlUserEntity) user, service, executor, null); + user = userUpdater.updateUserFromHomeOrg((SamlUserEntity) user, service, executor, null); } } catch (UserUpdateException e) { logger.warn("Could not update user {}: {}", e.getMessage(), user.getEppn()); diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthClientCallbackServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthClientCallbackServiceImpl.java index 533c6550b8b3031b778a6b56ff5b01539ee16220..8aa58c851b818c44f2e3e21814c544c8c0312cac 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthClientCallbackServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthClientCallbackServiceImpl.java @@ -51,6 +51,7 @@ import edu.kit.scc.webreg.entity.oauth.OAuthRpFlowStateEntity_; import edu.kit.scc.webreg.entity.oauth.OAuthUserEntity; import edu.kit.scc.webreg.entity.oauth.OAuthUserEntity_; import edu.kit.scc.webreg.exc.UserUpdateException; +import edu.kit.scc.webreg.service.impl.OAuthUserUpdater; import edu.kit.scc.webreg.service.saml.exc.OidcAuthenticationException; import edu.kit.scc.webreg.session.SessionManager; import jakarta.ejb.Stateless; diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthUserCreateService.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthUserCreateService.java index 6a09c921b14735fbdec952fe234d5c0a4d97e0cb..9b2641dd64d52aa8983e30b40cd4ffdc06f7eafc 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthUserCreateService.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthUserCreateService.java @@ -40,8 +40,10 @@ import edu.kit.scc.webreg.event.EventSubmitter; import edu.kit.scc.webreg.event.UserEvent; import edu.kit.scc.webreg.event.exc.EventSubmitException; import edu.kit.scc.webreg.exc.UserUpdateException; +import edu.kit.scc.webreg.service.group.OAuthGroupUpdater; import edu.kit.scc.webreg.service.identity.IdentityCreater; import edu.kit.scc.webreg.service.impl.AttributeMapHelper; +import edu.kit.scc.webreg.service.impl.OAuthUserUpdater; import edu.kit.scc.webreg.session.HttpRequestContext; import jakarta.ejb.Stateless; import jakarta.ejb.TransactionManagement; diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthUserUpdater.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthUserUpdater.java deleted file mode 100644 index 9edf61dc4ab7bba6e0076240622b9a7aedda42f5..0000000000000000000000000000000000000000 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthUserUpdater.java +++ /dev/null @@ -1,473 +0,0 @@ -package edu.kit.scc.webreg.service.oauth.client; - -import java.lang.reflect.InvocationTargetException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Random; -import java.util.Set; - -import org.apache.commons.beanutils.PropertyUtils; -import org.slf4j.Logger; -import org.slf4j.MDC; - -import edu.kit.scc.webreg.audit.Auditor; -import edu.kit.scc.webreg.audit.RegistryAuditor; -import edu.kit.scc.webreg.audit.UserUpdateAuditor; -import edu.kit.scc.webreg.bootstrap.ApplicationConfig; -import edu.kit.scc.webreg.dao.RegistryDao; -import edu.kit.scc.webreg.dao.SerialDao; -import edu.kit.scc.webreg.dao.as.ASUserAttrDao; -import edu.kit.scc.webreg.dao.audit.AuditDetailDao; -import edu.kit.scc.webreg.dao.audit.AuditEntryDao; -import edu.kit.scc.webreg.dao.jpa.oauth.OAuthUserDao; -import edu.kit.scc.webreg.entity.EventType; -import edu.kit.scc.webreg.entity.GroupEntity; -import edu.kit.scc.webreg.entity.RegistryEntity; -import edu.kit.scc.webreg.entity.RegistryStatus; -import edu.kit.scc.webreg.entity.ServiceEntity; -import edu.kit.scc.webreg.entity.ServiceEntity_; -import edu.kit.scc.webreg.entity.UserEntity; -import edu.kit.scc.webreg.entity.UserStatus; -import edu.kit.scc.webreg.entity.as.ASUserAttrEntity; -import edu.kit.scc.webreg.entity.as.AttributeSourceServiceEntity; -import edu.kit.scc.webreg.entity.attribute.IncomingAttributeSetEntity; -import edu.kit.scc.webreg.entity.audit.AuditStatus; -import edu.kit.scc.webreg.entity.oauth.OAuthUserEntity; -import edu.kit.scc.webreg.event.EventSubmitter; -import edu.kit.scc.webreg.event.UserEvent; -import edu.kit.scc.webreg.event.exc.EventSubmitException; -import edu.kit.scc.webreg.exc.RegisterException; -import edu.kit.scc.webreg.exc.UserUpdateException; -import edu.kit.scc.webreg.hook.HookManager; -import edu.kit.scc.webreg.hook.UserServiceHook; -import edu.kit.scc.webreg.service.ServiceService; -import edu.kit.scc.webreg.service.attribute.IncomingOAuthAttributesHandler; -import edu.kit.scc.webreg.service.identity.IdentityUpdater; -import edu.kit.scc.webreg.service.impl.AbstractUserUpdater; -import edu.kit.scc.webreg.service.impl.AttributeMapHelper; -import edu.kit.scc.webreg.service.reg.AttributeSourceQueryService; -import edu.kit.scc.webreg.service.reg.impl.Registrator; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -@ApplicationScoped -public class OAuthUserUpdater extends AbstractUserUpdater<OAuthUserEntity> { - - private static final long serialVersionUID = 1L; - - @Inject - private Logger logger; - - @Inject - private AuditEntryDao auditDao; - - @Inject - private AuditDetailDao auditDetailDao; - - @Inject - private OAuthUserDao userDao; - - @Inject - private ServiceService serviceService; - - @Inject - private RegistryDao registryDao; - - @Inject - private SerialDao serialDao; - - @Inject - private HookManager hookManager; - - @Inject - private OAuthGroupUpdater oauthGroupUpdater; - - @Inject - private ASUserAttrDao asUserAttrDao; - - @Inject - private AttributeSourceQueryService attributeSourceQueryService; - - @Inject - private EventSubmitter eventSubmitter; - - @Inject - private ApplicationConfig appConfig; - - @Inject - private Registrator registrator; - - @Inject - private AttributeMapHelper attrHelper; - - @Inject - private IdentityUpdater identityUpdater; - - @Inject - private IncomingOAuthAttributesHandler incomingAttributeHandler; - - public OAuthUserEntity updateUserFromOP(OAuthUserEntity user, String executor, StringBuffer debugLog) - throws UserUpdateException { - throw new UserUpdateException("Not implemented"); - } - - @Override - public OAuthUserEntity updateUser(OAuthUserEntity user, Map<String, List<Object>> attributeMap, String executor, - StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - return updateUser(user, attributeMap, executor, null, null, lastLoginHost); - } - - @Override - public OAuthUserEntity updateUser(OAuthUserEntity user, Map<String, List<Object>> attributeMap, String executor, - ServiceEntity service, StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - MDC.put("userId", "" + user.getId()); - logger.debug("Updating OIDC user {}", user.getEppn()); - - boolean changed = false; - - UserUpdateAuditor auditor = new UserUpdateAuditor(auditDao, auditDetailDao, appConfig); - auditor.startAuditTrail(executor); - auditor.setName(getClass().getName() + "-UserUpdate-Audit"); - auditor.setDetail("Update OAuth user " + user.getOauthId()); - - changed |= preUpdateUser(user, attributeMap, user.getOauthIssuer().getGenericStore(), executor, service, debugLog); - - // List to store parent services, that are not registered. Need to be registered - // later, when attribute map is populated - List<ServiceEntity> delayedRegisterList = new ArrayList<ServiceEntity>(); - - /** - * put no_assertion_count in generic store if assertion is missing. Else reset - * no assertion count and put last valid assertion date in - */ - if (attributeMap == null) { - if (!user.getGenericStore().containsKey("no_assertion_count")) { - user.getGenericStore().put("no_assertion_count", "1"); - } else { - user.getGenericStore().put("no_assertion_count", - "" + (Long.parseLong(user.getGenericStore().get("no_assertion_count")) + 1L)); - } - - logger.info("No attribute for user {}, skipping updateFromAttribute", user.getEppn()); - - user.getAttributeStore().clear(); - - if (UserStatus.ACTIVE.equals(user.getUserStatus())) { - changeUserStatus(user, UserStatus.ON_HOLD, auditor); - - /* - * Also flag all registries for user ON_HOLD - */ - List<RegistryEntity> registryList = registryDao.findByUserAndStatus(user, RegistryStatus.ACTIVE, - RegistryStatus.LOST_ACCESS, RegistryStatus.INVALID); - for (RegistryEntity registry : registryList) { - changeRegistryStatus(registry, RegistryStatus.ON_HOLD, "user-on-hold", auditor); - } - } - } else { - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - user.getGenericStore().put("no_assertion_count", "0"); - user.getGenericStore().put("last_valid_assertion", df.format(new Date())); - - changed |= updateUserFromAttribute(user, attributeMap, auditor); - - if (UserStatus.ON_HOLD.equals(user.getUserStatus())) { - changeUserStatus(user, UserStatus.ACTIVE, auditor); - - /* - * Also reenable all registries for user to LOST_ACCESS. They are rechecked then - */ - List<RegistryEntity> registryList = registryDao.findByUserAndStatus(user, RegistryStatus.ON_HOLD); - for (RegistryEntity registry : registryList) { - changeRegistryStatus(registry, RegistryStatus.LOST_ACCESS, "user-reactivated", auditor); - - /* - * check if parent registry is missing - */ - if (registry.getService().getParentService() != null) { - List<RegistryEntity> parentRegistryList = registryDao.findByServiceAndIdentityAndNotStatus( - registry.getService().getParentService(), user.getIdentity(), RegistryStatus.DELETED, - RegistryStatus.DEPROVISIONED); - if (parentRegistryList.size() == 0) { - delayedRegisterList.add(registry.getService().getParentService()); - } - } - } - - /* - * fire a user changed event to be sure, when the user is activated - */ - changed = true; - } - - /* - * if service is set, update only attribute sources spcific for this service. - * Else update all (login via web or generic attribute query) - */ - if (service != null) { - service = serviceService.findByIdWithAttrs(service.getId(), ServiceEntity_.attributeSourceService); - - for (AttributeSourceServiceEntity asse : service.getAttributeSourceService()) { - changed |= attributeSourceQueryService.updateUserAttributes(user, asse.getAttributeSource(), - executor); - } - } else { - List<ASUserAttrEntity> asUserAttrList = asUserAttrDao.findForUser(user); - for (ASUserAttrEntity asUserAttr : asUserAttrList) { - changed |= attributeSourceQueryService.updateUserAttributes(user, asUserAttr.getAttributeSource(), - executor); - } - } - - Set<GroupEntity> changedGroups = oauthGroupUpdater.updateGroupsForUser(user, attributeMap, auditor); - - if (changedGroups.size() > 0) { - changed = true; - } - - Map<String, String> attributeStore = user.getAttributeStore(); - for (Entry<String, List<Object>> entry : attributeMap.entrySet()) { - attributeStore.put(entry.getKey(), attrHelper.attributeListToString(entry.getValue())); - } - - IncomingAttributeSetEntity incomingAttributeSet = incomingAttributeHandler.createOrUpdateAttributes(user, attributeMap); - incomingAttributeHandler.processIncomingAttributeSet(incomingAttributeSet); - - identityUpdater.updateIdentity(user); - - if (appConfig.getConfigValue("create_missing_eppn_scope") != null) { - if (user.getEppn() == null) { - String scope = appConfig.getConfigValue("create_missing_eppn_scope"); - user.setEppn(user.getIdentity().getGeneratedLocalUsername() + "@" + scope); - changed = true; - } - } - } - - for (ServiceEntity delayedService : delayedRegisterList) { - try { - registrator.registerUser(user, delayedService, "user-" + user.getId(), false); - } catch (RegisterException e) { - logger.warn("Parent registrytion didn't work out like it should", e); - } - } - - changed |= postUpdateUser(user, attributeMap, user.getOauthIssuer().getGenericStore(), executor, service, debugLog, - lastLoginHost); - - user.setLastUpdate(new Date()); - user.setLastFailedUpdate(null); - user.setScheduledUpdate(getNextScheduledUpdate()); - - if (changed) { - fireUserChangeEvent(user, auditor.getActualExecutor(), auditor); - } - - auditor.setUser(user); - auditor.finishAuditTrail(); - auditor.commitAuditTrail(); - - return user; - } - -// public OAuthUserEntity updateUser(OAuthUserEntity user, IDTokenClaimsSet claims, UserInfo userInfo, -// RefreshToken refreshToken, BearerAccessToken bat, String executor, ServiceEntity service, -// StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { -// -// Map<String, List<Object>> attributeMap = oidcTokenHelper.convertToAttributeMap(claims, userInfo, refreshToken, -// bat); -// -// if (service != null) -// return updateUser(user, attributeMap, executor, service, debugLog, lastLoginHost); -// else -// return updateUser(user, attributeMap, executor, debugLog, lastLoginHost); -// } - -// public OAuthUserEntity updateUser(OAuthUserEntity user, IDTokenClaimsSet claims, UserInfo userInfo, -// RefreshToken refreshToken, BearerAccessToken bat, String executor, StringBuffer debugLog, -// String lastLoginHost) throws UserUpdateException { -// -// return updateUser(user, claims, userInfo, refreshToken, bat, executor, null, debugLog, lastLoginHost); -// } - - protected void fireUserChangeEvent(UserEntity user, String executor, Auditor auditor) { - - UserEvent userEvent = new UserEvent(user, auditor.getAudit()); - - try { - eventSubmitter.submit(userEvent, EventType.USER_UPDATE, executor); - } catch (EventSubmitException e) { - logger.warn("Could not submit event", e); - } - } - - public boolean updateUserNew(OAuthUserEntity user, Map<String, List<Object>> attributeMap, String executor, - Auditor auditor, StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - boolean changed = false; - - changed |= preUpdateUser(user, attributeMap, user.getOauthIssuer().getGenericStore(), executor, null, debugLog); - changed |= updateUserFromAttribute(user, attributeMap, auditor); - changed |= postUpdateUser(user, attributeMap, user.getOauthIssuer().getGenericStore(), executor, null, debugLog, - lastLoginHost); - - return changed; - } - - public boolean updateUserFromAttribute(UserEntity user, Map<String, List<Object>> attributeMap, Auditor auditor) - throws UserUpdateException { - return updateUserFromAttribute(user, attributeMap, false, auditor); - } - - public boolean updateUserFromAttribute(UserEntity user, Map<String, List<Object>> attributeMap, - boolean withoutUidNumber, Auditor auditor) throws UserUpdateException { - - boolean changed = false; - - UserServiceHook completeOverrideHook = null; - Set<UserServiceHook> activeHooks = new HashSet<UserServiceHook>(); - - for (UserServiceHook hook : hookManager.getUserHooks()) { - if (hook.isResponsible(user, attributeMap)) { - - hook.preUpdateUserFromAttribute(user, attributeMap, auditor); - activeHooks.add(hook); - - if (hook.isCompleteOverride()) { - completeOverrideHook = hook; - } - } - } - - if (completeOverrideHook == null) { - @SuppressWarnings("unchecked") - HashMap<String, Object> userMap = (HashMap<String, Object>) attributeMap.get("user").get(0); - - if (userMap.get("email") != null && (userMap.get("email") instanceof String)) - changed |= compareAndChangeProperty(user, "email", (String) userMap.get("email"), auditor); - else - changed |= compareAndChangeProperty(user, "email", null, auditor); - - if (userMap.get("name") != null && (userMap.get("name") instanceof String)) - changed |= compareAndChangeProperty(user, "name", (String) userMap.get("name"), auditor); - else - changed |= compareAndChangeProperty(user, "name", null, auditor); - - if ((!withoutUidNumber) && (user.getUidNumber() == null)) { - user.setUidNumber(serialDao.nextUidNumber().intValue()); - logger.info("Setting UID Number {} for user {}", user.getUidNumber(), user.getEppn()); - auditor.logAction(user.getEppn(), "SET FIELD", "uidNumber", "" + user.getUidNumber(), - AuditStatus.SUCCESS); - changed = true; - } - } else { - logger.info("Overriding standard User Update Mechanism! Activator: {}", - completeOverrideHook.getClass().getName()); - } - - for (UserServiceHook hook : activeHooks) { - hook.postUpdateUserFromAttribute(user, attributeMap, auditor); - } - - return changed; - } - - private boolean compareAndChangeProperty(UserEntity user, String property, String value, Auditor auditor) { - String s = null; - String action = null; - - try { - Object actualValue = PropertyUtils.getProperty(user, property); - - if (actualValue != null && actualValue.equals(value)) { - // Value didn't change, do nothing - return false; - } - - if (actualValue == null && value == null) { - // Value stayed null - return false; - } - - if (actualValue == null) { - s = "null"; - action = "SET FIELD"; - } else { - s = actualValue.toString(); - action = "UPDATE FIELD"; - } - - s = s + " -> " + value; - if (s.length() > 1017) - s = s.substring(0, 1017) + "..."; - - PropertyUtils.setProperty(user, property, value); - - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.SUCCESS); - } catch (IllegalAccessException e) { - logger.warn("This probably shouldn't happen: ", e); - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); - } catch (InvocationTargetException e) { - logger.warn("This probably shouldn't happen: ", e); - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); - } catch (NoSuchMethodException e) { - logger.warn("This probably shouldn't happen: ", e); - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); - } - - return true; - } - - protected void changeUserStatus(UserEntity user, UserStatus toStatus, Auditor auditor) { - UserStatus fromStatus = user.getUserStatus(); - user.setUserStatus(toStatus); - user.setLastStatusChange(new Date()); - - logger.debug("{}: change user status from {} to {}", user.getEppn(), fromStatus, toStatus); - auditor.logAction(user.getEppn(), "CHANGE STATUS", fromStatus + " -> " + toStatus, - "Change status " + fromStatus + " -> " + toStatus, AuditStatus.SUCCESS); - } - - protected void changeRegistryStatus(RegistryEntity registry, RegistryStatus toStatus, String statusMessage, - Auditor parentAuditor) { - RegistryStatus fromStatus = registry.getRegistryStatus(); - registry.setRegistryStatus(toStatus); - registry.setStatusMessage(statusMessage); - registry.setLastStatusChange(new Date()); - - logger.debug("{} {} {}: change registry status from {} to {}", new Object[] { registry.getUser().getEppn(), - registry.getService().getShortName(), registry.getId(), fromStatus, toStatus }); - RegistryAuditor registryAuditor = new RegistryAuditor(auditDao, auditDetailDao, appConfig); - registryAuditor.setParent(parentAuditor); - registryAuditor.startAuditTrail(parentAuditor.getActualExecutor()); - registryAuditor.setName(getClass().getName() + "-UserUpdate-Registry-Audit"); - registryAuditor.setDetail("Update registry " + registry.getId() + " for user " + registry.getUser().getEppn()); - registryAuditor.setRegistry(registry); - registryAuditor.logAction(registry.getUser().getEppn(), "CHANGE STATUS", "registry-" + registry.getId(), - "Change status " + fromStatus + " -> " + toStatus, AuditStatus.SUCCESS); - registryAuditor.finishAuditTrail(); - } - - private Date getNextScheduledUpdate() { - Long futureMillis = 30L * 24L * 60L * 60L * 1000L; - if (appConfig.getConfigOptions().containsKey("update_schedule_future")) { - futureMillis = Long.decode(appConfig.getConfigValue("update_schedule_future")); - } - Integer futureMillisRandom = 6 * 60 * 60 * 1000; - if (appConfig.getConfigOptions().containsKey("update_schedule_future_random")) { - futureMillisRandom = Integer.decode(appConfig.getConfigValue("update_schedule_future_random")); - } - Random r = new Random(); - return new Date(System.currentTimeMillis() + futureMillis + r.nextInt(futureMillisRandom)); - } - - protected void updateFail(OAuthUserEntity user) { - user.setLastFailedUpdate(new Date()); - user.setScheduledUpdate(getNextScheduledUpdate()); - } -} diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcClientCallbackServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcClientCallbackServiceImpl.java index f94222b2c3afb2b5ba8af9d06eaa6b2179112308..4f99eaa5169bddce6f41eb8590976b5a3743d245 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcClientCallbackServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcClientCallbackServiceImpl.java @@ -42,6 +42,7 @@ import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet; import com.nimbusds.openid.connect.sdk.claims.UserInfo; import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator; +import edu.kit.scc.regapp.oidc.tools.OidcOpMetadataSingletonBean; import edu.kit.scc.regapp.oidc.tools.OidcTokenHelper; import edu.kit.scc.webreg.annotations.RetryTransaction; import edu.kit.scc.webreg.dao.UserLoginInfoDao; @@ -56,6 +57,7 @@ import edu.kit.scc.webreg.entity.oidc.OidcRpFlowStateEntity_; import edu.kit.scc.webreg.entity.oidc.OidcUserEntity; import edu.kit.scc.webreg.entity.oidc.OidcUserEntity_; import edu.kit.scc.webreg.exc.UserUpdateException; +import edu.kit.scc.webreg.service.impl.OidcUserUpdater; import edu.kit.scc.webreg.service.saml.exc.OidcAuthenticationException; import edu.kit.scc.webreg.session.SessionManager; import jakarta.ejb.Stateless; diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcClientRedirectServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcClientRedirectServiceImpl.java index 151aaf1fa6fd307fc8af22455f7030bb7ef179bf..09bce84ff15c2c54a66a7054c58fddf3d0f043f2 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcClientRedirectServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcClientRedirectServiceImpl.java @@ -22,6 +22,7 @@ import com.nimbusds.oauth2.sdk.id.State; import com.nimbusds.openid.connect.sdk.AuthenticationRequest; import com.nimbusds.openid.connect.sdk.Nonce; +import edu.kit.scc.regapp.oidc.tools.OidcOpMetadataSingletonBean; import edu.kit.scc.webreg.annotations.RetryTransaction; import edu.kit.scc.webreg.dao.oidc.OidcRpConfigurationDao; import edu.kit.scc.webreg.dao.oidc.OidcRpFlowStateDao; diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserCreateServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserCreateServiceImpl.java index 2031fd5f5b3fb2e017d550e2f97d9ae22361a8e6..65faf7fe4db30ecb06bc9da3c0728cbf2216ca56 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserCreateServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserCreateServiceImpl.java @@ -44,8 +44,10 @@ import edu.kit.scc.webreg.event.EventSubmitter; import edu.kit.scc.webreg.event.UserEvent; import edu.kit.scc.webreg.event.exc.EventSubmitException; import edu.kit.scc.webreg.exc.UserUpdateException; +import edu.kit.scc.webreg.service.group.OidcGroupUpdater; import edu.kit.scc.webreg.service.identity.IdentityCreater; import edu.kit.scc.webreg.service.impl.AttributeMapHelper; +import edu.kit.scc.webreg.service.impl.OidcUserUpdater; import edu.kit.scc.webreg.session.HttpRequestContext; import jakarta.ejb.Stateless; import jakarta.ejb.TransactionManagement; diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserServiceImpl.java index 98090b540a03612e3c520975f1403fe214e371a3..e7a753f38bb0aa55a3fb9e3d456bd5d24c7b8fd2 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserServiceImpl.java @@ -20,6 +20,7 @@ import edu.kit.scc.webreg.dao.oidc.OidcUserDao; import edu.kit.scc.webreg.entity.oidc.OidcUserEntity; import edu.kit.scc.webreg.exc.UserUpdateException; import edu.kit.scc.webreg.service.impl.BaseServiceImpl; +import edu.kit.scc.webreg.service.impl.OidcUserUpdater; @Stateless public class OidcUserServiceImpl extends BaseServiceImpl<OidcUserEntity> implements OidcUserService, Serializable { diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserUpdater.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserUpdater.java deleted file mode 100644 index 70265d2b7ccba72be5065de0ad90a6941cfd7c75..0000000000000000000000000000000000000000 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcUserUpdater.java +++ /dev/null @@ -1,605 +0,0 @@ -package edu.kit.scc.webreg.service.oidc.client; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Random; -import java.util.Set; - -import org.apache.commons.beanutils.PropertyUtils; -import org.slf4j.Logger; -import org.slf4j.MDC; - -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.proc.BadJOSEException; -import com.nimbusds.jwt.JWT; -import com.nimbusds.jwt.JWTParser; -import com.nimbusds.oauth2.sdk.AuthorizationGrant; -import com.nimbusds.oauth2.sdk.ErrorObject; -import com.nimbusds.oauth2.sdk.ParseException; -import com.nimbusds.oauth2.sdk.RefreshTokenGrant; -import com.nimbusds.oauth2.sdk.TokenErrorResponse; -import com.nimbusds.oauth2.sdk.TokenRequest; -import com.nimbusds.oauth2.sdk.TokenResponse; -import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; -import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; -import com.nimbusds.oauth2.sdk.auth.Secret; -import com.nimbusds.oauth2.sdk.http.HTTPResponse; -import com.nimbusds.oauth2.sdk.id.ClientID; -import com.nimbusds.oauth2.sdk.id.Issuer; -import com.nimbusds.oauth2.sdk.token.BearerAccessToken; -import com.nimbusds.oauth2.sdk.token.RefreshToken; -import com.nimbusds.openid.connect.sdk.OIDCTokenResponse; -import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser; -import com.nimbusds.openid.connect.sdk.UserInfoRequest; -import com.nimbusds.openid.connect.sdk.UserInfoResponse; -import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet; -import com.nimbusds.openid.connect.sdk.claims.UserInfo; -import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator; - -import edu.kit.scc.regapp.oidc.tools.OidcTokenHelper; -import edu.kit.scc.webreg.audit.Auditor; -import edu.kit.scc.webreg.audit.RegistryAuditor; -import edu.kit.scc.webreg.audit.UserUpdateAuditor; -import edu.kit.scc.webreg.bootstrap.ApplicationConfig; -import edu.kit.scc.webreg.dao.RegistryDao; -import edu.kit.scc.webreg.dao.SerialDao; -import edu.kit.scc.webreg.dao.as.ASUserAttrDao; -import edu.kit.scc.webreg.dao.audit.AuditDetailDao; -import edu.kit.scc.webreg.dao.audit.AuditEntryDao; -import edu.kit.scc.webreg.dao.oidc.OidcUserDao; -import edu.kit.scc.webreg.entity.EventType; -import edu.kit.scc.webreg.entity.GroupEntity; -import edu.kit.scc.webreg.entity.RegistryEntity; -import edu.kit.scc.webreg.entity.RegistryStatus; -import edu.kit.scc.webreg.entity.ServiceEntity; -import edu.kit.scc.webreg.entity.ServiceEntity_; -import edu.kit.scc.webreg.entity.UserEntity; -import edu.kit.scc.webreg.entity.UserStatus; -import edu.kit.scc.webreg.entity.as.ASUserAttrEntity; -import edu.kit.scc.webreg.entity.as.AttributeSourceServiceEntity; -import edu.kit.scc.webreg.entity.attribute.IncomingAttributeSetEntity; -import edu.kit.scc.webreg.entity.audit.AuditStatus; -import edu.kit.scc.webreg.entity.oidc.OidcRpConfigurationEntity; -import edu.kit.scc.webreg.entity.oidc.OidcUserEntity; -import edu.kit.scc.webreg.event.EventSubmitter; -import edu.kit.scc.webreg.event.UserEvent; -import edu.kit.scc.webreg.event.exc.EventSubmitException; -import edu.kit.scc.webreg.exc.RegisterException; -import edu.kit.scc.webreg.exc.UserUpdateException; -import edu.kit.scc.webreg.hook.HookManager; -import edu.kit.scc.webreg.hook.UserServiceHook; -import edu.kit.scc.webreg.service.ServiceService; -import edu.kit.scc.webreg.service.attribute.IncomingOidcAttributesHandler; -import edu.kit.scc.webreg.service.identity.IdentityUpdater; -import edu.kit.scc.webreg.service.impl.AbstractUserUpdater; -import edu.kit.scc.webreg.service.impl.AttributeMapHelper; -import edu.kit.scc.webreg.service.reg.AttributeSourceQueryService; -import edu.kit.scc.webreg.service.reg.impl.Registrator; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -@ApplicationScoped -public class OidcUserUpdater extends AbstractUserUpdater<OidcUserEntity> { - - private static final long serialVersionUID = 1L; - - @Inject - private Logger logger; - - @Inject - private AuditEntryDao auditDao; - - @Inject - private AuditDetailDao auditDetailDao; - - @Inject - private OidcUserDao userDao; - - @Inject - private ServiceService serviceService; - - @Inject - private RegistryDao registryDao; - - @Inject - private SerialDao serialDao; - - @Inject - private HookManager hookManager; - - @Inject - private OidcGroupUpdater oidcGroupUpdater; - - @Inject - private ASUserAttrDao asUserAttrDao; - - @Inject - private AttributeSourceQueryService attributeSourceQueryService; - - @Inject - private EventSubmitter eventSubmitter; - - @Inject - private ApplicationConfig appConfig; - - @Inject - private Registrator registrator; - - @Inject - private AttributeMapHelper attrHelper; - - @Inject - private OidcTokenHelper oidcTokenHelper; - - @Inject - private OidcOpMetadataSingletonBean opMetadataBean; - - @Inject - private IdentityUpdater identityUpdater; - - @Inject - private IncomingOidcAttributesHandler incomingAttributeHandler; - - public OidcUserEntity updateUserFromOP(OidcUserEntity user, String executor, StringBuffer debugLog) - throws UserUpdateException { - - try { - /** - * TODO Implement refresh here - */ - OidcRpConfigurationEntity rpConfig = user.getIssuer(); - - if (user.getAttributeStore().get("refreshToken") == null) { - updateFail(user); - throw new UserUpdateException("refresh token is null"); - } - - RefreshToken token = new RefreshToken(user.getAttributeStore().get("refreshToken")); - AuthorizationGrant refreshTokenGrant = new RefreshTokenGrant(token); - - ClientID clientID = new ClientID(user.getIssuer().getClientId()); - Secret clientSecret = new Secret(user.getIssuer().getSecret()); - ClientAuthentication clientAuth = new ClientSecretBasic(clientID, clientSecret); - - TokenRequest tokenRequest = new TokenRequest(opMetadataBean.getTokenEndpointURI(user.getIssuer()), - clientAuth, refreshTokenGrant); - TokenResponse tokenResponse = OIDCTokenResponseParser.parse(tokenRequest.toHTTPRequest().send()); - - if (!tokenResponse.indicatesSuccess()) { - TokenErrorResponse errorResponse = tokenResponse.toErrorResponse(); - ErrorObject error = errorResponse.getErrorObject(); - logger.info("Got error: code {}, desc {}, http-status {}, uri {}", error.getCode(), - error.getDescription()); - updateFail(user); - } else { - OIDCTokenResponse oidcTokenResponse = (OIDCTokenResponse) tokenResponse.toSuccessResponse(); - logger.debug("response: {}", oidcTokenResponse.toJSONObject()); - - JWT idToken = oidcTokenResponse.getOIDCTokens().getIDToken(); - IDTokenClaimsSet claims = null; - - if (idToken != null) { - IDTokenValidator validator = new IDTokenValidator(new Issuer(rpConfig.getServiceUrl()), - new ClientID(rpConfig.getClientId()), JWSAlgorithm.RS256, - opMetadataBean.getJWKSetURI(rpConfig).toURL()); - - try { - claims = validator.validate(idToken, null); - logger.debug("Got signed claims verified from {}: {}", claims.getIssuer(), claims.getSubject()); - } catch (BadJOSEException | JOSEException e) { - throw new UserUpdateException("signature failed: " + e.getMessage()); - } - } - - RefreshToken refreshToken = null; - - if (oidcTokenResponse.getOIDCTokens().getRefreshToken() != null) { - - refreshToken = oidcTokenResponse.getOIDCTokens().getRefreshToken(); - try { - JWT refreshJwt = JWTParser.parse(refreshToken.getValue()); - // Well, what to do with this info? Check if refresh token is short or long - // lived? <1 day? - logger.info("refresh will expire at: {}", refreshJwt.getJWTClaimsSet().getExpirationTime()); - } catch (java.text.ParseException e) { - logger.debug("Refresh token is no JWT"); - } - } else { - logger.info("Answer contains no new refresh token, keeping old one"); - } - - BearerAccessToken bearerAccessToken = oidcTokenResponse.getOIDCTokens().getBearerAccessToken(); - - HTTPResponse httpResponse = new UserInfoRequest(opMetadataBean.getUserInfoEndpointURI(rpConfig), - bearerAccessToken).toHTTPRequest().send(); - - UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse); - - if (!userInfoResponse.indicatesSuccess()) { - throw new UserUpdateException("got userinfo error response: " - + userInfoResponse.toErrorResponse().getErrorObject().getDescription()); - } - - UserInfo userInfo = userInfoResponse.toSuccessResponse().getUserInfo(); - logger.info("userinfo {}, {}, {}", userInfo.getSubject(), userInfo.getPreferredUsername(), - userInfo.getEmailAddress()); - - logger.debug("Updating OIDC user {}", user.getSubjectId()); - - user = updateUser(user, claims, userInfo, refreshToken, bearerAccessToken, "web-sso", debugLog, null); - - } - } catch (IOException | ParseException e) { - logger.warn("Exception!", e); - } - - return user; - } - - @Override - public OidcUserEntity updateUser(OidcUserEntity user, Map<String, List<Object>> attributeMap, String executor, - StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - return updateUser(user, attributeMap, executor, null, null, lastLoginHost); - } - - @Override - public OidcUserEntity updateUser(OidcUserEntity user, Map<String, List<Object>> attributeMap, String executor, - ServiceEntity service, StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - MDC.put("userId", "" + user.getId()); - logger.debug("Updating OIDC user {}", user.getEppn()); - - boolean changed = false; - - UserUpdateAuditor auditor = new UserUpdateAuditor(auditDao, auditDetailDao, appConfig); - auditor.startAuditTrail(executor); - auditor.setName(getClass().getName() + "-UserUpdate-Audit"); - auditor.setDetail("Update OIDC user " + user.getSubjectId()); - - changed |= preUpdateUser(user, attributeMap, user.getIssuer().getGenericStore(), executor, service, debugLog); - - // List to store parent services, that are not registered. Need to be registered - // later, when attribute map is populated - List<ServiceEntity> delayedRegisterList = new ArrayList<ServiceEntity>(); - - /** - * put no_assertion_count in generic store if assertion is missing. Else reset - * no assertion count and put last valid assertion date in - */ - if (attributeMap == null) { - if (!user.getGenericStore().containsKey("no_assertion_count")) { - user.getGenericStore().put("no_assertion_count", "1"); - } else { - user.getGenericStore().put("no_assertion_count", - "" + (Long.parseLong(user.getGenericStore().get("no_assertion_count")) + 1L)); - } - - logger.info("No attribute for user {}, skipping updateFromAttribute", user.getEppn()); - - user.getAttributeStore().clear(); - - if (UserStatus.ACTIVE.equals(user.getUserStatus())) { - changeUserStatus(user, UserStatus.ON_HOLD, auditor); - - /* - * Also flag all registries for user ON_HOLD - */ - List<RegistryEntity> registryList = registryDao.findByUserAndStatus(user, RegistryStatus.ACTIVE, - RegistryStatus.LOST_ACCESS, RegistryStatus.INVALID); - for (RegistryEntity registry : registryList) { - changeRegistryStatus(registry, RegistryStatus.ON_HOLD, "user-on-hold", auditor); - } - } - } else { - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - user.getGenericStore().put("no_assertion_count", "0"); - user.getGenericStore().put("last_valid_assertion", df.format(new Date())); - - changed |= updateUserFromAttribute(user, attributeMap, auditor); - - if (UserStatus.ON_HOLD.equals(user.getUserStatus())) { - changeUserStatus(user, UserStatus.ACTIVE, auditor); - - /* - * Also reenable all registries for user to LOST_ACCESS. They are rechecked then - */ - List<RegistryEntity> registryList = registryDao.findByUserAndStatus(user, RegistryStatus.ON_HOLD); - for (RegistryEntity registry : registryList) { - changeRegistryStatus(registry, RegistryStatus.LOST_ACCESS, "user-reactivated", auditor); - - /* - * check if parent registry is missing - */ - if (registry.getService().getParentService() != null) { - List<RegistryEntity> parentRegistryList = registryDao.findByServiceAndIdentityAndNotStatus( - registry.getService().getParentService(), user.getIdentity(), RegistryStatus.DELETED, - RegistryStatus.DEPROVISIONED); - if (parentRegistryList.size() == 0) { - delayedRegisterList.add(registry.getService().getParentService()); - } - } - } - - /* - * fire a user changed event to be sure, when the user is activated - */ - changed = true; - } - - /* - * if service is set, update only attribute sources spcific for this service. - * Else update all (login via web or generic attribute query) - */ - if (service != null) { - service = serviceService.findByIdWithAttrs(service.getId(), ServiceEntity_.attributeSourceService); - - for (AttributeSourceServiceEntity asse : service.getAttributeSourceService()) { - changed |= attributeSourceQueryService.updateUserAttributes(user, asse.getAttributeSource(), - executor); - } - } else { - List<ASUserAttrEntity> asUserAttrList = asUserAttrDao.findForUser(user); - for (ASUserAttrEntity asUserAttr : asUserAttrList) { - changed |= attributeSourceQueryService.updateUserAttributes(user, asUserAttr.getAttributeSource(), - executor); - } - } - - Set<GroupEntity> changedGroups = oidcGroupUpdater.updateGroupsForUser(user, attributeMap, auditor); - - if (changedGroups.size() > 0) { - changed = true; - } - - Map<String, String> attributeStore = user.getAttributeStore(); - for (Entry<String, List<Object>> entry : attributeMap.entrySet()) { - attributeStore.put(entry.getKey(), attrHelper.attributeListToString(entry.getValue())); - } - - IncomingAttributeSetEntity incomingAttributeSet = incomingAttributeHandler.createOrUpdateAttributes(user, attributeMap); - incomingAttributeHandler.processIncomingAttributeSet(incomingAttributeSet); - - identityUpdater.updateIdentity(user); - - if (appConfig.getConfigValue("create_missing_eppn_scope") != null) { - if (user.getEppn() == null) { - String scope = appConfig.getConfigValue("create_missing_eppn_scope"); - user.setEppn(user.getIdentity().getGeneratedLocalUsername() + "@" + scope); - changed = true; - } - } - } - - for (ServiceEntity delayedService : delayedRegisterList) { - try { - registrator.registerUser(user, delayedService, "user-" + user.getId(), false); - } catch (RegisterException e) { - logger.warn("Parent registrytion didn't work out like it should", e); - } - } - - changed |= postUpdateUser(user, attributeMap, user.getIssuer().getGenericStore(), executor, service, debugLog, - lastLoginHost); - - user.setLastUpdate(new Date()); - user.setLastFailedUpdate(null); - //user.setExpireWarningSent(null); - //user.setExpiredSent(null); - user.setScheduledUpdate(getNextScheduledUpdate()); - - if (changed) { - fireUserChangeEvent(user, auditor.getActualExecutor(), auditor); - } - - auditor.setUser(user); - auditor.finishAuditTrail(); - auditor.commitAuditTrail(); - - return user; - } - - public OidcUserEntity updateUser(OidcUserEntity user, IDTokenClaimsSet claims, UserInfo userInfo, - RefreshToken refreshToken, BearerAccessToken bat, String executor, ServiceEntity service, - StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - - Map<String, List<Object>> attributeMap = oidcTokenHelper.convertToAttributeMap(claims, userInfo, refreshToken, - bat); - - if (service != null) - return updateUser(user, attributeMap, executor, service, debugLog, lastLoginHost); - else - return updateUser(user, attributeMap, executor, debugLog, lastLoginHost); - } - - public OidcUserEntity updateUser(OidcUserEntity user, IDTokenClaimsSet claims, UserInfo userInfo, - RefreshToken refreshToken, BearerAccessToken bat, String executor, StringBuffer debugLog, - String lastLoginHost) throws UserUpdateException { - - return updateUser(user, claims, userInfo, refreshToken, bat, executor, null, debugLog, lastLoginHost); - } - - protected void fireUserChangeEvent(UserEntity user, String executor, Auditor auditor) { - - UserEvent userEvent = new UserEvent(user, auditor.getAudit()); - - try { - eventSubmitter.submit(userEvent, EventType.USER_UPDATE, executor); - } catch (EventSubmitException e) { - logger.warn("Could not submit event", e); - } - } - - public boolean updateUserNew(OidcUserEntity user, Map<String, List<Object>> attributeMap, String executor, - Auditor auditor, StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - boolean changed = false; - - changed |= preUpdateUser(user, attributeMap, user.getIssuer().getGenericStore(), executor, null, debugLog); - changed |= updateUserFromAttribute(user, attributeMap, auditor); - changed |= postUpdateUser(user, attributeMap, user.getIssuer().getGenericStore(), executor, null, debugLog, - lastLoginHost); - - return changed; - } - - public boolean updateUserFromAttribute(UserEntity user, Map<String, List<Object>> attributeMap, Auditor auditor) - throws UserUpdateException { - return updateUserFromAttribute(user, attributeMap, false, auditor); - } - - public boolean updateUserFromAttribute(UserEntity user, Map<String, List<Object>> attributeMap, - boolean withoutUidNumber, Auditor auditor) throws UserUpdateException { - - boolean changed = false; - - UserServiceHook completeOverrideHook = null; - Set<UserServiceHook> activeHooks = new HashSet<UserServiceHook>(); - - for (UserServiceHook hook : hookManager.getUserHooks()) { - if (hook.isResponsible(user, attributeMap)) { - - hook.preUpdateUserFromAttribute(user, attributeMap, auditor); - activeHooks.add(hook); - - if (hook.isCompleteOverride()) { - completeOverrideHook = hook; - } - } - } - - if (completeOverrideHook == null) { - IDTokenClaimsSet claims = oidcTokenHelper.claimsFromMap(attributeMap); - if (claims == null) { - logger.info("No claims set for user {}", user.getId()); - } - - UserInfo userInfo = oidcTokenHelper.userInfoFromMap(attributeMap); - if (userInfo == null) { - throw new UserUpdateException("User info is missing in session"); - } - - changed |= compareAndChangeProperty(user, "email", userInfo.getEmailAddress(), auditor); - changed |= compareAndChangeProperty(user, "eppn", userInfo.getStringClaim("eduPersonPrincipalName"), - auditor); - changed |= compareAndChangeProperty(user, "givenName", userInfo.getGivenName(), auditor); - changed |= compareAndChangeProperty(user, "surName", userInfo.getFamilyName(), auditor); - - if ((!withoutUidNumber) && (user.getUidNumber() == null)) { - user.setUidNumber(serialDao.nextUidNumber().intValue()); - logger.info("Setting UID Number {} for user {}", user.getUidNumber(), user.getEppn()); - auditor.logAction(user.getEppn(), "SET FIELD", "uidNumber", "" + user.getUidNumber(), - AuditStatus.SUCCESS); - changed = true; - } - } else { - logger.info("Overriding standard User Update Mechanism! Activator: {}", - completeOverrideHook.getClass().getName()); - } - - for (UserServiceHook hook : activeHooks) { - hook.postUpdateUserFromAttribute(user, attributeMap, auditor); - } - - return changed; - } - - private boolean compareAndChangeProperty(UserEntity user, String property, String value, Auditor auditor) { - String s = null; - String action = null; - - try { - Object actualValue = PropertyUtils.getProperty(user, property); - - if (actualValue != null && actualValue.equals(value)) { - // Value didn't change, do nothing - return false; - } - - if (actualValue == null && value == null) { - // Value stayed null - return false; - } - - if (actualValue == null) { - s = "null"; - action = "SET FIELD"; - } else { - s = actualValue.toString(); - action = "UPDATE FIELD"; - } - - s = s + " -> " + value; - if (s.length() > 1017) - s = s.substring(0, 1017) + "..."; - - PropertyUtils.setProperty(user, property, value); - - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.SUCCESS); - } catch (IllegalAccessException e) { - logger.warn("This probably shouldn't happen: ", e); - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); - } catch (InvocationTargetException e) { - logger.warn("This probably shouldn't happen: ", e); - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); - } catch (NoSuchMethodException e) { - logger.warn("This probably shouldn't happen: ", e); - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); - } - - return true; - } - - protected void changeUserStatus(UserEntity user, UserStatus toStatus, Auditor auditor) { - UserStatus fromStatus = user.getUserStatus(); - user.setUserStatus(toStatus); - user.setLastStatusChange(new Date()); - - logger.debug("{}: change user status from {} to {}", user.getEppn(), fromStatus, toStatus); - auditor.logAction(user.getEppn(), "CHANGE STATUS", fromStatus + " -> " + toStatus, - "Change status " + fromStatus + " -> " + toStatus, AuditStatus.SUCCESS); - } - - protected void changeRegistryStatus(RegistryEntity registry, RegistryStatus toStatus, String statusMessage, - Auditor parentAuditor) { - RegistryStatus fromStatus = registry.getRegistryStatus(); - registry.setRegistryStatus(toStatus); - registry.setStatusMessage(statusMessage); - registry.setLastStatusChange(new Date()); - - logger.debug("{} {} {}: change registry status from {} to {}", new Object[] { registry.getUser().getEppn(), - registry.getService().getShortName(), registry.getId(), fromStatus, toStatus }); - RegistryAuditor registryAuditor = new RegistryAuditor(auditDao, auditDetailDao, appConfig); - registryAuditor.setParent(parentAuditor); - registryAuditor.startAuditTrail(parentAuditor.getActualExecutor()); - registryAuditor.setName(getClass().getName() + "-UserUpdate-Registry-Audit"); - registryAuditor.setDetail("Update registry " + registry.getId() + " for user " + registry.getUser().getEppn()); - registryAuditor.setRegistry(registry); - registryAuditor.logAction(registry.getUser().getEppn(), "CHANGE STATUS", "registry-" + registry.getId(), - "Change status " + fromStatus + " -> " + toStatus, AuditStatus.SUCCESS); - registryAuditor.finishAuditTrail(); - } - - private Date getNextScheduledUpdate() { - Long futureMillis = 30L * 24L * 60L * 60L * 1000L; - if (appConfig.getConfigOptions().containsKey("update_schedule_future")) { - futureMillis = Long.decode(appConfig.getConfigValue("update_schedule_future")); - } - Integer futureMillisRandom = 6 * 60 * 60 * 1000; - if (appConfig.getConfigOptions().containsKey("update_schedule_future_random")) { - futureMillisRandom = Integer.decode(appConfig.getConfigValue("update_schedule_future_random")); - } - Random r = new Random(); - return new Date(System.currentTimeMillis() + futureMillis + r.nextInt(futureMillisRandom)); - } - - protected void updateFail(OidcUserEntity user) { - user.setLastFailedUpdate(new Date()); - user.setScheduledUpdate(getNextScheduledUpdate()); - } -} diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlSpPostServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlSpPostServiceImpl.java index 422d22b70c0636d7435203f289d389cef7a02365..17d908eeb3b79a53af4f43b5e2e870a73b695a05 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlSpPostServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlSpPostServiceImpl.java @@ -29,7 +29,7 @@ import edu.kit.scc.webreg.entity.UserLoginInfoEntity; import edu.kit.scc.webreg.entity.UserLoginInfoStatus; import edu.kit.scc.webreg.entity.UserLoginMethod; import edu.kit.scc.webreg.exc.UserUpdateException; -import edu.kit.scc.webreg.service.impl.UserUpdater; +import edu.kit.scc.webreg.service.impl.SamlUserUpdater; import edu.kit.scc.webreg.service.saml.exc.OidcAuthenticationException; import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException; import edu.kit.scc.webreg.session.SessionManager; @@ -48,7 +48,7 @@ public class SamlSpPostServiceImpl implements SamlSpPostService { private UserLoginInfoDao userLoginInfoDao; @Inject - private UserUpdater userUpdater; + private SamlUserUpdater userUpdater; @Inject private SamlHelper samlHelper; diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/AbstractHomeOrgGroupUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/AbstractHomeOrgGroupUpdater.java new file mode 100644 index 0000000000000000000000000000000000000000..d25e917604eb8d8d591e7feb374f7eac95fa46f0 --- /dev/null +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/AbstractHomeOrgGroupUpdater.java @@ -0,0 +1,19 @@ +package edu.kit.scc.webreg.service.group; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import edu.kit.scc.webreg.audit.Auditor; +import edu.kit.scc.webreg.entity.GroupEntity; +import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.exc.UserUpdateException; + +public abstract class AbstractHomeOrgGroupUpdater<T extends UserEntity> implements HomeOrgGroupUpdater<T>, Serializable { + + private static final long serialVersionUID = 1L; + + public abstract Set<GroupEntity> updateGroupsForUser(T user, Map<String, List<Object>> attributeMap, Auditor auditor) + throws UserUpdateException; +} diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/HomeOrgGroupUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/HomeOrgGroupUpdater.java index 59c628efa2e7c2eaf7f3607e5df5509c05ee5b9b..c9b1fedfe14675909d41823b58a3ba940c8bf249 100644 --- a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/HomeOrgGroupUpdater.java +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/HomeOrgGroupUpdater.java @@ -1,385 +1,16 @@ -/******************************************************************************* - * Copyright (c) 2014 Michael Simon. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - * - * Contributors: - * Michael Simon - initial - ******************************************************************************/ package edu.kit.scc.webreg.service.group; -import java.io.Serializable; -import java.text.Normalizer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -import org.slf4j.Logger; - import edu.kit.scc.webreg.audit.Auditor; -import edu.kit.scc.webreg.dao.GroupDao; -import edu.kit.scc.webreg.dao.HomeOrgGroupDao; -import edu.kit.scc.webreg.dao.SerialDao; -import edu.kit.scc.webreg.dao.ServiceGroupFlagDao; -import edu.kit.scc.webreg.entity.EventType; import edu.kit.scc.webreg.entity.GroupEntity; -import edu.kit.scc.webreg.entity.HomeOrgGroupEntity; -import edu.kit.scc.webreg.entity.SamlUserEntity; -import edu.kit.scc.webreg.entity.ServiceBasedGroupEntity; -import edu.kit.scc.webreg.entity.ServiceGroupFlagEntity; -import edu.kit.scc.webreg.entity.ServiceGroupStatus; -import edu.kit.scc.webreg.entity.UserGroupEntity; -import edu.kit.scc.webreg.entity.audit.AuditStatus; -import edu.kit.scc.webreg.event.EventSubmitter; -import edu.kit.scc.webreg.event.MultipleGroupEvent; -import edu.kit.scc.webreg.event.exc.EventSubmitException; +import edu.kit.scc.webreg.entity.UserEntity; import edu.kit.scc.webreg.exc.UserUpdateException; -import edu.kit.scc.webreg.hook.GroupServiceHook; -import edu.kit.scc.webreg.hook.HookManager; -import edu.kit.scc.webreg.service.identity.HomeIdResolver; -import edu.kit.scc.webreg.service.impl.AttributeMapHelper; - -@ApplicationScoped -public class HomeOrgGroupUpdater implements Serializable { - - private static final long serialVersionUID = 1L; - - @Inject - private Logger logger; - - @Inject - private HookManager hookManager; - - @Inject - private HomeOrgGroupDao dao; - - @Inject - private GroupDao groupDao; - - @Inject - private ServiceGroupFlagDao groupFlagDao; - - @Inject - private AttributeMapHelper attrHelper; - - @Inject - private SerialDao serialDao; - - @Inject - private EventSubmitter eventSubmitter; - - @Inject - private HomeIdResolver homeIdResolver; - - public Set<GroupEntity> updateGroupsForUser(SamlUserEntity user, Map<String, List<Object>> attributeMap, Auditor auditor) - throws UserUpdateException { - - HashSet<GroupEntity> changedGroups = new HashSet<GroupEntity>(); - - changedGroups.addAll(updatePrimary(user, attributeMap, auditor)); - changedGroups.addAll(updateSecondary(user, attributeMap, auditor)); - - // Also add parent groups, to reflect changes - HashSet<GroupEntity> allChangedGroups = new HashSet<GroupEntity>(changedGroups.size()); - for (GroupEntity group : changedGroups) { - allChangedGroups.add(group); - if (group.getParents() != null) { - for (GroupEntity parent : group.getParents()) { - logger.debug("Adding parent group to changed groups: {}", parent.getName()); - allChangedGroups.add(parent); - } - } - } - - for (GroupEntity group : allChangedGroups) { - if (group instanceof ServiceBasedGroupEntity) { - List<ServiceGroupFlagEntity> groupFlagList = groupFlagDao.findByGroup((ServiceBasedGroupEntity) group); - for (ServiceGroupFlagEntity groupFlag : groupFlagList) { - groupFlag.setStatus(ServiceGroupStatus.DIRTY); - groupFlagDao.persist(groupFlag); - } - } - } - - // do not send group event, if there are not changed groups - if (allChangedGroups.size() > 0) { - MultipleGroupEvent mge = new MultipleGroupEvent(allChangedGroups); - try { - eventSubmitter.submit(mge, EventType.GROUP_UPDATE, auditor.getActualExecutor()); - } catch (EventSubmitException e) { - logger.warn("Exeption", e); - } - } - - return allChangedGroups; - } - - protected Set<GroupEntity> updatePrimary(SamlUserEntity user, Map<String, List<Object>> attributeMap, Auditor auditor) - throws UserUpdateException { - Set<GroupEntity> changedGroups = new HashSet<GroupEntity>(); - - GroupServiceHook completeOverrideHook = null; - Set<GroupServiceHook> activeHooks = new HashSet<GroupServiceHook>(); - - GroupEntity group = null; - - for (GroupServiceHook hook : hookManager.getGroupHooks()) { - if (hook.isPrimaryResponsible(user, attributeMap)) { - group = hook.preUpdateUserPrimaryGroupFromAttribute(dao, groupDao, group, user, attributeMap, auditor, changedGroups); - activeHooks.add(hook); - - if (hook.isPrimaryCompleteOverride()) { - completeOverrideHook = hook; - } - } - } - - if (completeOverrideHook == null) { - - String homeId = homeIdResolver.resolveHomeId(user, attributeMap); - - if (homeId == null) { - logger.warn("No Home ID is set for User {}, resetting primary group", user.getEppn()); - } - else { - //Filter all non character from homeid - homeId = homeId.toLowerCase(); - homeId = homeId.replaceAll("[^a-z0-9]", ""); - - String groupName = homeIdResolver.resolvePrimaryGroup(homeId, user, attributeMap); - - if (groupName == null) { - groupName = attrHelper.getSingleStringFirst(attributeMap, "http://bwidm.de/bwidmCC"); - } - - if (groupName == null) { - groupName = homeId; - } - else { - //Filter all non character from groupName - groupName = Normalizer.normalize(groupName, Normalizer.Form.NFD); - groupName = groupName.toLowerCase(); - groupName = groupName.replaceAll("[^a-z0-9\\-_]", ""); - } - - logger.info("Setting standard HomeID group {} for user {}", homeId, user.getEppn()); - group = dao.findByNameAndPrefix(groupName, homeId); - - if (group == null) { - HomeOrgGroupEntity homeGroup = dao.createNew(); - homeGroup.setUsers(new HashSet<UserGroupEntity>()); - homeGroup.setName(groupName); - auditor.logAction(homeGroup.getName(), "SET FIELD", "name", homeGroup.getName(), AuditStatus.SUCCESS); - homeGroup.setPrefix(homeId); - auditor.logAction(homeGroup.getName(), "SET FIELD", "prefix", homeGroup.getPrefix(), AuditStatus.SUCCESS); - homeGroup.setGidNumber(serialDao.next("gid-number-serial").intValue()); - auditor.logAction(homeGroup.getName(), "SET FIELD", "gidNumber", "" + homeGroup.getGidNumber(), AuditStatus.SUCCESS); - homeGroup.setIdp(user.getIdp()); - auditor.logAction(homeGroup.getName(), "SET FIELD", "idpEntityId", "" + user.getIdp().getEntityId(), AuditStatus.SUCCESS); - group = groupDao.persistWithServiceFlags(homeGroup); - auditor.logAction(group.getName(), "CREATE GROUP", null, "Group created", AuditStatus.SUCCESS); - - changedGroups.add(group); - } - } - } - else { - logger.info("Overriding standard Primary Group Update Mechanism! Activator: {}", completeOverrideHook.getClass().getName()); - } - - if (group == null) { - logger.warn("No Primary Group for user {}", user.getEppn()); - } - - for (GroupServiceHook hook : activeHooks) { - group = hook.postUpdateUserPrimaryGroupFromAttribute(dao, groupDao, group, user, attributeMap, auditor, changedGroups); - } - - if (user.getPrimaryGroup() != null && (! user.getPrimaryGroup().equals(group))) { - if (group == null) { - auditor.logAction(user.getEppn(), "UPDATE FIELD", "primaryGroup", - user.getPrimaryGroup().getName() + " (" + user.getPrimaryGroup().getGidNumber() + ") -> " + - "null", AuditStatus.SUCCESS); - } - else { - auditor.logAction(user.getEppn(), "UPDATE FIELD", "primaryGroup", - user.getPrimaryGroup().getName() + " (" + user.getPrimaryGroup().getGidNumber() + ") -> " + - group.getName() + " (" + group.getGidNumber() + ")", AuditStatus.SUCCESS); - changedGroups.add(group); - } - } - else if (user.getPrimaryGroup() == null && group != null) { - auditor.logAction(user.getEppn(), "UPDATE FIELD", "primaryGroup", - "null -> " + - group.getName() + " (" + group.getGidNumber() + ")", AuditStatus.SUCCESS); - changedGroups.add(group); - } - - user.setPrimaryGroup(group); - - return changedGroups; - } - - protected Set<GroupEntity> updateSecondary(SamlUserEntity user, Map<String, List<Object>> attributeMap, Auditor auditor) - throws UserUpdateException { - Set<GroupEntity> changedGroups = new HashSet<GroupEntity>(); - - GroupServiceHook completeOverrideHook = null; - Set<GroupServiceHook> activeHooks = new HashSet<GroupServiceHook>(); - - for (GroupServiceHook hook : hookManager.getGroupHooks()) { - if (hook.isSecondaryResponsible(user, attributeMap)) { - hook.preUpdateUserSecondaryGroupFromAttribute(dao, groupDao, user, attributeMap, auditor, changedGroups); - activeHooks.add(hook); - - if (hook.isSecondaryCompleteOverride()) { - completeOverrideHook = hook; - } - } - } - - if (completeOverrideHook == null) { - - String homeId = homeIdResolver.resolveHomeId(user, attributeMap); - - List<String> groupList = new ArrayList<String>(); - - if (homeId == null) { - logger.warn("No Home ID is set for User {}, resetting secondary groups", user.getEppn()); - } - else if (attributeMap.get("http://bwidm.de/bwidmMemberOf") == null) { - logger.info("No http://bwidm.de/bwidmMemberOf is set. Resetting secondary groups"); - } - else { - List<String> groupsFromAttr = attrHelper.attributeListToStringList(attributeMap, "http://bwidm.de/bwidmMemberOf"); - - //Check if a group name contains a ';', and divide this group - for (String group : groupsFromAttr) { - if (group.contains(";")) { - String[] splitGroups = group.split(";"); - for (String g : splitGroups) { - groupList.add(filterGroup(g)); - } - } - else { - groupList.add(filterGroup(group)); - } - } - } - - if (user.getGroups() == null) - user.setGroups(new HashSet<UserGroupEntity>()); - - Set<GroupEntity> groupsFromAssertion = new HashSet<GroupEntity>(); - - logger.debug("Looking up groups from database"); - Map<String, HomeOrgGroupEntity> dbGroupMap = new HashMap<String, HomeOrgGroupEntity>(); - logger.debug("Indexing groups from database"); - for (HomeOrgGroupEntity dbGroup : dao.findByNameListAndPrefix(groupList, homeId)) { - dbGroupMap.put(dbGroup.getName(), dbGroup); - } - - for (String group : groupList) { - if (group != null && (!group.equals(""))) { - - logger.debug("Analyzing group {}", group); - HomeOrgGroupEntity groupEntity = dbGroupMap.get(group); - - try { - if (groupEntity == null) { - int gidNumber = serialDao.next("gid-number-serial").intValue(); - logger.info("Creating group {} with gidNumber {}", group, gidNumber); - groupEntity = dao.createNew(); - - groupEntity.setUsers(new HashSet<UserGroupEntity>()); - groupEntity.setParents(new HashSet<GroupEntity>()); - groupEntity.setName(group); - auditor.logAction(groupEntity.getName(), "SET FIELD", "name", groupEntity.getName(), AuditStatus.SUCCESS); - groupEntity.setPrefix(homeId); - auditor.logAction(groupEntity.getName(), "SET FIELD", "prefix", groupEntity.getPrefix(), AuditStatus.SUCCESS); - groupEntity.setGidNumber(gidNumber); - auditor.logAction(groupEntity.getName(), "SET FIELD", "gidNumber", "" + groupEntity.getGidNumber(), AuditStatus.SUCCESS); - groupEntity.setIdp(user.getIdp()); - auditor.logAction(groupEntity.getName(), "SET FIELD", "idpEntityId", "" + user.getIdp().getEntityId(), AuditStatus.SUCCESS); - groupEntity = (HomeOrgGroupEntity) groupDao.persistWithServiceFlags(groupEntity); - auditor.logAction(groupEntity.getName(), "CREATE GROUP", null, "Group created", AuditStatus.SUCCESS); - - changedGroups.add(groupEntity); - } - - if (groupEntity != null) { - groupsFromAssertion.add(groupEntity); - - if (! groupDao.isUserInGroup(user, groupEntity)) { - logger.debug("Adding user {} to group {}", user.getEppn(), groupEntity.getName()); - groupDao.addUserToGroup(user, groupEntity); - changedGroups.remove(groupEntity); - //groupEntity = dao.persist(groupEntity); - auditor.logAction(user.getEppn(), "ADD TO GROUP", groupEntity.getName(), null, AuditStatus.SUCCESS); - - changedGroups.add(groupEntity); - } - } - - } catch (NumberFormatException e) { - logger.warn("GidNumber has a bad number format: {}", e.getMessage()); - } - } - } - - Set<GroupEntity> groupsToRemove = new HashSet<GroupEntity>(groupDao.findByUser(user)); - groupsToRemove.removeAll(groupsFromAssertion); - for (GroupEntity removeGroup : groupsToRemove) { - if (removeGroup instanceof HomeOrgGroupEntity) { - if (! removeGroup.equals(user.getPrimaryGroup())) { - logger.debug("Removing user {} from group {}", user.getEppn(), removeGroup.getName()); - groupDao.removeUserGromGroup(user, removeGroup); - - auditor.logAction(user.getEppn(), "REMOVE FROM GROUP", removeGroup.getName(), null, AuditStatus.SUCCESS); - - changedGroups.add(removeGroup); - } - } - else { - logger.debug("Group {} of type {}. Doing nothing.", removeGroup.getName(), removeGroup.getClass().getSimpleName()); - } - } +public interface HomeOrgGroupUpdater<T extends UserEntity> { - /* - * Add Primary group to secondary as well - */ - if (user.getPrimaryGroup() != null && (! groupDao.isUserInGroup(user, user.getPrimaryGroup()))) { - logger.debug("Adding user {} to his primary group {} as secondary", user.getEppn(), user.getPrimaryGroup().getName()); - groupDao.addUserToGroup(user, user.getPrimaryGroup()); - changedGroups.add(user.getPrimaryGroup()); - } - } - else { - logger.info("Overriding standard Secondary Group Update Mechanism! Activator: {}", completeOverrideHook.getClass().getName()); - } - - for (GroupServiceHook hook : activeHooks) { - hook.postUpdateUserSecondaryGroupFromAttribute(dao, groupDao, user, attributeMap, auditor, changedGroups); - } - - return changedGroups; - } - - private String filterGroup(String groupName) { - //Filter all non character from groupName - groupName = Normalizer.normalize(groupName, Normalizer.Form.NFD); - groupName = groupName.toLowerCase(); - groupName = groupName.replaceAll("[^a-z0-9\\-_]", ""); - - return groupName; - } + public Set<GroupEntity> updateGroupsForUser(T user, Map<String, List<Object>> attributeMap, Auditor auditor) + throws UserUpdateException; } diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthGroupUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OAuthGroupUpdater.java similarity index 93% rename from bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthGroupUpdater.java rename to regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OAuthGroupUpdater.java index c4a2c5cda4c7afbc8f90c6915732937f224f2975..1607d2ecd9b649476d18f6ad23a116c489bcd1d0 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oauth/client/OAuthGroupUpdater.java +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OAuthGroupUpdater.java @@ -1,4 +1,4 @@ -package edu.kit.scc.webreg.service.oauth.client; +package edu.kit.scc.webreg.service.group; import static edu.kit.scc.webreg.dao.ops.RqlExpressions.and; import static edu.kit.scc.webreg.dao.ops.RqlExpressions.equal; @@ -26,9 +26,6 @@ import edu.kit.scc.webreg.entity.audit.AuditStatus; import edu.kit.scc.webreg.entity.oauth.OAuthGroupEntity; import edu.kit.scc.webreg.entity.oauth.OAuthGroupEntity_; import edu.kit.scc.webreg.entity.oauth.OAuthUserEntity; -import edu.kit.scc.webreg.entity.oidc.OidcGroupEntity; -import edu.kit.scc.webreg.entity.oidc.OidcGroupEntity_; -import edu.kit.scc.webreg.entity.oidc.OidcUserEntity; import edu.kit.scc.webreg.event.EventSubmitter; import edu.kit.scc.webreg.event.MultipleGroupEvent; import edu.kit.scc.webreg.event.exc.EventSubmitException; @@ -37,7 +34,9 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @ApplicationScoped -public class OAuthGroupUpdater { +public class OAuthGroupUpdater extends AbstractHomeOrgGroupUpdater<OAuthUserEntity> { + + private static final long serialVersionUID = 1L; @Inject private Logger logger; diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcGroupUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OidcGroupUpdater.java similarity index 94% rename from bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcGroupUpdater.java rename to regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OidcGroupUpdater.java index 59801039e50e4073d8f437e86bc24f6ad13a7d83..f259fc869e6e546c8b80507b2b8dfead7c34c4c9 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcGroupUpdater.java +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OidcGroupUpdater.java @@ -1,4 +1,4 @@ -package edu.kit.scc.webreg.service.oidc.client; +package edu.kit.scc.webreg.service.group; import static edu.kit.scc.webreg.dao.ops.RqlExpressions.and; import static edu.kit.scc.webreg.dao.ops.RqlExpressions.equal; @@ -9,9 +9,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - import org.slf4j.Logger; import edu.kit.scc.webreg.audit.Auditor; @@ -32,11 +29,14 @@ import edu.kit.scc.webreg.entity.oidc.OidcUserEntity; import edu.kit.scc.webreg.event.EventSubmitter; import edu.kit.scc.webreg.event.MultipleGroupEvent; import edu.kit.scc.webreg.event.exc.EventSubmitException; -import edu.kit.scc.webreg.service.SerialService; import edu.kit.scc.webreg.service.identity.HomeIdResolver; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; @ApplicationScoped -public class OidcGroupUpdater { +public class OidcGroupUpdater extends AbstractHomeOrgGroupUpdater<OidcUserEntity> { + + private static final long serialVersionUID = 1L; @Inject private Logger logger; diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/SamlGroupUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/SamlGroupUpdater.java new file mode 100644 index 0000000000000000000000000000000000000000..45aa2b976736e211fc5ef7e6b48848c39bc355de --- /dev/null +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/SamlGroupUpdater.java @@ -0,0 +1,383 @@ +/******************************************************************************* + * Copyright (c) 2014 Michael Simon. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Michael Simon - initial + ******************************************************************************/ +package edu.kit.scc.webreg.service.group; + +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; + +import edu.kit.scc.webreg.audit.Auditor; +import edu.kit.scc.webreg.dao.GroupDao; +import edu.kit.scc.webreg.dao.HomeOrgGroupDao; +import edu.kit.scc.webreg.dao.SerialDao; +import edu.kit.scc.webreg.dao.ServiceGroupFlagDao; +import edu.kit.scc.webreg.entity.EventType; +import edu.kit.scc.webreg.entity.GroupEntity; +import edu.kit.scc.webreg.entity.HomeOrgGroupEntity; +import edu.kit.scc.webreg.entity.SamlUserEntity; +import edu.kit.scc.webreg.entity.ServiceBasedGroupEntity; +import edu.kit.scc.webreg.entity.ServiceGroupFlagEntity; +import edu.kit.scc.webreg.entity.ServiceGroupStatus; +import edu.kit.scc.webreg.entity.UserGroupEntity; +import edu.kit.scc.webreg.entity.audit.AuditStatus; +import edu.kit.scc.webreg.event.EventSubmitter; +import edu.kit.scc.webreg.event.MultipleGroupEvent; +import edu.kit.scc.webreg.event.exc.EventSubmitException; +import edu.kit.scc.webreg.exc.UserUpdateException; +import edu.kit.scc.webreg.hook.GroupServiceHook; +import edu.kit.scc.webreg.hook.HookManager; +import edu.kit.scc.webreg.service.identity.HomeIdResolver; +import edu.kit.scc.webreg.service.impl.AttributeMapHelper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class SamlGroupUpdater extends AbstractHomeOrgGroupUpdater<SamlUserEntity> { + + private static final long serialVersionUID = 1L; + + @Inject + private Logger logger; + + @Inject + private HookManager hookManager; + + @Inject + private HomeOrgGroupDao dao; + + @Inject + private GroupDao groupDao; + + @Inject + private ServiceGroupFlagDao groupFlagDao; + + @Inject + private AttributeMapHelper attrHelper; + + @Inject + private SerialDao serialDao; + + @Inject + private EventSubmitter eventSubmitter; + + @Inject + private HomeIdResolver homeIdResolver; + + public Set<GroupEntity> updateGroupsForUser(SamlUserEntity user, Map<String, List<Object>> attributeMap, Auditor auditor) + throws UserUpdateException { + + HashSet<GroupEntity> changedGroups = new HashSet<GroupEntity>(); + + changedGroups.addAll(updatePrimary(user, attributeMap, auditor)); + changedGroups.addAll(updateSecondary(user, attributeMap, auditor)); + + // Also add parent groups, to reflect changes + HashSet<GroupEntity> allChangedGroups = new HashSet<GroupEntity>(changedGroups.size()); + for (GroupEntity group : changedGroups) { + allChangedGroups.add(group); + if (group.getParents() != null) { + for (GroupEntity parent : group.getParents()) { + logger.debug("Adding parent group to changed groups: {}", parent.getName()); + allChangedGroups.add(parent); + } + } + } + + for (GroupEntity group : allChangedGroups) { + if (group instanceof ServiceBasedGroupEntity) { + List<ServiceGroupFlagEntity> groupFlagList = groupFlagDao.findByGroup((ServiceBasedGroupEntity) group); + for (ServiceGroupFlagEntity groupFlag : groupFlagList) { + groupFlag.setStatus(ServiceGroupStatus.DIRTY); + groupFlagDao.persist(groupFlag); + } + } + } + + // do not send group event, if there are not changed groups + if (allChangedGroups.size() > 0) { + MultipleGroupEvent mge = new MultipleGroupEvent(allChangedGroups); + try { + eventSubmitter.submit(mge, EventType.GROUP_UPDATE, auditor.getActualExecutor()); + } catch (EventSubmitException e) { + logger.warn("Exeption", e); + } + } + + return allChangedGroups; + } + + protected Set<GroupEntity> updatePrimary(SamlUserEntity user, Map<String, List<Object>> attributeMap, Auditor auditor) + throws UserUpdateException { + Set<GroupEntity> changedGroups = new HashSet<GroupEntity>(); + + GroupServiceHook completeOverrideHook = null; + Set<GroupServiceHook> activeHooks = new HashSet<GroupServiceHook>(); + + GroupEntity group = null; + + for (GroupServiceHook hook : hookManager.getGroupHooks()) { + if (hook.isPrimaryResponsible(user, attributeMap)) { + group = hook.preUpdateUserPrimaryGroupFromAttribute(dao, groupDao, group, user, attributeMap, auditor, changedGroups); + activeHooks.add(hook); + + if (hook.isPrimaryCompleteOverride()) { + completeOverrideHook = hook; + } + } + } + + if (completeOverrideHook == null) { + + String homeId = homeIdResolver.resolveHomeId(user, attributeMap); + + if (homeId == null) { + logger.warn("No Home ID is set for User {}, resetting primary group", user.getEppn()); + } + else { + //Filter all non character from homeid + homeId = homeId.toLowerCase(); + homeId = homeId.replaceAll("[^a-z0-9]", ""); + + String groupName = homeIdResolver.resolvePrimaryGroup(homeId, user, attributeMap); + + if (groupName == null) { + groupName = attrHelper.getSingleStringFirst(attributeMap, "http://bwidm.de/bwidmCC"); + } + + if (groupName == null) { + groupName = homeId; + } + else { + //Filter all non character from groupName + groupName = Normalizer.normalize(groupName, Normalizer.Form.NFD); + groupName = groupName.toLowerCase(); + groupName = groupName.replaceAll("[^a-z0-9\\-_]", ""); + } + + logger.info("Setting standard HomeID group {} for user {}", homeId, user.getEppn()); + group = dao.findByNameAndPrefix(groupName, homeId); + + if (group == null) { + HomeOrgGroupEntity homeGroup = dao.createNew(); + homeGroup.setUsers(new HashSet<UserGroupEntity>()); + homeGroup.setName(groupName); + auditor.logAction(homeGroup.getName(), "SET FIELD", "name", homeGroup.getName(), AuditStatus.SUCCESS); + homeGroup.setPrefix(homeId); + auditor.logAction(homeGroup.getName(), "SET FIELD", "prefix", homeGroup.getPrefix(), AuditStatus.SUCCESS); + homeGroup.setGidNumber(serialDao.next("gid-number-serial").intValue()); + auditor.logAction(homeGroup.getName(), "SET FIELD", "gidNumber", "" + homeGroup.getGidNumber(), AuditStatus.SUCCESS); + homeGroup.setIdp(user.getIdp()); + auditor.logAction(homeGroup.getName(), "SET FIELD", "idpEntityId", "" + user.getIdp().getEntityId(), AuditStatus.SUCCESS); + group = groupDao.persistWithServiceFlags(homeGroup); + auditor.logAction(group.getName(), "CREATE GROUP", null, "Group created", AuditStatus.SUCCESS); + + changedGroups.add(group); + } + } + } + else { + logger.info("Overriding standard Primary Group Update Mechanism! Activator: {}", completeOverrideHook.getClass().getName()); + } + + if (group == null) { + logger.warn("No Primary Group for user {}", user.getEppn()); + } + + for (GroupServiceHook hook : activeHooks) { + group = hook.postUpdateUserPrimaryGroupFromAttribute(dao, groupDao, group, user, attributeMap, auditor, changedGroups); + } + + if (user.getPrimaryGroup() != null && (! user.getPrimaryGroup().equals(group))) { + if (group == null) { + auditor.logAction(user.getEppn(), "UPDATE FIELD", "primaryGroup", + user.getPrimaryGroup().getName() + " (" + user.getPrimaryGroup().getGidNumber() + ") -> " + + "null", AuditStatus.SUCCESS); + } + else { + auditor.logAction(user.getEppn(), "UPDATE FIELD", "primaryGroup", + user.getPrimaryGroup().getName() + " (" + user.getPrimaryGroup().getGidNumber() + ") -> " + + group.getName() + " (" + group.getGidNumber() + ")", AuditStatus.SUCCESS); + changedGroups.add(group); + } + } + else if (user.getPrimaryGroup() == null && group != null) { + auditor.logAction(user.getEppn(), "UPDATE FIELD", "primaryGroup", + "null -> " + + group.getName() + " (" + group.getGidNumber() + ")", AuditStatus.SUCCESS); + changedGroups.add(group); + } + + user.setPrimaryGroup(group); + + return changedGroups; + } + + protected Set<GroupEntity> updateSecondary(SamlUserEntity user, Map<String, List<Object>> attributeMap, Auditor auditor) + throws UserUpdateException { + Set<GroupEntity> changedGroups = new HashSet<GroupEntity>(); + + GroupServiceHook completeOverrideHook = null; + Set<GroupServiceHook> activeHooks = new HashSet<GroupServiceHook>(); + + for (GroupServiceHook hook : hookManager.getGroupHooks()) { + if (hook.isSecondaryResponsible(user, attributeMap)) { + hook.preUpdateUserSecondaryGroupFromAttribute(dao, groupDao, user, attributeMap, auditor, changedGroups); + activeHooks.add(hook); + + if (hook.isSecondaryCompleteOverride()) { + completeOverrideHook = hook; + } + } + } + + if (completeOverrideHook == null) { + + String homeId = homeIdResolver.resolveHomeId(user, attributeMap); + + List<String> groupList = new ArrayList<String>(); + + if (homeId == null) { + logger.warn("No Home ID is set for User {}, resetting secondary groups", user.getEppn()); + } + else if (attributeMap.get("http://bwidm.de/bwidmMemberOf") == null) { + logger.info("No http://bwidm.de/bwidmMemberOf is set. Resetting secondary groups"); + } + else { + List<String> groupsFromAttr = attrHelper.attributeListToStringList(attributeMap, "http://bwidm.de/bwidmMemberOf"); + + //Check if a group name contains a ';', and divide this group + for (String group : groupsFromAttr) { + if (group.contains(";")) { + String[] splitGroups = group.split(";"); + for (String g : splitGroups) { + groupList.add(filterGroup(g)); + } + } + else { + groupList.add(filterGroup(group)); + } + } + } + + if (user.getGroups() == null) + user.setGroups(new HashSet<UserGroupEntity>()); + + Set<GroupEntity> groupsFromAssertion = new HashSet<GroupEntity>(); + + logger.debug("Looking up groups from database"); + Map<String, HomeOrgGroupEntity> dbGroupMap = new HashMap<String, HomeOrgGroupEntity>(); + logger.debug("Indexing groups from database"); + for (HomeOrgGroupEntity dbGroup : dao.findByNameListAndPrefix(groupList, homeId)) { + dbGroupMap.put(dbGroup.getName(), dbGroup); + } + + for (String group : groupList) { + if (group != null && (!group.equals(""))) { + + logger.debug("Analyzing group {}", group); + HomeOrgGroupEntity groupEntity = dbGroupMap.get(group); + + try { + if (groupEntity == null) { + int gidNumber = serialDao.next("gid-number-serial").intValue(); + logger.info("Creating group {} with gidNumber {}", group, gidNumber); + groupEntity = dao.createNew(); + + groupEntity.setUsers(new HashSet<UserGroupEntity>()); + groupEntity.setParents(new HashSet<GroupEntity>()); + groupEntity.setName(group); + auditor.logAction(groupEntity.getName(), "SET FIELD", "name", groupEntity.getName(), AuditStatus.SUCCESS); + groupEntity.setPrefix(homeId); + auditor.logAction(groupEntity.getName(), "SET FIELD", "prefix", groupEntity.getPrefix(), AuditStatus.SUCCESS); + groupEntity.setGidNumber(gidNumber); + auditor.logAction(groupEntity.getName(), "SET FIELD", "gidNumber", "" + groupEntity.getGidNumber(), AuditStatus.SUCCESS); + groupEntity.setIdp(user.getIdp()); + auditor.logAction(groupEntity.getName(), "SET FIELD", "idpEntityId", "" + user.getIdp().getEntityId(), AuditStatus.SUCCESS); + groupEntity = (HomeOrgGroupEntity) groupDao.persistWithServiceFlags(groupEntity); + auditor.logAction(groupEntity.getName(), "CREATE GROUP", null, "Group created", AuditStatus.SUCCESS); + + changedGroups.add(groupEntity); + } + + if (groupEntity != null) { + groupsFromAssertion.add(groupEntity); + + if (! groupDao.isUserInGroup(user, groupEntity)) { + logger.debug("Adding user {} to group {}", user.getEppn(), groupEntity.getName()); + groupDao.addUserToGroup(user, groupEntity); + changedGroups.remove(groupEntity); + //groupEntity = dao.persist(groupEntity); + auditor.logAction(user.getEppn(), "ADD TO GROUP", groupEntity.getName(), null, AuditStatus.SUCCESS); + + changedGroups.add(groupEntity); + } + } + + } catch (NumberFormatException e) { + logger.warn("GidNumber has a bad number format: {}", e.getMessage()); + } + } + } + + Set<GroupEntity> groupsToRemove = new HashSet<GroupEntity>(groupDao.findByUser(user)); + groupsToRemove.removeAll(groupsFromAssertion); + + for (GroupEntity removeGroup : groupsToRemove) { + if (removeGroup instanceof HomeOrgGroupEntity) { + if (! removeGroup.equals(user.getPrimaryGroup())) { + logger.debug("Removing user {} from group {}", user.getEppn(), removeGroup.getName()); + groupDao.removeUserGromGroup(user, removeGroup); + + auditor.logAction(user.getEppn(), "REMOVE FROM GROUP", removeGroup.getName(), null, AuditStatus.SUCCESS); + + changedGroups.add(removeGroup); + } + } + else { + logger.debug("Group {} of type {}. Doing nothing.", removeGroup.getName(), removeGroup.getClass().getSimpleName()); + } + } + + /* + * Add Primary group to secondary as well + */ + if (user.getPrimaryGroup() != null && (! groupDao.isUserInGroup(user, user.getPrimaryGroup()))) { + logger.debug("Adding user {} to his primary group {} as secondary", user.getEppn(), user.getPrimaryGroup().getName()); + groupDao.addUserToGroup(user, user.getPrimaryGroup()); + changedGroups.add(user.getPrimaryGroup()); + } + } + else { + logger.info("Overriding standard Secondary Group Update Mechanism! Activator: {}", completeOverrideHook.getClass().getName()); + } + + for (GroupServiceHook hook : activeHooks) { + hook.postUpdateUserSecondaryGroupFromAttribute(dao, groupDao, user, attributeMap, auditor, changedGroups); + } + + return changedGroups; + } + + private String filterGroup(String groupName) { + //Filter all non character from groupName + groupName = Normalizer.normalize(groupName, Normalizer.Form.NFD); + groupName = groupName.toLowerCase(); + groupName = groupName.replaceAll("[^a-z0-9\\-_]", ""); + + return groupName; + } +} diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/AbstractUserUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/AbstractUserUpdater.java index e7f7cfd5b311a9110f8530e05c2aa1b8d814affd..6745f1f06f8ddacee503b668ba7cb8bf3274084e 100644 --- a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/AbstractUserUpdater.java +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/AbstractUserUpdater.java @@ -1,72 +1,332 @@ package edu.kit.scc.webreg.service.impl; +import static edu.kit.scc.webreg.dao.ops.RqlExpressions.equal; + import java.io.Serializable; import java.lang.reflect.InvocationTargetException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; - -import jakarta.inject.Inject; +import java.util.Map.Entry; +import java.util.Random; +import java.util.Set; import org.slf4j.Logger; +import org.slf4j.MDC; +import edu.kit.scc.webreg.as.AttributeSourceUpdater; +import edu.kit.scc.webreg.audit.Auditor; +import edu.kit.scc.webreg.audit.RegistryAuditor; +import edu.kit.scc.webreg.audit.UserUpdateAuditor; +import edu.kit.scc.webreg.bootstrap.ApplicationConfig; +import edu.kit.scc.webreg.dao.RegistryDao; +import edu.kit.scc.webreg.dao.ServiceDao; +import edu.kit.scc.webreg.dao.as.ASUserAttrDao; +import edu.kit.scc.webreg.dao.as.AttributeSourceDao; +import edu.kit.scc.webreg.dao.audit.AuditDetailDao; +import edu.kit.scc.webreg.dao.audit.AuditEntryDao; +import edu.kit.scc.webreg.entity.EventType; +import edu.kit.scc.webreg.entity.GroupEntity; +import edu.kit.scc.webreg.entity.RegistryEntity; +import edu.kit.scc.webreg.entity.RegistryStatus; import edu.kit.scc.webreg.entity.ServiceEntity; +import edu.kit.scc.webreg.entity.ServiceEntity_; import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.entity.UserStatus; +import edu.kit.scc.webreg.entity.as.ASUserAttrEntity_; +import edu.kit.scc.webreg.entity.as.AttributeSourceEntity; +import edu.kit.scc.webreg.entity.as.AttributeSourceEntity_; +import edu.kit.scc.webreg.entity.as.AttributeSourceServiceEntity; +import edu.kit.scc.webreg.entity.attribute.IncomingAttributeSetEntity; +import edu.kit.scc.webreg.entity.audit.AuditDetailEntity; +import edu.kit.scc.webreg.entity.audit.AuditStatus; +import edu.kit.scc.webreg.entity.audit.AuditUserUpdateEntity; +import edu.kit.scc.webreg.event.EventSubmitter; +import edu.kit.scc.webreg.event.UserEvent; +import edu.kit.scc.webreg.event.exc.EventSubmitException; +import edu.kit.scc.webreg.exc.RegisterException; import edu.kit.scc.webreg.exc.UserUpdateException; import edu.kit.scc.webreg.hook.IdentityScriptingHookWorkflow; import edu.kit.scc.webreg.hook.UserUpdateHook; import edu.kit.scc.webreg.hook.UserUpdateHookException; +import edu.kit.scc.webreg.service.attribute.IncomingAttributesHandler; +import edu.kit.scc.webreg.service.group.HomeOrgGroupUpdater; import edu.kit.scc.webreg.service.identity.IdentityScriptingEnv; +import edu.kit.scc.webreg.service.identity.IdentityUpdater; +import edu.kit.scc.webreg.service.reg.impl.Registrator; +import jakarta.inject.Inject; -public abstract class AbstractUserUpdater<T extends UserEntity> implements Serializable { +public abstract class AbstractUserUpdater<T extends UserEntity> implements UserUpdater<T>, Serializable { private static final long serialVersionUID = 1L; @Inject private Logger logger; + @Inject + private IdentityUpdater identityUpdater; + @Inject private IdentityScriptingEnv scriptingEnv; - - public abstract T updateUser(T user, Map<String, List<Object>> attributeMap, String executor, StringBuffer debugLog, String lastLoginHost) - throws UserUpdateException; - public abstract T updateUser(T user, Map<String, List<Object>> attributeMap, String executor, ServiceEntity service, StringBuffer debugLog, String lastLoginHost) - throws UserUpdateException; + @Inject + private RegistryDao registryDao; + + @Inject + private ServiceDao serviceDao; + + @Inject + private AttributeSourceDao attributeSourceDao; + + @Inject + private ASUserAttrDao asUserAttrDao; + + @Inject + private AttributeSourceUpdater attributeSourceUpdater; + + @Inject + private AuditEntryDao auditDao; + + @Inject + private AuditDetailDao auditDetailDao; + + @Inject + private AttributeMapHelper attrHelper; + + @Inject + private Registrator registrator; + + @Inject + private ApplicationConfig appConfig; + + @Inject + private EventSubmitter eventSubmitter; + + public abstract boolean updateUserFromAttribute(T user, Map<String, List<Object>> attributeMap, + boolean withoutUidNumber, Auditor auditor) throws UserUpdateException; + + public abstract Map<String, String> resolveHomeOrgGenericStore(T user); + + public abstract IncomingAttributesHandler<?> resolveIncomingAttributeHandler(T user); + + public boolean updateUserFromAttribute(T user, Map<String, List<Object>> attributeMap, Auditor auditor) + throws UserUpdateException { + return updateUserFromAttribute(user, attributeMap, false, auditor); + } + + @Override + public T expireUser(T user, String executor) { + logger.info("Expiring user {}. Trying one last update", user.getId()); + + UserUpdateAuditor auditor = new UserUpdateAuditor(auditDao, auditDetailDao, appConfig); + auditor.startAuditTrail(executor); + auditor.setName(getClass().getName() + "-UserExpire-Audit"); + auditor.setDetail("Expire user " + user.getId()); + + try { + user = updateUserFromHomeOrg(user, null, executor, null); + + // User update from home org did not fail. That means, we don't need to bother + // the user to login. Clear the expiry warning, things should be done + // automatically + user.setExpireWarningSent(null); + + return user; + + } catch (UserUpdateException e) { + // The Exception is expected, because the home org will not accept user updates + // in the back channel. The user already got an expire warning at this point. + user.getAttributeStore().clear(); + + // user empty attribute map in order to remove all existing values + IncomingAttributeSetEntity incomingAttributeSet = resolveIncomingAttributeHandler(user) + .createOrUpdateAttributes(user, new HashMap<>()); + resolveIncomingAttributeHandler(user).processIncomingAttributeSet(incomingAttributeSet); + + // sets user account on ON_HOLD, if it's in state ACTIVE + deactivateUser(user, auditor); + + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + user.getGenericStore().put("epired_on", df.format(new Date())); + + fireUserChangeEvent(user, auditor.getActualExecutor(), auditor); + + return user; + } finally { + auditor.setUser(user); + auditor.finishAuditTrail(); + auditor.commitAuditTrail(); + } + } + + public T updateUser(T user, Map<String, List<Object>> attributeMap, String executor, StringBuffer debugLog, + String lastLoginHost) throws UserUpdateException { + return updateUser(user, attributeMap, executor, null, debugLog, lastLoginHost); + } + + public T updateUser(T user, Map<String, List<Object>> attributeMap, String executor, ServiceEntity service, + StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { + MDC.put("userId", "" + user.getId()); + logger.debug("Updating user {} (class: {})", user.getId(), user.getClass().getSimpleName()); + + boolean changed = false; + + UserUpdateAuditor auditor = new UserUpdateAuditor(auditDao, auditDetailDao, appConfig); + auditor.startAuditTrail(executor); + auditor.setName(getClass().getName() + "-UserUpdate-Audit"); + auditor.setDetail("Update user " + user.getId()); + + changed |= preUpdateUser(user, attributeMap, resolveHomeOrgGenericStore(user), executor, service, debugLog); + + // List to store parent services, that are not registered. Need to be registered + // later, when attribute map is populated + List<ServiceEntity> delayedRegisterList = new ArrayList<ServiceEntity>(); + + /** + * put no_assertion_count in generic store if assertion is missing. Else reset + * no assertion count and put last valid assertion date in + */ + if (attributeMap == null) { + if (!user.getGenericStore().containsKey("no_assertion_count")) { + user.getGenericStore().put("no_assertion_count", "1"); + } else { + user.getGenericStore().put("no_assertion_count", + "" + (Long.parseLong(user.getGenericStore().get("no_assertion_count")) + 1L)); + } + + logger.info("No attribute for user {}, skipping updateFromAttribute", user.getEppn()); + + user.getAttributeStore().clear(); + + // user empty attribute map in order to remove all existing values + IncomingAttributeSetEntity incomingAttributeSet = resolveIncomingAttributeHandler(user) + .createOrUpdateAttributes(user, new HashMap<>()); + resolveIncomingAttributeHandler(user).processIncomingAttributeSet(incomingAttributeSet); + + // sets user account on ON_HOLD, if it's in state ACTIVE + deactivateUser(user, auditor); + + } else { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + user.getGenericStore().put("no_assertion_count", "0"); + user.getGenericStore().put("last_valid_assertion", df.format(new Date())); + + changed |= updateUserFromAttribute(user, attributeMap, auditor); + + // if a user is in state ON_HOLD, this reactivates the user to ACTIVE + // and sets all registries to LOST_ACCESS in order to be checked again + changed |= reactivateUser(user, delayedRegisterList, auditor); + + changed |= updateAttributeSources(user, service, executor, auditor); + + changed |= updateGroups(user, attributeMap, auditor); + + Map<String, String> attributeStore = user.getAttributeStore(); + attributeStore.clear(); + for (Entry<String, List<Object>> entry : attributeMap.entrySet()) { + attributeStore.put(entry.getKey(), attrHelper.attributeListToString(entry.getValue())); + } + + IncomingAttributeSetEntity incomingAttributeSet = resolveIncomingAttributeHandler(user) + .createOrUpdateAttributes(user, attributeMap); + resolveIncomingAttributeHandler(user).processIncomingAttributeSet(incomingAttributeSet); + + identityUpdater.updateIdentity(user); - protected boolean preUpdateUser(UserEntity user, Map<String, List<Object>> attributeMap, Map<String,String> homeOrgGenericStore, - String executor, ServiceEntity service, StringBuffer debugLog) + if (appConfig.getConfigValue("create_missing_eppn_scope") != null) { + if (user.getEppn() == null) { + String scope = appConfig.getConfigValue("create_missing_eppn_scope"); + user.setEppn(user.getIdentity().getGeneratedLocalUsername() + "@" + scope); + changed = true; + } + } + } + + for (ServiceEntity delayedService : delayedRegisterList) { + try { + registrator.registerUser(user, delayedService, "user-" + user.getId(), false); + } catch (RegisterException e) { + logger.warn("Parent registration didn't work out like it should", e); + } + } + + changed |= postUpdateUser(user, attributeMap, resolveHomeOrgGenericStore(user), executor, service, debugLog, + lastLoginHost); + + user.setLastUpdate(new Date()); + user.setLastFailedUpdate(null); + user.setExpireWarningSent(null); + user.setExpiredSent(null); + user.setScheduledUpdate(getNextScheduledUpdate()); + + if (changed) { + fireUserChangeEvent(user, auditor.getActualExecutor(), auditor); + } + + auditor.setUser(user); + auditor.finishAuditTrail(); + auditor.commitAuditTrail(); + + if (debugLog != null) { + AuditUserUpdateEntity audit = auditor.getAudit(); + debugLog.append("\n\nPrinting audit from user update process:\n\nName: ").append(audit.getName()) + .append("\nDetail: ").append(audit.getDetail()).append("\n"); + for (AuditDetailEntity detail : audit.getAuditDetails()) { + debugLog.append(detail.getEndTime()).append(" | ").append(detail.getSubject()).append(" | ") + .append(detail.getObject()).append(" | ").append(detail.getAction()).append(" | ") + .append(detail.getLog()).append(" | ").append(detail.getAuditStatus()).append("\n"); + } + + if (audit.getAuditDetails().size() == 0) { + debugLog.append("Nothing seems to have changed.\n"); + } + } + + return user; + } + + public abstract HomeOrgGroupUpdater<T> getGroupUpdater(); + + protected boolean preUpdateUser(T user, Map<String, List<Object>> attributeMap, + Map<String, String> homeOrgGenericStore, String executor, ServiceEntity service, StringBuffer debugLog) throws UserUpdateException { boolean returnValue = false; - + UserUpdateHook updateHook = resolveUpdateHook(homeOrgGenericStore); - + if (updateHook != null) { try { - returnValue |= updateHook.preUpdateUser(user, homeOrgGenericStore, attributeMap, executor, service, null); + returnValue |= updateHook.preUpdateUser(user, homeOrgGenericStore, attributeMap, executor, service, + null); } catch (UserUpdateHookException e) { logger.warn("An exception happened while calling UserUpdateHook!", e); } } - + return returnValue; } - protected boolean postUpdateUser(UserEntity user, Map<String, List<Object>> attributeMap, Map<String,String> homeOrgGenericStore, - String executor, ServiceEntity service, StringBuffer debugLog, String lastLoginHost) - throws UserUpdateException { + protected boolean postUpdateUser(T user, Map<String, List<Object>> attributeMap, + Map<String, String> homeOrgGenericStore, String executor, ServiceEntity service, StringBuffer debugLog, + String lastLoginHost) throws UserUpdateException { boolean returnValue = false; if (lastLoginHost != null) { user.setLastLoginHost(lastLoginHost); } - + UserUpdateHook updateHook = resolveUpdateHook(homeOrgGenericStore); if (updateHook != null) { try { - returnValue |= updateHook.postUpdateUser(user, homeOrgGenericStore, attributeMap, executor, service, null); + returnValue |= updateHook.postUpdateUser(user, homeOrgGenericStore, attributeMap, executor, service, + null); } catch (UserUpdateHookException e) { logger.warn("An exception happened while calling UserUpdateHook!", e); } @@ -74,7 +334,7 @@ public abstract class AbstractUserUpdater<T extends UserEntity> implements Seria return returnValue; } - private UserUpdateHook resolveUpdateHook(Map<String,String> homeOrgGenericStore) { + private UserUpdateHook resolveUpdateHook(Map<String, String> homeOrgGenericStore) { UserUpdateHook updateHook = null; if (homeOrgGenericStore.containsKey("user_update_hook")) { String hookClass = homeOrgGenericStore.get("user_update_hook"); @@ -89,7 +349,151 @@ public abstract class AbstractUserUpdater<T extends UserEntity> implements Seria logger.warn("Cannot instantiate updateHook class. This is probably a misconfiguration."); } } - + return updateHook; } + + protected void deactivateUser(T user, Auditor auditor) { + if (UserStatus.ACTIVE.equals(user.getUserStatus())) { + changeUserStatus(user, UserStatus.ON_HOLD, auditor); + + identityUpdater.updateIdentity(user); + + /* + * Also flag all registries for user ON_HOLD + */ + List<RegistryEntity> registryList = registryDao.findByUserAndStatus(user, RegistryStatus.ACTIVE, + RegistryStatus.LOST_ACCESS, RegistryStatus.INVALID); + for (RegistryEntity registry : registryList) { + changeRegistryStatus(registry, RegistryStatus.ON_HOLD, "user-on-hold", auditor); + } + } + } + + protected boolean reactivateUser(T user, List<ServiceEntity> delayedRegisterList, Auditor auditor) { + Boolean changed = false; + if (UserStatus.ON_HOLD.equals(user.getUserStatus())) { + changeUserStatus(user, UserStatus.ACTIVE, auditor); + + /* + * Also reenable all registries for user to LOST_ACCESS. They are rechecked then + */ + List<RegistryEntity> registryList = registryDao.findByUserAndStatus(user, RegistryStatus.ON_HOLD); + for (RegistryEntity registry : registryList) { + changeRegistryStatus(registry, RegistryStatus.LOST_ACCESS, "user-reactivated", auditor); + + /* + * check if parent registry is missing + */ + if (registry.getService().getParentService() != null) { + List<RegistryEntity> parentRegistryList = registryDao.findByServiceAndIdentityAndNotStatus( + registry.getService().getParentService(), user.getIdentity(), RegistryStatus.DELETED, + RegistryStatus.DEPROVISIONED); + if (parentRegistryList.size() == 0) { + delayedRegisterList.add(registry.getService().getParentService()); + } + } + } + + /* + * fire a user changed event to be sure, when the user is activated + */ + changed = true; + } + + return changed; + } + + protected boolean updateGroups(T user, Map<String, List<Object>> attributeMap, Auditor auditor) + throws UserUpdateException { + Set<GroupEntity> changedGroups = getGroupUpdater().updateGroupsForUser(user, attributeMap, auditor); + + if (changedGroups.size() > 0) { + return true; + } else { + return false; + } + } + + protected boolean updateAttributeSources(T user, ServiceEntity service, String executor, Auditor auditor) + throws UserUpdateException { + Boolean changed = false; + + /* + * if service is set, update only attribute sources spcific for this service. + * Else update all (login via web or generic attribute query) + */ + if (service != null) { + service = serviceDao.find(equal(ServiceEntity_.id, service.getId()), ServiceEntity_.attributeSourceService); + + for (AttributeSourceServiceEntity asse : service.getAttributeSourceService()) { + changed |= attributeSourceUpdater.updateUserAttributes(user, asse.getAttributeSource(), executor); + } + } else { + // find all user sources to update + Set<AttributeSourceEntity> asList = new HashSet<>( + attributeSourceDao.findAll(equal(AttributeSourceEntity_.userSource, true))); + // and add all sources which are already connected to the user + asList.addAll(asUserAttrDao.findAll(equal(ASUserAttrEntity_.user, user)).stream() + .map(a -> a.getAttributeSource()).toList()); + for (AttributeSourceEntity as : asList) { + changed |= attributeSourceUpdater.updateUserAttributes(user, as, executor); + } + } + return changed; + } + + protected void changeUserStatus(T user, UserStatus toStatus, Auditor auditor) { + UserStatus fromStatus = user.getUserStatus(); + user.setUserStatus(toStatus); + user.setLastStatusChange(new Date()); + + logger.debug("{}: change user status from {} to {}", user.getEppn(), fromStatus, toStatus); + auditor.logAction(user.getEppn(), "CHANGE STATUS", fromStatus + " -> " + toStatus, + "Change status " + fromStatus + " -> " + toStatus, AuditStatus.SUCCESS); + } + + protected void changeRegistryStatus(RegistryEntity registry, RegistryStatus toStatus, String statusMessage, + Auditor parentAuditor) { + RegistryStatus fromStatus = registry.getRegistryStatus(); + registry.setRegistryStatus(toStatus); + registry.setStatusMessage(statusMessage); + registry.setLastStatusChange(new Date()); + + logger.debug("{} {} {}: change registry status from {} to {}", new Object[] { registry.getUser().getEppn(), + registry.getService().getShortName(), registry.getId(), fromStatus, toStatus }); + RegistryAuditor registryAuditor = new RegistryAuditor(auditDao, auditDetailDao, appConfig); + registryAuditor.setParent(parentAuditor); + registryAuditor.startAuditTrail(parentAuditor.getActualExecutor()); + registryAuditor.setName(getClass().getName() + "-UserUpdate-Registry-Audit"); + registryAuditor.setDetail("Update registry " + registry.getId() + " for user " + registry.getUser().getEppn()); + registryAuditor.setRegistry(registry); + registryAuditor.logAction(registry.getUser().getEppn(), "CHANGE STATUS", "registry-" + registry.getId(), + "Change status " + fromStatus + " -> " + toStatus, AuditStatus.SUCCESS); + registryAuditor.finishAuditTrail(); + } + + protected Date getNextScheduledUpdate() { + Long futureMillis = 30L * 24L * 60L * 60L * 1000L; + if (appConfig.getConfigOptions().containsKey("update_schedule_future")) { + futureMillis = Long.decode(appConfig.getConfigValue("update_schedule_future")); + } + Integer futureMillisRandom = 6 * 60 * 60 * 1000; + if (appConfig.getConfigOptions().containsKey("update_schedule_future_random")) { + futureMillisRandom = Integer.decode(appConfig.getConfigValue("update_schedule_future_random")); + } + Random r = new Random(); + return new Date(System.currentTimeMillis() + futureMillis + r.nextInt(futureMillisRandom)); + } + + protected void fireUserChangeEvent(T user, String executor, Auditor auditor) { + + UserEvent userEvent = new UserEvent(user, auditor.getAudit()); + + try { + eventSubmitter.submit(userEvent, EventType.USER_UPDATE, executor); + } catch (EventSubmitException e) { + logger.warn("Could not submit event", e); + } + } } diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/OAuthUserUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/OAuthUserUpdater.java new file mode 100644 index 0000000000000000000000000000000000000000..8c4b7dcbd020afc5a52d7f209f722896288025db --- /dev/null +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/OAuthUserUpdater.java @@ -0,0 +1,192 @@ +package edu.kit.scc.webreg.service.impl; + +import java.lang.reflect.InvocationTargetException; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.beanutils.PropertyUtils; +import org.slf4j.Logger; + +import edu.kit.scc.webreg.audit.Auditor; +import edu.kit.scc.webreg.dao.SerialDao; +import edu.kit.scc.webreg.entity.ServiceEntity; +import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.entity.attribute.IncomingOAuthAttributeEntity; +import edu.kit.scc.webreg.entity.audit.AuditStatus; +import edu.kit.scc.webreg.entity.oauth.OAuthUserEntity; +import edu.kit.scc.webreg.exc.UserUpdateException; +import edu.kit.scc.webreg.hook.HookManager; +import edu.kit.scc.webreg.hook.UserServiceHook; +import edu.kit.scc.webreg.service.attribute.IncomingAttributesHandler; +import edu.kit.scc.webreg.service.attribute.IncomingOAuthAttributesHandler; +import edu.kit.scc.webreg.service.group.HomeOrgGroupUpdater; +import edu.kit.scc.webreg.service.group.OAuthGroupUpdater; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class OAuthUserUpdater extends AbstractUserUpdater<OAuthUserEntity> { + + private static final long serialVersionUID = 1L; + + @Inject + private Logger logger; + + @Inject + private SerialDao serialDao; + + @Inject + private HookManager hookManager; + + @Inject + private OAuthGroupUpdater oauthGroupUpdater; + + @Inject + private IncomingOAuthAttributesHandler incomingAttributeHandler; + + public OAuthUserEntity updateUserFromOP(OAuthUserEntity user, String executor, StringBuffer debugLog) + throws UserUpdateException { + return updateUserFromHomeOrg(user, null, executor, debugLog); + } + + public OAuthUserEntity updateUserFromHomeOrg(OAuthUserEntity user, ServiceEntity service, String executor, + StringBuffer debugLog) throws UserUpdateException { + updateFail(user); + throw new UserUpdateException("Not implemented"); + } + + public boolean updateUserNew(OAuthUserEntity user, Map<String, List<Object>> attributeMap, String executor, + Auditor auditor, StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { + boolean changed = false; + + changed |= preUpdateUser(user, attributeMap, user.getOauthIssuer().getGenericStore(), executor, null, debugLog); + changed |= updateUserFromAttribute(user, attributeMap, auditor); + changed |= postUpdateUser(user, attributeMap, user.getOauthIssuer().getGenericStore(), executor, null, debugLog, + lastLoginHost); + + return changed; + } + + public boolean updateUserFromAttribute(OAuthUserEntity user, Map<String, List<Object>> attributeMap, + boolean withoutUidNumber, Auditor auditor) throws UserUpdateException { + + boolean changed = false; + + UserServiceHook completeOverrideHook = null; + Set<UserServiceHook> activeHooks = new HashSet<UserServiceHook>(); + + for (UserServiceHook hook : hookManager.getUserHooks()) { + if (hook.isResponsible(user, attributeMap)) { + + hook.preUpdateUserFromAttribute(user, attributeMap, auditor); + activeHooks.add(hook); + + if (hook.isCompleteOverride()) { + completeOverrideHook = hook; + } + } + } + + if (completeOverrideHook == null) { + @SuppressWarnings("unchecked") + HashMap<String, Object> userMap = (HashMap<String, Object>) attributeMap.get("user").get(0); + + if (userMap.get("email") != null && (userMap.get("email") instanceof String)) + changed |= compareAndChangeProperty(user, "email", (String) userMap.get("email"), auditor); + else + changed |= compareAndChangeProperty(user, "email", null, auditor); + + if (userMap.get("name") != null && (userMap.get("name") instanceof String)) + changed |= compareAndChangeProperty(user, "name", (String) userMap.get("name"), auditor); + else + changed |= compareAndChangeProperty(user, "name", null, auditor); + + if ((!withoutUidNumber) && (user.getUidNumber() == null)) { + user.setUidNumber(serialDao.nextUidNumber().intValue()); + logger.info("Setting UID Number {} for user {}", user.getUidNumber(), user.getEppn()); + auditor.logAction(user.getEppn(), "SET FIELD", "uidNumber", "" + user.getUidNumber(), + AuditStatus.SUCCESS); + changed = true; + } + } else { + logger.info("Overriding standard User Update Mechanism! Activator: {}", + completeOverrideHook.getClass().getName()); + } + + for (UserServiceHook hook : activeHooks) { + hook.postUpdateUserFromAttribute(user, attributeMap, auditor); + } + + return changed; + } + + private boolean compareAndChangeProperty(UserEntity user, String property, String value, Auditor auditor) { + String s = null; + String action = null; + + try { + Object actualValue = PropertyUtils.getProperty(user, property); + + if (actualValue != null && actualValue.equals(value)) { + // Value didn't change, do nothing + return false; + } + + if (actualValue == null && value == null) { + // Value stayed null + return false; + } + + if (actualValue == null) { + s = "null"; + action = "SET FIELD"; + } else { + s = actualValue.toString(); + action = "UPDATE FIELD"; + } + + s = s + " -> " + value; + if (s.length() > 1017) + s = s.substring(0, 1017) + "..."; + + PropertyUtils.setProperty(user, property, value); + + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.SUCCESS); + } catch (IllegalAccessException e) { + logger.warn("This probably shouldn't happen: ", e); + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); + } catch (InvocationTargetException e) { + logger.warn("This probably shouldn't happen: ", e); + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); + } catch (NoSuchMethodException e) { + logger.warn("This probably shouldn't happen: ", e); + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); + } + + return true; + } + + protected void updateFail(OAuthUserEntity user) { + user.setLastFailedUpdate(new Date()); + user.setScheduledUpdate(getNextScheduledUpdate()); + } + + @Override + public HomeOrgGroupUpdater<OAuthUserEntity> getGroupUpdater() { + return oauthGroupUpdater; + } + + @Override + public Map<String, String> resolveHomeOrgGenericStore(OAuthUserEntity user) { + return user.getOauthIssuer().getGenericStore(); + } + + @Override + public IncomingAttributesHandler<IncomingOAuthAttributeEntity> resolveIncomingAttributeHandler(OAuthUserEntity user) { + return incomingAttributeHandler; + } +} diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/OidcUserUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/OidcUserUpdater.java new file mode 100644 index 0000000000000000000000000000000000000000..07bf647ca523c2ebfbfaf032a25d4bf6243e79df --- /dev/null +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/OidcUserUpdater.java @@ -0,0 +1,344 @@ +package edu.kit.scc.webreg.service.impl; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.beanutils.PropertyUtils; +import org.slf4j.Logger; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; +import com.nimbusds.oauth2.sdk.AuthorizationGrant; +import com.nimbusds.oauth2.sdk.ErrorObject; +import com.nimbusds.oauth2.sdk.ParseException; +import com.nimbusds.oauth2.sdk.RefreshTokenGrant; +import com.nimbusds.oauth2.sdk.TokenErrorResponse; +import com.nimbusds.oauth2.sdk.TokenRequest; +import com.nimbusds.oauth2.sdk.TokenResponse; +import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; +import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; +import com.nimbusds.oauth2.sdk.auth.Secret; +import com.nimbusds.oauth2.sdk.http.HTTPResponse; +import com.nimbusds.oauth2.sdk.id.ClientID; +import com.nimbusds.oauth2.sdk.id.Issuer; +import com.nimbusds.oauth2.sdk.token.BearerAccessToken; +import com.nimbusds.oauth2.sdk.token.RefreshToken; +import com.nimbusds.openid.connect.sdk.OIDCTokenResponse; +import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser; +import com.nimbusds.openid.connect.sdk.UserInfoRequest; +import com.nimbusds.openid.connect.sdk.UserInfoResponse; +import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet; +import com.nimbusds.openid.connect.sdk.claims.UserInfo; +import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator; + +import edu.kit.scc.regapp.oidc.tools.OidcOpMetadataSingletonBean; +import edu.kit.scc.regapp.oidc.tools.OidcTokenHelper; +import edu.kit.scc.webreg.audit.Auditor; +import edu.kit.scc.webreg.dao.SerialDao; +import edu.kit.scc.webreg.entity.ServiceEntity; +import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.entity.attribute.IncomingOidcAttributeEntity; +import edu.kit.scc.webreg.entity.audit.AuditStatus; +import edu.kit.scc.webreg.entity.oidc.OidcRpConfigurationEntity; +import edu.kit.scc.webreg.entity.oidc.OidcUserEntity; +import edu.kit.scc.webreg.exc.UserUpdateException; +import edu.kit.scc.webreg.hook.HookManager; +import edu.kit.scc.webreg.hook.UserServiceHook; +import edu.kit.scc.webreg.service.attribute.IncomingAttributesHandler; +import edu.kit.scc.webreg.service.attribute.IncomingOidcAttributesHandler; +import edu.kit.scc.webreg.service.group.HomeOrgGroupUpdater; +import edu.kit.scc.webreg.service.group.OidcGroupUpdater; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class OidcUserUpdater extends AbstractUserUpdater<OidcUserEntity> { + + private static final long serialVersionUID = 1L; + + @Inject + private Logger logger; + + @Inject + private SerialDao serialDao; + + @Inject + private HookManager hookManager; + + @Inject + private OidcGroupUpdater oidcGroupUpdater; + + @Inject + private OidcTokenHelper oidcTokenHelper; + + @Inject + private OidcOpMetadataSingletonBean opMetadataBean; + + @Inject + private IncomingOidcAttributesHandler incomingAttributeHandler; + + public OidcUserEntity updateUserFromOP(OidcUserEntity user, String executor, StringBuffer debugLog) + throws UserUpdateException { + return updateUserFromHomeOrg(user, null, executor, debugLog); + } + + public OidcUserEntity updateUserFromHomeOrg(OidcUserEntity user, ServiceEntity service, String executor, StringBuffer debugLog) + throws UserUpdateException { + + try { + /** + * TODO Implement refresh here + */ + OidcRpConfigurationEntity rpConfig = user.getIssuer(); + + if (user.getAttributeStore().get("refreshToken") == null) { + updateFail(user); + throw new UserUpdateException("refresh token is null"); + } + + RefreshToken token = new RefreshToken(user.getAttributeStore().get("refreshToken")); + AuthorizationGrant refreshTokenGrant = new RefreshTokenGrant(token); + + ClientID clientID = new ClientID(user.getIssuer().getClientId()); + Secret clientSecret = new Secret(user.getIssuer().getSecret()); + ClientAuthentication clientAuth = new ClientSecretBasic(clientID, clientSecret); + + TokenRequest tokenRequest = new TokenRequest(opMetadataBean.getTokenEndpointURI(user.getIssuer()), + clientAuth, refreshTokenGrant); + TokenResponse tokenResponse = OIDCTokenResponseParser.parse(tokenRequest.toHTTPRequest().send()); + + if (!tokenResponse.indicatesSuccess()) { + TokenErrorResponse errorResponse = tokenResponse.toErrorResponse(); + ErrorObject error = errorResponse.getErrorObject(); + logger.info("Got error: code {}, desc {}, http-status {}, uri {}", error.getCode(), + error.getDescription()); + updateFail(user); + throw new UserUpdateException(); + } else { + OIDCTokenResponse oidcTokenResponse = (OIDCTokenResponse) tokenResponse.toSuccessResponse(); + logger.debug("response: {}", oidcTokenResponse.toJSONObject()); + + JWT idToken = oidcTokenResponse.getOIDCTokens().getIDToken(); + IDTokenClaimsSet claims = null; + + if (idToken != null) { + IDTokenValidator validator = new IDTokenValidator(new Issuer(rpConfig.getServiceUrl()), + new ClientID(rpConfig.getClientId()), JWSAlgorithm.RS256, + opMetadataBean.getJWKSetURI(rpConfig).toURL()); + + try { + claims = validator.validate(idToken, null); + logger.debug("Got signed claims verified from {}: {}", claims.getIssuer(), claims.getSubject()); + } catch (BadJOSEException | JOSEException e) { + throw new UserUpdateException("signature failed: " + e.getMessage()); + } + } + + RefreshToken refreshToken = null; + + if (oidcTokenResponse.getOIDCTokens().getRefreshToken() != null) { + + refreshToken = oidcTokenResponse.getOIDCTokens().getRefreshToken(); + try { + JWT refreshJwt = JWTParser.parse(refreshToken.getValue()); + // Well, what to do with this info? Check if refresh token is short or long + // lived? <1 day? + logger.info("refresh will expire at: {}", refreshJwt.getJWTClaimsSet().getExpirationTime()); + } catch (java.text.ParseException e) { + logger.debug("Refresh token is no JWT"); + } + } else { + logger.info("Answer contains no new refresh token, keeping old one"); + } + + BearerAccessToken bearerAccessToken = oidcTokenResponse.getOIDCTokens().getBearerAccessToken(); + + HTTPResponse httpResponse = new UserInfoRequest(opMetadataBean.getUserInfoEndpointURI(rpConfig), + bearerAccessToken).toHTTPRequest().send(); + + UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse); + + if (!userInfoResponse.indicatesSuccess()) { + updateFail(user); + throw new UserUpdateException("got userinfo error response: " + + userInfoResponse.toErrorResponse().getErrorObject().getDescription()); + } + + UserInfo userInfo = userInfoResponse.toSuccessResponse().getUserInfo(); + logger.info("userinfo {}, {}, {}", userInfo.getSubject(), userInfo.getPreferredUsername(), + userInfo.getEmailAddress()); + + logger.debug("Updating OIDC user {}", user.getSubjectId()); + + user = updateUser(user, claims, userInfo, refreshToken, bearerAccessToken, "web-sso", debugLog, null); + + } + } catch (IOException | ParseException e) { + logger.warn("Exception!", e); + } + + return user; + } + + public OidcUserEntity updateUser(OidcUserEntity user, IDTokenClaimsSet claims, UserInfo userInfo, + RefreshToken refreshToken, BearerAccessToken bat, String executor, ServiceEntity service, + StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { + + Map<String, List<Object>> attributeMap = oidcTokenHelper.convertToAttributeMap(claims, userInfo, refreshToken, + bat); + + if (service != null) + return updateUser(user, attributeMap, executor, service, debugLog, lastLoginHost); + else + return updateUser(user, attributeMap, executor, debugLog, lastLoginHost); + } + + public OidcUserEntity updateUser(OidcUserEntity user, IDTokenClaimsSet claims, UserInfo userInfo, + RefreshToken refreshToken, BearerAccessToken bat, String executor, StringBuffer debugLog, + String lastLoginHost) throws UserUpdateException { + + return updateUser(user, claims, userInfo, refreshToken, bat, executor, null, debugLog, lastLoginHost); + } + + public boolean updateUserNew(OidcUserEntity user, Map<String, List<Object>> attributeMap, String executor, + Auditor auditor, StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { + boolean changed = false; + + changed |= preUpdateUser(user, attributeMap, user.getIssuer().getGenericStore(), executor, null, debugLog); + changed |= updateUserFromAttribute(user, attributeMap, auditor); + changed |= postUpdateUser(user, attributeMap, user.getIssuer().getGenericStore(), executor, null, debugLog, + lastLoginHost); + + return changed; + } + + public boolean updateUserFromAttribute(OidcUserEntity user, Map<String, List<Object>> attributeMap, + boolean withoutUidNumber, Auditor auditor) throws UserUpdateException { + + boolean changed = false; + + UserServiceHook completeOverrideHook = null; + Set<UserServiceHook> activeHooks = new HashSet<UserServiceHook>(); + + for (UserServiceHook hook : hookManager.getUserHooks()) { + if (hook.isResponsible(user, attributeMap)) { + + hook.preUpdateUserFromAttribute(user, attributeMap, auditor); + activeHooks.add(hook); + + if (hook.isCompleteOverride()) { + completeOverrideHook = hook; + } + } + } + + if (completeOverrideHook == null) { + IDTokenClaimsSet claims = oidcTokenHelper.claimsFromMap(attributeMap); + if (claims == null) { + logger.info("No claims set for user {}", user.getId()); + } + + UserInfo userInfo = oidcTokenHelper.userInfoFromMap(attributeMap); + if (userInfo == null) { + throw new UserUpdateException("User info is missing in session"); + } + + changed |= compareAndChangeProperty(user, "email", userInfo.getEmailAddress(), auditor); + changed |= compareAndChangeProperty(user, "eppn", userInfo.getStringClaim("eduPersonPrincipalName"), + auditor); + changed |= compareAndChangeProperty(user, "givenName", userInfo.getGivenName(), auditor); + changed |= compareAndChangeProperty(user, "surName", userInfo.getFamilyName(), auditor); + + if ((!withoutUidNumber) && (user.getUidNumber() == null)) { + user.setUidNumber(serialDao.nextUidNumber().intValue()); + logger.info("Setting UID Number {} for user {}", user.getUidNumber(), user.getEppn()); + auditor.logAction(user.getEppn(), "SET FIELD", "uidNumber", "" + user.getUidNumber(), + AuditStatus.SUCCESS); + changed = true; + } + } else { + logger.info("Overriding standard User Update Mechanism! Activator: {}", + completeOverrideHook.getClass().getName()); + } + + for (UserServiceHook hook : activeHooks) { + hook.postUpdateUserFromAttribute(user, attributeMap, auditor); + } + + return changed; + } + + private boolean compareAndChangeProperty(UserEntity user, String property, String value, Auditor auditor) { + String s = null; + String action = null; + + try { + Object actualValue = PropertyUtils.getProperty(user, property); + + if (actualValue != null && actualValue.equals(value)) { + // Value didn't change, do nothing + return false; + } + + if (actualValue == null && value == null) { + // Value stayed null + return false; + } + + if (actualValue == null) { + s = "null"; + action = "SET FIELD"; + } else { + s = actualValue.toString(); + action = "UPDATE FIELD"; + } + + s = s + " -> " + value; + if (s.length() > 1017) + s = s.substring(0, 1017) + "..."; + + PropertyUtils.setProperty(user, property, value); + + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.SUCCESS); + } catch (IllegalAccessException e) { + logger.warn("This probably shouldn't happen: ", e); + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); + } catch (InvocationTargetException e) { + logger.warn("This probably shouldn't happen: ", e); + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); + } catch (NoSuchMethodException e) { + logger.warn("This probably shouldn't happen: ", e); + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); + } + + return true; + } + + protected void updateFail(OidcUserEntity user) { + user.setLastFailedUpdate(new Date()); + user.setScheduledUpdate(getNextScheduledUpdate()); + } + + @Override + public HomeOrgGroupUpdater<OidcUserEntity> getGroupUpdater() { + return oidcGroupUpdater; + } + + @Override + public Map<String, String> resolveHomeOrgGenericStore(OidcUserEntity user) { + return user.getIssuer().getGenericStore(); + } + + @Override + public IncomingAttributesHandler<IncomingOidcAttributeEntity> resolveIncomingAttributeHandler(OidcUserEntity user) { + return incomingAttributeHandler; + } +} diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/SamlUserUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/SamlUserUpdater.java new file mode 100644 index 0000000000000000000000000000000000000000..ec61616e8efd606681b090e6e3458c261e0aa1ba --- /dev/null +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/SamlUserUpdater.java @@ -0,0 +1,425 @@ +package edu.kit.scc.webreg.service.impl; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.commons.beanutils.PropertyUtils; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.soap.common.SOAPException; +import org.opensaml.xmlsec.encryption.support.DecryptionException; +import org.slf4j.Logger; + +import edu.kit.scc.webreg.audit.Auditor; +import edu.kit.scc.webreg.audit.IdpCommunicationAuditor; +import edu.kit.scc.webreg.bootstrap.ApplicationConfig; +import edu.kit.scc.webreg.dao.SamlAssertionDao; +import edu.kit.scc.webreg.dao.SamlIdpMetadataDao; +import edu.kit.scc.webreg.dao.SamlSpConfigurationDao; +import edu.kit.scc.webreg.dao.SamlUserDao; +import edu.kit.scc.webreg.dao.SerialDao; +import edu.kit.scc.webreg.dao.audit.AuditDetailDao; +import edu.kit.scc.webreg.dao.audit.AuditEntryDao; +import edu.kit.scc.webreg.entity.SamlAssertionEntity; +import edu.kit.scc.webreg.entity.SamlIdpMetadataEntity; +import edu.kit.scc.webreg.entity.SamlIdpMetadataEntityStatus; +import edu.kit.scc.webreg.entity.SamlSpConfigurationEntity; +import edu.kit.scc.webreg.entity.SamlUserEntity; +import edu.kit.scc.webreg.entity.ServiceEntity; +import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.entity.attribute.IncomingSamlAttributeEntity; +import edu.kit.scc.webreg.entity.audit.AuditStatus; +import edu.kit.scc.webreg.exc.UserUpdateException; +import edu.kit.scc.webreg.hook.HookManager; +import edu.kit.scc.webreg.hook.UserServiceHook; +import edu.kit.scc.webreg.logging.LogHelper; +import edu.kit.scc.webreg.service.attribute.IncomingAttributesHandler; +import edu.kit.scc.webreg.service.attribute.IncomingSamlAttributesHandler; +import edu.kit.scc.webreg.service.group.HomeOrgGroupUpdater; +import edu.kit.scc.webreg.service.group.SamlGroupUpdater; +import edu.kit.scc.webreg.service.saml.AttributeQueryHelper; +import edu.kit.scc.webreg.service.saml.Saml2AssertionService; +import edu.kit.scc.webreg.service.saml.SamlHelper; +import edu.kit.scc.webreg.service.saml.exc.MetadataException; +import edu.kit.scc.webreg.service.saml.exc.NoAssertionException; +import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException; +import edu.kit.scc.webreg.service.saml.exc.SamlUnknownPrincipalException; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class SamlUserUpdater extends AbstractUserUpdater<SamlUserEntity> { + + private static final long serialVersionUID = 1L; + + @Inject + private Logger logger; + + @Inject + private AuditEntryDao auditDao; + + @Inject + private AuditDetailDao auditDetailDao; + + @Inject + private Saml2AssertionService saml2AssertionService; + + @Inject + private AttributeQueryHelper attrQueryHelper; + + @Inject + private SamlUserDao userDao; + + @Inject + private SamlGroupUpdater homeOrgGroupUpdater; + + @Inject + private SamlHelper samlHelper; + + @Inject + private SamlIdpMetadataDao idpDao; + + @Inject + private SamlSpConfigurationDao spDao; + + @Inject + private SerialDao serialDao; + + @Inject + private HookManager hookManager; + + @Inject + private IncomingSamlAttributesHandler incomingAttributeHandler; + + @Inject + private SamlAssertionDao samlAsserionDao; + + @Inject + private AttributeMapHelper attrHelper; + + @Inject ApplicationConfig appConfig; + + @Inject + private LogHelper logHelper; + + public SamlUserEntity updateUser(SamlUserEntity user, Assertion assertion, String executor, ServiceEntity service, + StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { + + if (assertion != null) { + samlAsserionDao.deleteAssertionForUser(user); + + SamlAssertionEntity samlAssertionEntity = samlAsserionDao.createNew(); + samlAssertionEntity.setUser(user); + samlAssertionEntity.setAssertionData(samlHelper.prettyPrint(assertion)); + samlAssertionEntity.setValidUntil(new Date(System.currentTimeMillis() + (4L * 60L * 60L * 1000L))); + samlAssertionEntity = samlAsserionDao.persist(samlAssertionEntity); + } + + Map<String, List<Object>> attributeMap = saml2AssertionService.extractAttributes(assertion); + + if (debugLog != null) { + debugLog.append("Extracted attributes from Assertion:\n"); + for (Entry<String, List<Object>> entry : attributeMap.entrySet()) { + debugLog.append(entry.getKey()).append(":\t").append(entry.getValue()).append("\n"); + } + } + + if (service != null) + return updateUser(user, attributeMap, executor, service, debugLog, lastLoginHost); + else + return updateUser(user, attributeMap, executor, debugLog, lastLoginHost); + } + + public SamlUserEntity updateUser(SamlUserEntity user, Assertion assertion, String executor, String lastLoginHost) + throws UserUpdateException { + + return updateUser(user, assertion, executor, null, null, lastLoginHost); + } + + public SamlUserEntity updateUserFromIdp(SamlUserEntity user, String executor) throws UserUpdateException { + return updateUserFromHomeOrg(user, null, executor, null); + } + + public SamlUserEntity updateUserFromHomeOrg(SamlUserEntity user, ServiceEntity service, String executor, + StringBuffer debugLog) throws UserUpdateException { + + SamlSpConfigurationEntity spEntity = spDao.findByEntityId(user.getPersistentSpId()); + SamlIdpMetadataEntity idpEntity = idpDao.findByEntityId(user.getIdp().getEntityId()); + + IdpCommunicationAuditor auditor = new IdpCommunicationAuditor(auditDao, auditDetailDao, appConfig); + auditor.setName("UpdateUserFromIdp"); + auditor.setDetail("Call IDP " + idpEntity.getEntityId() + " from SP " + spEntity.getEntityId() + " for User " + + user.getEppn()); + auditor.setIdp(idpEntity); + auditor.setSpConfig(spEntity); + auditor.startAuditTrail(executor); + + EntityDescriptor idpEntityDescriptor = samlHelper.unmarshal(idpEntity.getEntityDescriptor(), + EntityDescriptor.class, auditor); + + Response samlResponse; + try { + /* + * If something goes wrong here, communication with the idp probably failed + */ + + samlResponse = attrQueryHelper.query(user, idpEntity, idpEntityDescriptor, spEntity, debugLog); + + if (logger.isTraceEnabled()) + logger.trace("{}", samlHelper.prettyPrint(samlResponse)); + + if (debugLog != null) { + debugLog.append("\nIncoming SAML Response:\n\n").append(samlHelper.prettyPrint(samlResponse)) + .append("\n"); + } + + } catch (SOAPException e) { + /* + * This exception is thrown if the certificate chain is incomplete e.g. + */ + handleException(user, e, idpEntity, auditor, debugLog); + throw new UserUpdateException(e); + } catch (MetadataException e) { + /* + * is thrown if AttributeQuery location is missing in metadata, or something is + * wrong with the sp certificate + */ + handleException(user, e, idpEntity, auditor, debugLog); + throw new UserUpdateException(e); + } catch (SecurityException e) { + handleException(user, e, idpEntity, auditor, debugLog); + throw new UserUpdateException(e); + } catch (Exception e) { + handleException(user, e, idpEntity, auditor, debugLog); + throw new UserUpdateException(e); + } + + try { + /* + * Don't check Assertion Signature, because we are contacting the IDP directly + */ + Assertion assertion; + try { + if (debugLog != null) { + debugLog.append("\nExtracting Assertion from SAML Response without signature check...\n"); + } + + assertion = saml2AssertionService.processSamlResponse(samlResponse, idpEntity, idpEntityDescriptor, + spEntity, false); + + if (logger.isTraceEnabled()) + logger.trace("{}", samlHelper.prettyPrint(assertion)); + + } catch (NoAssertionException e) { + if (user.getIdp() != null) + logger.warn("No assertion delivered for user {} from idp {}", user.getEppn(), + user.getIdp().getEntityId()); + else + logger.warn("No assertion delivered for user {} from idp {}", user.getEppn()); + assertion = null; + } catch (SamlUnknownPrincipalException e) { + if (user.getIdp() != null) + logger.warn("Unknown principal status for user {} from idp {}", user.getEppn(), + user.getIdp().getEntityId()); + else + logger.warn("Unknown principal status for user {}", user.getEppn()); + assertion = null; + } + + updateIdpStatus(SamlIdpMetadataEntityStatus.GOOD, idpEntity); + + return updateUser(user, assertion, "attribute-query", service, debugLog, null); + } catch (DecryptionException e) { + handleException(user, e, idpEntity, auditor, debugLog); + throw new UserUpdateException(e); + } catch (IOException e) { + handleException(user, e, idpEntity, auditor, debugLog); + throw new UserUpdateException(e); + } catch (SamlAuthenticationException e) { + /* + * Thrown if i.e. the AttributeQuery profile is not configured correctly + */ + handleException(user, e, idpEntity, auditor, debugLog); + throw new UserUpdateException(e); + } + } + + protected void handleException(SamlUserEntity user, Exception e, SamlIdpMetadataEntity idpEntity, Auditor auditor, + StringBuffer debugLog) { + updateFail(user); + String message = e.getMessage(); + if (e.getCause() != null) + message += " InnerCause: " + e.getCause().getMessage(); + auditor.logAction(idpEntity.getEntityId(), "SAML ATTRIBUTE QUERY", user.getEppn(), message, AuditStatus.FAIL); + auditor.finishAuditTrail(); + auditor.commitAuditTrail(); + + if (debugLog != null) { + debugLog.append("Attribute Query failed: ").append(e.getMessage()); + if (e.getCause() != null) + debugLog.append("Cause: ").append(e.getCause().getMessage()); + debugLog.append(logHelper.convertStacktrace(e)); + } + + updateIdpStatus(SamlIdpMetadataEntityStatus.FAULTY, idpEntity); + } + + protected void updateIdpStatus(SamlIdpMetadataEntityStatus status, SamlIdpMetadataEntity idpEntity) { + if (!status.equals(idpEntity.getAqIdpStatus())) { + idpEntity.setAqIdpStatus(status); + idpEntity.setLastAqStatusChange(new Date()); + } + } + + protected void updateFail(SamlUserEntity user) { + user.setLastFailedUpdate(new Date()); + user.setScheduledUpdate(getNextScheduledUpdate()); + user = userDao.persist(user); + } + + + + public boolean updateUserNew(SamlUserEntity user, Map<String, List<Object>> attributeMap, String executor, + Auditor auditor, StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { + boolean changed = false; + + changed |= preUpdateUser(user, attributeMap, user.getIdp().getGenericStore(), executor, null, debugLog); + changed |= updateUserFromAttribute(user, attributeMap, auditor); + changed |= postUpdateUser(user, attributeMap, user.getIdp().getGenericStore(), executor, null, debugLog, + lastLoginHost); + + return changed; + } + + public boolean updateUserFromAttribute(SamlUserEntity user, Map<String, List<Object>> attributeMap, + boolean withoutUidNumber, Auditor auditor) throws UserUpdateException { + + boolean changed = false; + + UserServiceHook completeOverrideHook = null; + Set<UserServiceHook> activeHooks = new HashSet<UserServiceHook>(); + + for (UserServiceHook hook : hookManager.getUserHooks()) { + if (hook.isResponsible(user, attributeMap)) { + + hook.preUpdateUserFromAttribute(user, attributeMap, auditor); + activeHooks.add(hook); + + if (hook.isCompleteOverride()) { + completeOverrideHook = hook; + } + } + } + + if (completeOverrideHook == null) { + changed |= compareAndChangeProperty(user, "email", attributeMap.get("urn:oid:0.9.2342.19200300.100.1.3"), + auditor); + changed |= compareAndChangeProperty(user, "eppn", attributeMap.get("urn:oid:1.3.6.1.4.1.5923.1.1.1.6"), + auditor); + changed |= compareAndChangeProperty(user, "givenName", attributeMap.get("urn:oid:2.5.4.42"), auditor); + changed |= compareAndChangeProperty(user, "surName", attributeMap.get("urn:oid:2.5.4.4"), auditor); + + List<String> emailList = attrHelper.attributeListToStringList(attributeMap, + "urn:oid:0.9.2342.19200300.100.1.3"); + if (emailList != null && emailList.size() > 1) { + + if (user.getEmailAddresses() == null) { + user.setEmailAddresses(new HashSet<String>()); + } + + for (int i = 1; i < emailList.size(); i++) { + user.getEmailAddresses().add(emailList.get(i)); + } + } + + if ((!withoutUidNumber) && (user.getUidNumber() == null)) { + user.setUidNumber(serialDao.nextUidNumber().intValue()); + logger.info("Setting UID Number {} for user {}", user.getUidNumber(), user.getEppn()); + auditor.logAction(user.getEppn(), "SET FIELD", "uidNumber", "" + user.getUidNumber(), + AuditStatus.SUCCESS); + changed = true; + } + } else { + logger.info("Overriding standard User Update Mechanism! Activator: {}", + completeOverrideHook.getClass().getName()); + } + + for (UserServiceHook hook : activeHooks) { + hook.postUpdateUserFromAttribute(user, attributeMap, auditor); + } + + return changed; + } + + private boolean compareAndChangeProperty(UserEntity user, String property, List<Object> objectValue, + Auditor auditor) { + String s = null; + String action = null; + + // In case of a List (multiple SAML Values), take the first value + String value = attrHelper.getSingleStringFirst(objectValue); + + try { + Object actualValue = PropertyUtils.getProperty(user, property); + + if (actualValue != null && actualValue.equals(value)) { + // Value didn't change, do nothing + return false; + } + + if (actualValue == null && value == null) { + // Value stayed null + return false; + } + + if (actualValue == null) { + s = "null"; + action = "SET FIELD"; + } else { + s = actualValue.toString(); + action = "UPDATE FIELD"; + } + + s = s + " -> " + value; + if (s.length() > 1017) + s = s.substring(0, 1017) + "..."; + + PropertyUtils.setProperty(user, property, value); + + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.SUCCESS); + } catch (IllegalAccessException e) { + logger.warn("This probably shouldn't happen: ", e); + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); + } catch (InvocationTargetException e) { + logger.warn("This probably shouldn't happen: ", e); + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); + } catch (NoSuchMethodException e) { + logger.warn("This probably shouldn't happen: ", e); + auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); + } + + return true; + } + + @Override + public HomeOrgGroupUpdater<SamlUserEntity> getGroupUpdater() { + return homeOrgGroupUpdater; + } + + @Override + public Map<String, String> resolveHomeOrgGenericStore(SamlUserEntity user) { + return user.getIdp().getGenericStore(); + } + + @Override + public IncomingAttributesHandler<IncomingSamlAttributeEntity> resolveIncomingAttributeHandler(SamlUserEntity user) { + return incomingAttributeHandler; + } +} diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdater.java index 8119a7d9e1e2e28f6c802edaf02f47bdea658d05..b4f6b4e1bc9f84bf9aacbcf09ea0e69ccb81be2f 100644 --- a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdater.java +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/impl/UserUpdater.java @@ -1,708 +1,22 @@ -package edu.kit.scc.webreg.service.impl; - -import static edu.kit.scc.webreg.dao.ops.RqlExpressions.equal; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Random; -import java.util.Set; - -import org.apache.commons.beanutils.PropertyUtils; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import org.opensaml.soap.common.SOAPException; -import org.opensaml.xmlsec.encryption.support.DecryptionException; -import org.slf4j.Logger; -import org.slf4j.MDC; - -import edu.kit.scc.webreg.as.AttributeSourceUpdater; -import edu.kit.scc.webreg.audit.Auditor; -import edu.kit.scc.webreg.audit.IdpCommunicationAuditor; -import edu.kit.scc.webreg.audit.RegistryAuditor; -import edu.kit.scc.webreg.audit.UserUpdateAuditor; -import edu.kit.scc.webreg.bootstrap.ApplicationConfig; -import edu.kit.scc.webreg.dao.RegistryDao; -import edu.kit.scc.webreg.dao.SamlAssertionDao; -import edu.kit.scc.webreg.dao.SamlIdpMetadataDao; -import edu.kit.scc.webreg.dao.SamlSpConfigurationDao; -import edu.kit.scc.webreg.dao.SamlUserDao; -import edu.kit.scc.webreg.dao.SerialDao; -import edu.kit.scc.webreg.dao.ServiceDao; -import edu.kit.scc.webreg.dao.as.ASUserAttrDao; -import edu.kit.scc.webreg.dao.as.AttributeSourceDao; -import edu.kit.scc.webreg.dao.audit.AuditDetailDao; -import edu.kit.scc.webreg.dao.audit.AuditEntryDao; -import edu.kit.scc.webreg.entity.EventType; -import edu.kit.scc.webreg.entity.GroupEntity; -import edu.kit.scc.webreg.entity.RegistryEntity; -import edu.kit.scc.webreg.entity.RegistryStatus; -import edu.kit.scc.webreg.entity.SamlAssertionEntity; -import edu.kit.scc.webreg.entity.SamlIdpMetadataEntity; -import edu.kit.scc.webreg.entity.SamlIdpMetadataEntityStatus; -import edu.kit.scc.webreg.entity.SamlSpConfigurationEntity; -import edu.kit.scc.webreg.entity.SamlUserEntity; -import edu.kit.scc.webreg.entity.ServiceEntity; -import edu.kit.scc.webreg.entity.ServiceEntity_; -import edu.kit.scc.webreg.entity.UserEntity; -import edu.kit.scc.webreg.entity.UserStatus; -import edu.kit.scc.webreg.entity.as.ASUserAttrEntity_; -import edu.kit.scc.webreg.entity.as.AttributeSourceEntity; -import edu.kit.scc.webreg.entity.as.AttributeSourceEntity_; -import edu.kit.scc.webreg.entity.as.AttributeSourceServiceEntity; -import edu.kit.scc.webreg.entity.attribute.IncomingAttributeSetEntity; -import edu.kit.scc.webreg.entity.audit.AuditDetailEntity; -import edu.kit.scc.webreg.entity.audit.AuditStatus; -import edu.kit.scc.webreg.entity.audit.AuditUserUpdateEntity; -import edu.kit.scc.webreg.event.EventSubmitter; -import edu.kit.scc.webreg.event.UserEvent; -import edu.kit.scc.webreg.event.exc.EventSubmitException; -import edu.kit.scc.webreg.exc.RegisterException; -import edu.kit.scc.webreg.exc.UserUpdateException; -import edu.kit.scc.webreg.hook.HookManager; -import edu.kit.scc.webreg.hook.UserServiceHook; -import edu.kit.scc.webreg.logging.LogHelper; -import edu.kit.scc.webreg.service.attribute.IncomingSamlAttributesHandler; -import edu.kit.scc.webreg.service.group.HomeOrgGroupUpdater; -import edu.kit.scc.webreg.service.identity.IdentityUpdater; -import edu.kit.scc.webreg.service.impl.AttributeMapHelper; -import edu.kit.scc.webreg.service.reg.impl.Registrator; -import edu.kit.scc.webreg.service.saml.AttributeQueryHelper; -import edu.kit.scc.webreg.service.saml.Saml2AssertionService; -import edu.kit.scc.webreg.service.saml.SamlHelper; -import edu.kit.scc.webreg.service.saml.exc.MetadataException; -import edu.kit.scc.webreg.service.saml.exc.NoAssertionException; -import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException; -import edu.kit.scc.webreg.service.saml.exc.SamlUnknownPrincipalException; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -@ApplicationScoped -public class UserUpdater extends AbstractUserUpdater<SamlUserEntity> { - - private static final long serialVersionUID = 1L; - - @Inject - private Logger logger; - - @Inject - private AuditEntryDao auditDao; - - @Inject - private AuditDetailDao auditDetailDao; - - @Inject - private Saml2AssertionService saml2AssertionService; - - @Inject - private AttributeQueryHelper attrQueryHelper; - - @Inject - private SamlUserDao userDao; - - @Inject - private ServiceDao serviceDao; - - @Inject - private RegistryDao registryDao; - - @Inject - private HomeOrgGroupUpdater homeOrgGroupUpdater; - - @Inject - private SamlHelper samlHelper; - - @Inject - private SamlIdpMetadataDao idpDao; - - @Inject - private SamlSpConfigurationDao spDao; - - @Inject - private SerialDao serialDao; - - @Inject - private HookManager hookManager; - - @Inject - private AttributeSourceDao attributeSourceDao; - - @Inject - private ASUserAttrDao asUserAttrDao; - - @Inject - private SamlAssertionDao samlAsserionDao; - - @Inject - private AttributeSourceUpdater attributeSourceUpdater; - - @Inject - private AttributeMapHelper attrHelper; - - @Inject - private EventSubmitter eventSubmitter; - - @Inject - private ApplicationConfig appConfig; - - @Inject - private Registrator registrator; - - @Inject - private IdentityUpdater identityUpdater; - - @Inject - private IncomingSamlAttributesHandler incomingAttributeHandler; - - @Inject - private LogHelper logHelper; - - @Override - public SamlUserEntity updateUser(SamlUserEntity user, Map<String, List<Object>> attributeMap, String executor, - StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - return updateUser(user, attributeMap, executor, null, debugLog, lastLoginHost); - } - - @Override - public SamlUserEntity updateUser(SamlUserEntity user, Map<String, List<Object>> attributeMap, String executor, - ServiceEntity service, StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - MDC.put("userId", "" + user.getId()); - logger.debug("Updating SAML user {}", user.getEppn()); - - boolean changed = false; - - UserUpdateAuditor auditor = new UserUpdateAuditor(auditDao, auditDetailDao, appConfig); - auditor.startAuditTrail(executor); - auditor.setName(getClass().getName() + "-UserUpdate-Audit"); - auditor.setDetail("Update user " + user.getEppn()); - - changed |= preUpdateUser(user, attributeMap, user.getIdp().getGenericStore(), executor, service, debugLog); - - // List to store parent services, that are not registered. Need to be registered - // later, when attribute map is populated - List<ServiceEntity> delayedRegisterList = new ArrayList<ServiceEntity>(); - - /** - * put no_assertion_count in generic store if assertion is missing. Else reset - * no assertion count and put last valid assertion date in - */ - if (attributeMap == null) { - if (!user.getGenericStore().containsKey("no_assertion_count")) { - user.getGenericStore().put("no_assertion_count", "1"); - } else { - user.getGenericStore().put("no_assertion_count", - "" + (Long.parseLong(user.getGenericStore().get("no_assertion_count")) + 1L)); - } - - logger.info("No attribute for user {}, skipping updateFromAttribute", user.getEppn()); - - user.getAttributeStore().clear(); - - // user empty attribute map in order to remove all existing values - IncomingAttributeSetEntity incomingAttributeSet = incomingAttributeHandler.createOrUpdateAttributes(user, new HashMap<>()); - incomingAttributeHandler.processIncomingAttributeSet(incomingAttributeSet); - - if (UserStatus.ACTIVE.equals(user.getUserStatus())) { - changeUserStatus(user, UserStatus.ON_HOLD, auditor); - - identityUpdater.updateIdentity(user); - - /* - * Also flag all registries for user ON_HOLD - */ - List<RegistryEntity> registryList = registryDao.findByUserAndStatus(user, RegistryStatus.ACTIVE, - RegistryStatus.LOST_ACCESS, RegistryStatus.INVALID); - for (RegistryEntity registry : registryList) { - changeRegistryStatus(registry, RegistryStatus.ON_HOLD, "user-on-hold", auditor); - } - } - } else { - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - user.getGenericStore().put("no_assertion_count", "0"); - user.getGenericStore().put("last_valid_assertion", df.format(new Date())); - - changed |= updateUserFromAttribute(user, attributeMap, auditor); - - if (UserStatus.ON_HOLD.equals(user.getUserStatus())) { - changeUserStatus(user, UserStatus.ACTIVE, auditor); - - /* - * Also reenable all registries for user to LOST_ACCESS. They are rechecked then - */ - List<RegistryEntity> registryList = registryDao.findByUserAndStatus(user, RegistryStatus.ON_HOLD); - for (RegistryEntity registry : registryList) { - changeRegistryStatus(registry, RegistryStatus.LOST_ACCESS, "user-reactivated", auditor); - - /* - * check if parent registry is missing - */ - if (registry.getService().getParentService() != null) { - List<RegistryEntity> parentRegistryList = registryDao.findByServiceAndIdentityAndNotStatus( - registry.getService().getParentService(), user.getIdentity(), RegistryStatus.DELETED, - RegistryStatus.DEPROVISIONED); - if (parentRegistryList.size() == 0) { - delayedRegisterList.add(registry.getService().getParentService()); - } - } - } - - /* - * fire a user changed event to be sure, when the user is activated - */ - changed = true; - } - - /* - * if service is set, update only attribute sources spcific for this service. - * Else update all (login via web or generic attribute query) - */ - if (service != null) { - service = serviceDao.find(equal(ServiceEntity_.id, service.getId()), - ServiceEntity_.attributeSourceService); - - for (AttributeSourceServiceEntity asse : service.getAttributeSourceService()) { - changed |= attributeSourceUpdater.updateUserAttributes(user, asse.getAttributeSource(), executor); - } - } else { - // find all user sources to update - Set<AttributeSourceEntity> asList = new HashSet<>(attributeSourceDao - .findAll(equal(AttributeSourceEntity_.userSource, true))); - // and add all sources which are already connected to the user - asList.addAll(asUserAttrDao.findAll(equal(ASUserAttrEntity_.user, user)).stream() - .map(a -> a.getAttributeSource()).toList()); - for (AttributeSourceEntity as : asList) { - changed |= attributeSourceUpdater.updateUserAttributes(user, as, executor); - } - } - - Set<GroupEntity> changedGroups = homeOrgGroupUpdater.updateGroupsForUser(user, attributeMap, auditor); - - if (changedGroups.size() > 0) { - changed = true; - } - - Map<String, String> attributeStore = user.getAttributeStore(); - attributeStore.clear(); - for (Entry<String, List<Object>> entry : attributeMap.entrySet()) { - attributeStore.put(entry.getKey(), attrHelper.attributeListToString(entry.getValue())); - } - - IncomingAttributeSetEntity incomingAttributeSet = incomingAttributeHandler.createOrUpdateAttributes(user, attributeMap); - incomingAttributeHandler.processIncomingAttributeSet(incomingAttributeSet); - - identityUpdater.updateIdentity(user); - - if (appConfig.getConfigValue("create_missing_eppn_scope") != null) { - if (user.getEppn() == null) { - String scope = appConfig.getConfigValue("create_missing_eppn_scope"); - user.setEppn(user.getIdentity().getGeneratedLocalUsername() + "@" + scope); - changed = true; - } - } - } - - for (ServiceEntity delayedService : delayedRegisterList) { - try { - registrator.registerUser(user, delayedService, "user-" + user.getId(), false); - } catch (RegisterException e) { - logger.warn("Parent registration didn't work out like it should", e); - } - } - - changed |= postUpdateUser(user, attributeMap, user.getIdp().getGenericStore(), executor, service, debugLog, - lastLoginHost); - - user.setLastUpdate(new Date()); - user.setLastFailedUpdate(null); - user.setScheduledUpdate(getNextScheduledUpdate()); - - if (changed) { - fireUserChangeEvent(user, auditor.getActualExecutor(), auditor); - } - - auditor.setUser(user); - auditor.finishAuditTrail(); - auditor.commitAuditTrail(); - - if (debugLog != null) { - AuditUserUpdateEntity audit = auditor.getAudit(); - debugLog.append("\n\nPrinting audit from user update process:\n\nName: ").append(audit.getName()) - .append("\nDetail: ").append(audit.getDetail()).append("\n"); - for (AuditDetailEntity detail : audit.getAuditDetails()) { - debugLog.append(detail.getEndTime()).append(" | ").append(detail.getSubject()).append(" | ") - .append(detail.getObject()).append(" | ").append(detail.getAction()).append(" | ") - .append(detail.getLog()).append(" | ").append(detail.getAuditStatus()).append("\n"); - } - - if (audit.getAuditDetails().size() == 0) { - debugLog.append("Nothing seems to have changed.\n"); - } - } - - return user; - } - - private Date getNextScheduledUpdate() { - Long futureMillis = 30L * 24L * 60L * 60L * 1000L; - if (appConfig.getConfigOptions().containsKey("update_schedule_future")) { - futureMillis = Long.decode(appConfig.getConfigValue("update_schedule_future")); - } - Integer futureMillisRandom = 6 * 60 * 60 * 1000; - if (appConfig.getConfigOptions().containsKey("update_schedule_future_random")) { - futureMillisRandom = Integer.decode(appConfig.getConfigValue("update_schedule_future_random")); - } - Random r = new Random(); - return new Date(System.currentTimeMillis() + futureMillis + r.nextInt(futureMillisRandom)); - } - - public SamlUserEntity updateUser(SamlUserEntity user, Assertion assertion, String executor, ServiceEntity service, - StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - - if (assertion != null) { - samlAsserionDao.deleteAssertionForUser(user); - - SamlAssertionEntity samlAssertionEntity = samlAsserionDao.createNew(); - samlAssertionEntity.setUser(user); - samlAssertionEntity.setAssertionData(samlHelper.prettyPrint(assertion)); - samlAssertionEntity.setValidUntil(new Date(System.currentTimeMillis() + (4L * 60L * 60L * 1000L))); - samlAssertionEntity = samlAsserionDao.persist(samlAssertionEntity); - } - - Map<String, List<Object>> attributeMap = saml2AssertionService.extractAttributes(assertion); - - if (debugLog != null) { - debugLog.append("Extracted attributes from Assertion:\n"); - for (Entry<String, List<Object>> entry : attributeMap.entrySet()) { - debugLog.append(entry.getKey()).append(":\t").append(entry.getValue()).append("\n"); - } - } - - if (service != null) - return updateUser(user, attributeMap, executor, service, debugLog, lastLoginHost); - else - return updateUser(user, attributeMap, executor, debugLog, lastLoginHost); - } - - public SamlUserEntity updateUser(SamlUserEntity user, Assertion assertion, String executor, String lastLoginHost) - throws UserUpdateException { - - return updateUser(user, assertion, executor, null, null, lastLoginHost); - } - - public SamlUserEntity updateUserFromIdp(SamlUserEntity user, String executor) throws UserUpdateException { - return updateUserFromIdp(user, null, executor, null); - } - - public SamlUserEntity updateUserFromIdp(SamlUserEntity user, ServiceEntity service, String executor, - StringBuffer debugLog) throws UserUpdateException { - - SamlSpConfigurationEntity spEntity = spDao.findByEntityId(user.getPersistentSpId()); - SamlIdpMetadataEntity idpEntity = idpDao.findByEntityId(user.getIdp().getEntityId()); - - IdpCommunicationAuditor auditor = new IdpCommunicationAuditor(auditDao, auditDetailDao, appConfig); - auditor.setName("UpdateUserFromIdp"); - auditor.setDetail("Call IDP " + idpEntity.getEntityId() + " from SP " + spEntity.getEntityId() + " for User " - + user.getEppn()); - auditor.setIdp(idpEntity); - auditor.setSpConfig(spEntity); - auditor.startAuditTrail(executor); - - EntityDescriptor idpEntityDescriptor = samlHelper.unmarshal(idpEntity.getEntityDescriptor(), - EntityDescriptor.class, auditor); - - Response samlResponse; - try { - /* - * If something goes wrong here, communication with the idp probably failed - */ - - samlResponse = attrQueryHelper.query(user, idpEntity, idpEntityDescriptor, spEntity, debugLog); - - if (logger.isTraceEnabled()) - logger.trace("{}", samlHelper.prettyPrint(samlResponse)); - - if (debugLog != null) { - debugLog.append("\nIncoming SAML Response:\n\n").append(samlHelper.prettyPrint(samlResponse)) - .append("\n"); - } - - } catch (SOAPException e) { - /* - * This exception is thrown if the certificate chain is incomplete e.g. - */ - handleException(user, e, idpEntity, auditor, debugLog); - throw new UserUpdateException(e); - } catch (MetadataException e) { - /* - * is thrown if AttributeQuery location is missing in metadata, or something is - * wrong with the sp certificate - */ - handleException(user, e, idpEntity, auditor, debugLog); - throw new UserUpdateException(e); - } catch (SecurityException e) { - handleException(user, e, idpEntity, auditor, debugLog); - throw new UserUpdateException(e); - } catch (Exception e) { - handleException(user, e, idpEntity, auditor, debugLog); - throw new UserUpdateException(e); - } - - try { - /* - * Don't check Assertion Signature, because we are contacting the IDP directly - */ - Assertion assertion; - try { - if (debugLog != null) { - debugLog.append("\nExtracting Assertion from SAML Response without signature check...\n"); - } - - assertion = saml2AssertionService.processSamlResponse(samlResponse, idpEntity, idpEntityDescriptor, - spEntity, false); - - if (logger.isTraceEnabled()) - logger.trace("{}", samlHelper.prettyPrint(assertion)); - - } catch (NoAssertionException e) { - if (user.getIdp() != null) - logger.warn("No assertion delivered for user {} from idp {}", user.getEppn(), - user.getIdp().getEntityId()); - else - logger.warn("No assertion delivered for user {} from idp {}", user.getEppn()); - assertion = null; - } catch (SamlUnknownPrincipalException e) { - if (user.getIdp() != null) - logger.warn("Unknown principal status for user {} from idp {}", user.getEppn(), - user.getIdp().getEntityId()); - else - logger.warn("Unknown principal status for user {}", user.getEppn()); - assertion = null; - } - - updateIdpStatus(SamlIdpMetadataEntityStatus.GOOD, idpEntity); - - return updateUser(user, assertion, "attribute-query", service, debugLog, null); - } catch (DecryptionException e) { - handleException(user, e, idpEntity, auditor, debugLog); - throw new UserUpdateException(e); - } catch (IOException e) { - handleException(user, e, idpEntity, auditor, debugLog); - throw new UserUpdateException(e); - } catch (SamlAuthenticationException e) { - /* - * Thrown if i.e. the AttributeQuery profile is not configured correctly - */ - handleException(user, e, idpEntity, auditor, debugLog); - throw new UserUpdateException(e); - } - } - - protected void handleException(SamlUserEntity user, Exception e, SamlIdpMetadataEntity idpEntity, Auditor auditor, - StringBuffer debugLog) { - updateFail(user); - String message = e.getMessage(); - if (e.getCause() != null) - message += " InnerCause: " + e.getCause().getMessage(); - auditor.logAction(idpEntity.getEntityId(), "SAML ATTRIBUTE QUERY", user.getEppn(), message, AuditStatus.FAIL); - auditor.finishAuditTrail(); - auditor.commitAuditTrail(); - - if (debugLog != null) { - debugLog.append("Attribute Query failed: ").append(e.getMessage()); - if (e.getCause() != null) - debugLog.append("Cause: ").append(e.getCause().getMessage()); - debugLog.append(logHelper.convertStacktrace(e)); - } - - updateIdpStatus(SamlIdpMetadataEntityStatus.FAULTY, idpEntity); - } - - protected void updateIdpStatus(SamlIdpMetadataEntityStatus status, SamlIdpMetadataEntity idpEntity) { - if (!status.equals(idpEntity.getAqIdpStatus())) { - idpEntity.setAqIdpStatus(status); - idpEntity.setLastAqStatusChange(new Date()); - } - } - - protected void updateFail(SamlUserEntity user) { - user.setLastFailedUpdate(new Date()); - user.setScheduledUpdate(getNextScheduledUpdate()); - user = userDao.persist(user); - } - - protected void fireUserChangeEvent(UserEntity user, String executor, Auditor auditor) { - - UserEvent userEvent = new UserEvent(user, auditor.getAudit()); - - try { - eventSubmitter.submit(userEvent, EventType.USER_UPDATE, executor); - } catch (EventSubmitException e) { - logger.warn("Could not submit event", e); - } - } - - public boolean updateUserNew(SamlUserEntity user, Map<String, List<Object>> attributeMap, String executor, - Auditor auditor, StringBuffer debugLog, String lastLoginHost) throws UserUpdateException { - boolean changed = false; - - changed |= preUpdateUser(user, attributeMap, user.getIdp().getGenericStore(), executor, null, debugLog); - changed |= updateUserFromAttribute(user, attributeMap, auditor); - changed |= postUpdateUser(user, attributeMap, user.getIdp().getGenericStore(), executor, null, debugLog, - lastLoginHost); - - return changed; - } - - public boolean updateUserFromAttribute(SamlUserEntity user, Map<String, List<Object>> attributeMap, Auditor auditor) - throws UserUpdateException { - return updateUserFromAttribute(user, attributeMap, false, auditor); - } - - public boolean updateUserFromAttribute(SamlUserEntity user, Map<String, List<Object>> attributeMap, - boolean withoutUidNumber, Auditor auditor) throws UserUpdateException { - - boolean changed = false; - - UserServiceHook completeOverrideHook = null; - Set<UserServiceHook> activeHooks = new HashSet<UserServiceHook>(); - - for (UserServiceHook hook : hookManager.getUserHooks()) { - if (hook.isResponsible(user, attributeMap)) { - - hook.preUpdateUserFromAttribute(user, attributeMap, auditor); - activeHooks.add(hook); - - if (hook.isCompleteOverride()) { - completeOverrideHook = hook; - } - } - } - - if (completeOverrideHook == null) { - changed |= compareAndChangeProperty(user, "email", attributeMap.get("urn:oid:0.9.2342.19200300.100.1.3"), - auditor); - changed |= compareAndChangeProperty(user, "eppn", attributeMap.get("urn:oid:1.3.6.1.4.1.5923.1.1.1.6"), - auditor); - changed |= compareAndChangeProperty(user, "givenName", attributeMap.get("urn:oid:2.5.4.42"), auditor); - changed |= compareAndChangeProperty(user, "surName", attributeMap.get("urn:oid:2.5.4.4"), auditor); - - List<String> emailList = attrHelper.attributeListToStringList(attributeMap, - "urn:oid:0.9.2342.19200300.100.1.3"); - if (emailList != null && emailList.size() > 1) { - - if (user.getEmailAddresses() == null) { - user.setEmailAddresses(new HashSet<String>()); - } - - for (int i = 1; i < emailList.size(); i++) { - user.getEmailAddresses().add(emailList.get(i)); - } - } - - if ((!withoutUidNumber) && (user.getUidNumber() == null)) { - user.setUidNumber(serialDao.nextUidNumber().intValue()); - logger.info("Setting UID Number {} for user {}", user.getUidNumber(), user.getEppn()); - auditor.logAction(user.getEppn(), "SET FIELD", "uidNumber", "" + user.getUidNumber(), - AuditStatus.SUCCESS); - changed = true; - } - } else { - logger.info("Overriding standard User Update Mechanism! Activator: {}", - completeOverrideHook.getClass().getName()); - } - - for (UserServiceHook hook : activeHooks) { - hook.postUpdateUserFromAttribute(user, attributeMap, auditor); - } - - return changed; - } - - private boolean compareAndChangeProperty(UserEntity user, String property, List<Object> objectValue, - Auditor auditor) { - String s = null; - String action = null; - - // In case of a List (multiple SAML Values), take the first value - String value = attrHelper.getSingleStringFirst(objectValue); - - try { - Object actualValue = PropertyUtils.getProperty(user, property); - - if (actualValue != null && actualValue.equals(value)) { - // Value didn't change, do nothing - return false; - } - - if (actualValue == null && value == null) { - // Value stayed null - return false; - } - - if (actualValue == null) { - s = "null"; - action = "SET FIELD"; - } else { - s = actualValue.toString(); - action = "UPDATE FIELD"; - } - - s = s + " -> " + value; - if (s.length() > 1017) - s = s.substring(0, 1017) + "..."; - - PropertyUtils.setProperty(user, property, value); - - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.SUCCESS); - } catch (IllegalAccessException e) { - logger.warn("This probably shouldn't happen: ", e); - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); - } catch (InvocationTargetException e) { - logger.warn("This probably shouldn't happen: ", e); - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); - } catch (NoSuchMethodException e) { - logger.warn("This probably shouldn't happen: ", e); - auditor.logAction(user.getEppn(), action, property, s, AuditStatus.FAIL); - } - - return true; - } - - protected void changeUserStatus(UserEntity user, UserStatus toStatus, Auditor auditor) { - UserStatus fromStatus = user.getUserStatus(); - user.setUserStatus(toStatus); - user.setLastStatusChange(new Date()); - - logger.debug("{}: change user status from {} to {}", user.getEppn(), fromStatus, toStatus); - auditor.logAction(user.getEppn(), "CHANGE STATUS", fromStatus + " -> " + toStatus, - "Change status " + fromStatus + " -> " + toStatus, AuditStatus.SUCCESS); - } - - protected void changeRegistryStatus(RegistryEntity registry, RegistryStatus toStatus, String statusMessage, - Auditor parentAuditor) { - RegistryStatus fromStatus = registry.getRegistryStatus(); - registry.setRegistryStatus(toStatus); - registry.setStatusMessage(statusMessage); - registry.setLastStatusChange(new Date()); - - logger.debug("{} {} {}: change registry status from {} to {}", new Object[] { registry.getUser().getEppn(), - registry.getService().getShortName(), registry.getId(), fromStatus, toStatus }); - RegistryAuditor registryAuditor = new RegistryAuditor(auditDao, auditDetailDao, appConfig); - registryAuditor.setParent(parentAuditor); - registryAuditor.startAuditTrail(parentAuditor.getActualExecutor()); - registryAuditor.setName(getClass().getName() + "-UserUpdate-Registry-Audit"); - registryAuditor.setDetail("Update registry " + registry.getId() + " for user " + registry.getUser().getEppn()); - registryAuditor.setRegistry(registry); - registryAuditor.logAction(registry.getUser().getEppn(), "CHANGE STATUS", "registry-" + registry.getId(), - "Change status " + fromStatus + " -> " + toStatus, AuditStatus.SUCCESS); - registryAuditor.finishAuditTrail(); - } -} +package edu.kit.scc.webreg.service.impl; + +import java.util.List; +import java.util.Map; + +import edu.kit.scc.webreg.entity.ServiceEntity; +import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.exc.UserUpdateException; + +public interface UserUpdater<T extends UserEntity> { + + public T updateUser(T user, Map<String, List<Object>> attributeMap, String executor, StringBuffer debugLog, String lastLoginHost) + throws UserUpdateException; + + public T updateUser(T user, Map<String, List<Object>> attributeMap, String executor, ServiceEntity service, StringBuffer debugLog, String lastLoginHost) + throws UserUpdateException; + + public T updateUserFromHomeOrg(T user, ServiceEntity service, String executor, + StringBuffer debugLog) throws UserUpdateException; + + public T expireUser(T user, String executor); +} diff --git a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/user/UserLifecycleManager.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/user/UserLifecycleManager.java new file mode 100644 index 0000000000000000000000000000000000000000..0a4d4a774277e0ed1b9cd6105b8090f93c82442d --- /dev/null +++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/user/UserLifecycleManager.java @@ -0,0 +1,106 @@ +package edu.kit.scc.webreg.service.user; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; + +import edu.kit.scc.regapp.mail.impl.TemplateMailSender; +import edu.kit.scc.webreg.dao.UserDao; +import edu.kit.scc.webreg.dao.audit.AuditDetailDao; +import edu.kit.scc.webreg.dao.audit.AuditEntryDao; +import edu.kit.scc.webreg.entity.SamlUserEntity; +import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.entity.oauth.OAuthUserEntity; +import edu.kit.scc.webreg.entity.oidc.OidcUserEntity; +import edu.kit.scc.webreg.event.EventSubmitter; +import edu.kit.scc.webreg.exc.UserUpdateException; +import edu.kit.scc.webreg.service.identity.IdentityScriptingEnv; +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 jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class UserLifecycleManager implements Serializable { + + private static final long serialVersionUID = 1L; + + @Inject + private Logger logger; + + @Inject + private AuditEntryDao auditDao; + + @Inject + private AuditDetailDao auditDetailDao; + + @Inject + private UserDao userDao; + + @Inject + private IdentityScriptingEnv scriptingEnv; + + @Inject + private TemplateMailSender mailService; + + @Inject + private EventSubmitter eventSubmitter; + + @Inject + private SamlUserUpdater userUpdater; + + @Inject + private OidcUserUpdater oidcUserUpdater; + + @Inject + private OAuthUserUpdater oauthUserUpdater; + + public void sendUserExpiryWarning(UserEntity user, String emailTemplateName) { + logger.debug("Trying to send expiry warning to user {} to e-mail address {}. First updating...", user.getId(), + user.getIdentity().getPrimaryEmail()); + + try { + if (user instanceof SamlUserEntity) { + user = userUpdater.updateUserFromHomeOrg((SamlUserEntity) user, null, "user-expire-job", null); + } else if (user instanceof OidcUserEntity) { + user = oidcUserUpdater.updateUserFromHomeOrg((OidcUserEntity) user, null, "user-expire-job", null); + } else if (user instanceof OAuthUserEntity) { + user = oauthUserUpdater.updateUserFromHomeOrg((OAuthUserEntity) user, null, "user-expire-job", null); + } + + logger.info("Update didn't fail. Don't send expiry warning to user"); + } catch (UserUpdateException e) { + logger.debug("Update failed, sending expiry warning to user {} to e-mail address {}", user.getId(), + user.getIdentity().getPrimaryEmail()); + sendMail(user, emailTemplateName); + user.setExpireWarningSent(new Date()); + } + } + + public void expireUser(UserEntity user, String emailTemplateName) { + logger.debug("Trying to expire user {} with e-mail address {}", user.getId(), + user.getIdentity().getPrimaryEmail()); + + if (user instanceof SamlUserEntity) { + user = userUpdater.expireUser((SamlUserEntity) user, "user-expire-job"); + } else if (user instanceof OidcUserEntity) { + user = oidcUserUpdater.expireUser((OidcUserEntity) user, "user-expire-job"); + } else if (user instanceof OAuthUserEntity) { + user = oauthUserUpdater.expireUser((OAuthUserEntity) user, "user-expire-job"); + } + + sendMail(user, emailTemplateName); + user.setExpiredSent(new Date()); + } + + private void sendMail(UserEntity user, String emailTemplateName) { + Map<String, Object> context = new HashMap<String, Object>(2); + context.put("user", user); + context.put("identity", user.getIdentity()); + mailService.sendMail(emailTemplateName, context, true); + } +} diff --git a/regapp-mail/src/main/java/edu/kit/scc/regapp/mail/impl/TemplateMailSender.java b/regapp-mail/src/main/java/edu/kit/scc/regapp/mail/impl/TemplateMailSender.java index c36c21819d7119866f3ff08eee3ed6b7cd6f25b5..6d92364e1e74cff9f78e8cff9b3f9b11dd09697d 100644 --- a/regapp-mail/src/main/java/edu/kit/scc/regapp/mail/impl/TemplateMailSender.java +++ b/regapp-mail/src/main/java/edu/kit/scc/regapp/mail/impl/TemplateMailSender.java @@ -78,6 +78,7 @@ public class TemplateMailSender { rendererContext.put("user", user); } } + } else if (rendererContext.containsKey("user")) { UserEntity user = userDao.fetch(((UserEntity) rendererContext.get("user")).getId()); rendererContext.putAll(userPrefsResolver.resolvePrefs(user.getIdentity())); diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcOpMetadataSingletonBean.java b/regapp-oidc/src/main/java/edu/kit/scc/regapp/oidc/tools/OidcOpMetadataSingletonBean.java similarity index 96% rename from bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcOpMetadataSingletonBean.java rename to regapp-oidc/src/main/java/edu/kit/scc/regapp/oidc/tools/OidcOpMetadataSingletonBean.java index c7e51c32207b711ede1ef8175bd9e9b6f2c73193..f36542d7eb24200893b71b15ad8a0035a815a9f3 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/oidc/client/OidcOpMetadataSingletonBean.java +++ b/regapp-oidc/src/main/java/edu/kit/scc/regapp/oidc/tools/OidcOpMetadataSingletonBean.java @@ -1,13 +1,10 @@ -package edu.kit.scc.webreg.service.oidc.client; +package edu.kit.scc.regapp.oidc.tools; import java.io.IOException; import java.net.URI; import java.util.HashMap; import java.util.Map; -import jakarta.ejb.Singleton; -import jakarta.inject.Inject; - import org.slf4j.Logger; import com.nimbusds.oauth2.sdk.ParseException; @@ -17,8 +14,10 @@ import com.nimbusds.openid.connect.sdk.op.OIDCProviderConfigurationRequest; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import edu.kit.scc.webreg.entity.oidc.OidcRpConfigurationEntity; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; -@Singleton +@ApplicationScoped public class OidcOpMetadataSingletonBean { @Inject diff --git a/regapp-tools/src/main/java/edu/kit/scc/webreg/tools/IdentityUserPrefsResolver.java b/regapp-tools/src/main/java/edu/kit/scc/webreg/tools/IdentityUserPrefsResolver.java index ca6c9ffbd36b80fe9286ba57643272b6e94c5076..e54d7015b582a75ff3813fa1649b409bf3428bdf 100644 --- a/regapp-tools/src/main/java/edu/kit/scc/webreg/tools/IdentityUserPrefsResolver.java +++ b/regapp-tools/src/main/java/edu/kit/scc/webreg/tools/IdentityUserPrefsResolver.java @@ -50,6 +50,11 @@ public class IdentityUserPrefsResolver { } } + if (identity.getPrimaryEmail() != null) { + // if a primary e-mail address is set, use it + prefsMap.put("email", identity.getPrimaryEmail().getEmailAddress()); + } + return prefsMap; }