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 f6ee027a6dcf0c1b00c4a02de423f525ab3d6e98..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;
@@ -81,7 +81,7 @@ public class UserCreateServiceImpl implements UserCreateService {
 	private SamlUserUpdater userUpdater;
 
 	@Inject
-	private HomeOrgGroupUpdater homeOrgGroupUpdater;
+	private SamlGroupUpdater homeOrgGroupUpdater;
 
 	@Inject
 	private RoleDao roleDao;
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/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OAuthGroupUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OAuthGroupUpdater.java
index bc3c5c7eece00b9cd408830600eb4589a811378f..1607d2ecd9b649476d18f6ad23a116c489bcd1d0 100644
--- a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OAuthGroupUpdater.java
+++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OAuthGroupUpdater.java
@@ -34,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/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OidcGroupUpdater.java b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OidcGroupUpdater.java
index ee3454edf4c6fe3dfc3d709ad5cb37c895d9bfda..f259fc869e6e546c8b80507b2b8dfead7c34c4c9 100644
--- a/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OidcGroupUpdater.java
+++ b/regapp-idty/src/main/java/edu/kit/scc/webreg/service/group/OidcGroupUpdater.java
@@ -34,7 +34,9 @@ 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 2661a5a1efd42c7b002779e97dd5c646bbcb19e0..0afb7410654da1da1997a3fb37a7bbd81aafbc7d 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,21 +1,64 @@
 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 UserUpdater<T>, Serializable {
 
@@ -24,49 +67,217 @@ public abstract class AbstractUserUpdater<T extends UserEntity> implements UserU
 	@Inject
 	private Logger logger;
 
+	@Inject
+	private IdentityUpdater identityUpdater;
+
 	@Inject
 	private IdentityScriptingEnv scriptingEnv;
+
+	@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 abstract T updateUser(T user, Map<String, List<Object>> attributeMap, String executor, StringBuffer debugLog, String lastLoginHost)
-			throws UserUpdateException;
+	public boolean updateUserFromAttribute(T user, Map<String, List<Object>> attributeMap, Auditor auditor)
+			throws UserUpdateException {
+		return updateUserFromAttribute(user, attributeMap, false, auditor);
+	}
+
+	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.getEppn());
+
+		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);
+
+			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);
+			}
+		}
 
-	public abstract T updateUser(T user, Map<String, List<Object>> attributeMap, String executor, ServiceEntity service, StringBuffer debugLog, String lastLoginHost)
-			throws UserUpdateException;
+		changed |= postUpdateUser(user, attributeMap, resolveHomeOrgGenericStore(user), executor, service, debugLog,
+				lastLoginHost);
 
-	protected boolean preUpdateUser(UserEntity user, Map<String, List<Object>> attributeMap, Map<String,String> homeOrgGenericStore, 
-				String executor, ServiceEntity service, StringBuffer debugLog)
+		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 +285,7 @@ public abstract class AbstractUserUpdater<T extends UserEntity> implements UserU
 		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 +300,151 @@ public abstract class AbstractUserUpdater<T extends UserEntity> implements UserU
 				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
index ef9fb8ec8a8156d6e799d73efd1bc652c962b5c7..6c816bcd77e8dc49eac51ddcdc60ec544f445f8a 100644
--- 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
@@ -1,56 +1,30 @@
 package edu.kit.scc.webreg.service.impl;
 
 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.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.SerialDao;
-import edu.kit.scc.webreg.dao.ServiceDao;
-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.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.attribute.IncomingOAuthAttributeEntity;
 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.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 edu.kit.scc.webreg.service.identity.IdentityUpdater;
-import edu.kit.scc.webreg.service.reg.impl.Registrator;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 
@@ -62,21 +36,6 @@ public class OAuthUserUpdater extends AbstractUserUpdater<OAuthUserEntity> {
 	@Inject
 	private Logger logger;
 
-	@Inject
-	private AuditEntryDao auditDao;
-
-	@Inject
-	private AuditDetailDao auditDetailDao;
-
-	@Inject
-	private OAuthUserDao userDao;
-
-	@Inject
-	private ServiceDao serviceDao;
-
-	@Inject
-	private RegistryDao registryDao;
-
 	@Inject
 	private SerialDao serialDao;
 
@@ -86,27 +45,6 @@ public class OAuthUserUpdater extends AbstractUserUpdater<OAuthUserEntity> {
 	@Inject
 	private OAuthGroupUpdater oauthGroupUpdater;
 
-	@Inject
-	private ASUserAttrDao asUserAttrDao;
-
-	@Inject
-	private AttributeSourceUpdater attributeSourceUpdater;
-
-	@Inject
-	private EventSubmitter eventSubmitter;
-
-	@Inject
-	private ApplicationConfig appConfig;
-
-	@Inject
-	private Registrator registrator;
-
-	@Inject
-	private AttributeMapHelper attrHelper;
-
-	@Inject
-	private IdentityUpdater identityUpdater;
-
 	@Inject
 	private IncomingOAuthAttributesHandler incomingAttributeHandler;
 	
@@ -120,198 +58,6 @@ public class OAuthUserUpdater extends AbstractUserUpdater<OAuthUserEntity> {
 		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 = serviceDao.fetch(service.getId());
-
-				for (AttributeSourceServiceEntity asse : service.getAttributeSourceService()) {
-					changed |= attributeSourceUpdater.updateUserAttributes(user, asse.getAttributeSource(),
-							executor);
-				}
-			} else {
-				List<ASUserAttrEntity> asUserAttrList = asUserAttrDao.findForUser(user);
-				for (ASUserAttrEntity asUserAttr : asUserAttrList) {
-					changed |= attributeSourceUpdater.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.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 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;
@@ -324,12 +70,7 @@ public class OAuthUserUpdater extends AbstractUserUpdater<OAuthUserEntity> {
 		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,
+	public boolean updateUserFromAttribute(OAuthUserEntity user, Map<String, List<Object>> attributeMap,
 			boolean withoutUidNumber, Auditor auditor) throws UserUpdateException {
 
 		boolean changed = false;
@@ -428,51 +169,29 @@ public class OAuthUserUpdater extends AbstractUserUpdater<OAuthUserEntity> {
 		return true;
 	}
 
-	protected void changeUserStatus(UserEntity user, UserStatus toStatus, Auditor auditor) {
-		UserStatus fromStatus = user.getUserStatus();
-		user.setUserStatus(toStatus);
-		user.setLastStatusChange(new Date());
+	protected void updateFail(OAuthUserEntity user) {
+		user.setLastFailedUpdate(new Date());
+		user.setScheduledUpdate(getNextScheduledUpdate());
+	}
 
-		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);
+	@Override
+	public OAuthUserEntity expireUser(OAuthUserEntity user) throws UserUpdateException {
+		// TODO Auto-generated method stub
+		return null;
 	}
 
-	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();
+	@Override
+	public HomeOrgGroupUpdater<OAuthUserEntity> getGroupUpdater() {
+		return oauthGroupUpdater;
 	}
 
-	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));
+	@Override
+	public Map<String, String> resolveHomeOrgGenericStore(OAuthUserEntity user) {
+		return user.getOauthIssuer().getGenericStore();
 	}
 
-	protected void updateFail(OAuthUserEntity user) {
-		user.setLastFailedUpdate(new Date());
-		user.setScheduledUpdate(getNextScheduledUpdate());
+	@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
index f8de47079c5af54ae83ecf8da19038cf7d2da47b..9103738f25c3c8f3319fbad3343d92778cf8305c 100644
--- 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
@@ -2,19 +2,14 @@ package edu.kit.scc.webreg.service.impl;
 
 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;
@@ -46,42 +41,21 @@ 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.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.SerialDao;
-import edu.kit.scc.webreg.dao.ServiceDao;
-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.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.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.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.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 edu.kit.scc.webreg.service.identity.IdentityUpdater;
-import edu.kit.scc.webreg.service.reg.impl.Registrator;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 
@@ -93,21 +67,6 @@ public class OidcUserUpdater extends AbstractUserUpdater<OidcUserEntity> {
 	@Inject
 	private Logger logger;
 
-	@Inject
-	private AuditEntryDao auditDao;
-
-	@Inject
-	private AuditDetailDao auditDetailDao;
-
-	@Inject
-	private OidcUserDao userDao;
-
-	@Inject
-	private ServiceDao serviceDao;
-
-	@Inject
-	private RegistryDao registryDao;
-
 	@Inject
 	private SerialDao serialDao;
 
@@ -117,33 +76,12 @@ public class OidcUserUpdater extends AbstractUserUpdater<OidcUserEntity> {
 	@Inject
 	private OidcGroupUpdater oidcGroupUpdater;
 
-	@Inject
-	private ASUserAttrDao asUserAttrDao;
-
-	@Inject
-	private AttributeSourceUpdater attributeSourceUpdater;
-
-	@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;
 
@@ -248,167 +186,6 @@ public class OidcUserUpdater extends AbstractUserUpdater<OidcUserEntity> {
 		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 = serviceDao.fetch(service.getId());
-
-				for (AttributeSourceServiceEntity asse : service.getAttributeSourceService()) {
-					changed |= attributeSourceUpdater.updateUserAttributes(user, asse.getAttributeSource(),
-							executor);
-				}
-			} else {
-				List<ASUserAttrEntity> asUserAttrList = asUserAttrDao.findForUser(user);
-				for (ASUserAttrEntity asUserAttr : asUserAttrList) {
-					changed |= attributeSourceUpdater.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 {
@@ -429,17 +206,6 @@ public class OidcUserUpdater extends AbstractUserUpdater<OidcUserEntity> {
 		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;
@@ -452,12 +218,7 @@ public class OidcUserUpdater extends AbstractUserUpdater<OidcUserEntity> {
 		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,
+	public boolean updateUserFromAttribute(OidcUserEntity user, Map<String, List<Object>> attributeMap,
 			boolean withoutUidNumber, Auditor auditor) throws UserUpdateException {
 
 		boolean changed = false;
@@ -559,51 +320,29 @@ public class OidcUserUpdater extends AbstractUserUpdater<OidcUserEntity> {
 		return true;
 	}
 
-	protected void changeUserStatus(UserEntity user, UserStatus toStatus, Auditor auditor) {
-		UserStatus fromStatus = user.getUserStatus();
-		user.setUserStatus(toStatus);
-		user.setLastStatusChange(new Date());
+	protected void updateFail(OidcUserEntity user) {
+		user.setLastFailedUpdate(new Date());
+		user.setScheduledUpdate(getNextScheduledUpdate());
+	}
 
-		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);
+	@Override
+	public OidcUserEntity expireUser(OidcUserEntity user) throws UserUpdateException {
+		// TODO Auto-generated method stub
+		return null;
 	}
 
-	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();
+	@Override
+	public HomeOrgGroupUpdater<OidcUserEntity> getGroupUpdater() {
+		return oidcGroupUpdater;
 	}
 
-	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));
+	@Override
+	public Map<String, String> resolveHomeOrgGenericStore(OidcUserEntity user) {
+		return user.getIssuer().getGenericStore();
 	}
 
-	protected void updateFail(OidcUserEntity user) {
-		user.setLastFailedUpdate(new Date());
-		user.setScheduledUpdate(getNextScheduledUpdate());
+	@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
index 88709b639beb2f5eb530b0e773f24e68ed513dc5..33eff071799972b393dcca50576c21013bdf5614 100644
--- 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
@@ -1,18 +1,12 @@
 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;
@@ -22,58 +16,34 @@ 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.attribute.IncomingSamlAttributeEntity;
 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.IncomingAttributesHandler;
 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.reg.impl.Registrator;
+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;
@@ -108,13 +78,7 @@ public class SamlUserUpdater extends AbstractUserUpdater<SamlUserEntity> {
 	private SamlUserDao userDao;
 
 	@Inject
-	private ServiceDao serviceDao;
-
-	@Inject
-	private RegistryDao registryDao;
-
-	@Inject
-	private HomeOrgGroupUpdater homeOrgGroupUpdater;
+	private SamlGroupUpdater homeOrgGroupUpdater;
 
 	@Inject
 	private SamlHelper samlHelper;
@@ -132,238 +96,19 @@ public class SamlUserUpdater extends AbstractUserUpdater<SamlUserEntity> {
 	private HookManager hookManager;
 
 	@Inject
-	private AttributeSourceDao attributeSourceDao;
-
-	@Inject
-	private ASUserAttrDao asUserAttrDao;
+	private IncomingSamlAttributesHandler incomingAttributeHandler;
 
 	@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 ApplicationConfig appConfig;
 
-	@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.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;
-	}
-
-	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 {
 
@@ -539,16 +284,7 @@ public class SamlUserUpdater extends AbstractUserUpdater<SamlUserEntity> {
 		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 {
@@ -562,11 +298,6 @@ public class SamlUserUpdater extends AbstractUserUpdater<SamlUserEntity> {
 		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 {
 
@@ -677,33 +408,24 @@ public class SamlUserUpdater extends AbstractUserUpdater<SamlUserEntity> {
 		return true;
 	}
 
-	protected void changeUserStatus(UserEntity user, UserStatus toStatus, Auditor auditor) {
-		UserStatus fromStatus = user.getUserStatus();
-		user.setUserStatus(toStatus);
-		user.setLastStatusChange(new Date());
+	@Override
+	public SamlUserEntity expireUser(SamlUserEntity user) throws UserUpdateException {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public HomeOrgGroupUpdater<SamlUserEntity> getGroupUpdater() {
+		return homeOrgGroupUpdater;
+	}
 
-		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);
+	@Override
+	public Map<String, String> resolveHomeOrgGenericStore(SamlUserEntity user) {
+		return user.getIdp().getGenericStore();
 	}
 
-	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();
+	@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 5a943e3bd0582f74d14d0e108a4efa5290ff492e..858a3e714713e181cfa5b8fc6ee8dd96bcb86db4 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
@@ -18,4 +18,5 @@ public interface UserUpdater<T extends UserEntity> {
 	public T updateUserFromHomeOrg(T user, ServiceEntity service, String executor,
 			StringBuffer debugLog) throws UserUpdateException;
 
+	public T expireUser(T user) throws UserUpdateException;
 }