diff --git a/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java index acb8fe32c..37ad6517d 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java +++ b/dmp-backend/core/src/main/java/eu/eudat/authorization/AuthorizationProperties.java @@ -9,6 +9,25 @@ import java.util.List; @ConfigurationProperties(prefix = "authorization") public class AuthorizationProperties { + private String globalAdminRole; + + public String getGlobalAdminRole() { + return globalAdminRole; + } + + public void setGlobalAdminRole(String globalAdminRole) { + this.globalAdminRole = globalAdminRole; + } + private Boolean autoAssignGlobalAdminToNewTenants; + + public Boolean getAutoAssignGlobalAdminToNewTenants() { + return autoAssignGlobalAdminToNewTenants; + } + + public void setAutoAssignGlobalAdminToNewTenants(Boolean autoAssignGlobalAdminToNewTenants) { + this.autoAssignGlobalAdminToNewTenants = autoAssignGlobalAdminToNewTenants; + } + private List allowedTenantRoles; public List getAllowedTenantRoles() { diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakAuthorityProperties.java b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakAuthorityProperties.java index 3c8cd8bb1..b4be762c2 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakAuthorityProperties.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakAuthorityProperties.java @@ -1,22 +1,18 @@ package eu.eudat.service.keycloak; -import org.springframework.boot.context.properties.bind.ConstructorBinding; - public class KeycloakAuthorityProperties { - private final String groupId, groupTitle; + private String groupId; - @ConstructorBinding - public KeycloakAuthorityProperties(String groupId, String groupTitle) { - this.groupId = groupId; - this.groupTitle = groupTitle; + public KeycloakAuthorityProperties() { } public String getGroupId() { return groupId; } - public String getGroupTitle() { - return groupTitle; + public void setGroupId(String groupId) { + this.groupId = groupId; } } + diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakResourcesConfiguration.java b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakResourcesConfiguration.java index d74493419..202930f43 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakResourcesConfiguration.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakResourcesConfiguration.java @@ -19,4 +19,14 @@ public class KeycloakResourcesConfiguration { return properties; } + public String getTenantGroupName(String tenantCode) { + return properties.getTenantGroupsNamingStrategy() + .replace("{tenantCode}", tenantCode); + } + + public String getTenantRoleAttributeValue(String tenantCode, String key) { + return properties.getTenantAuthorities().get(key).getRoleAttributeValueStrategy() + .replace("{tenantCode}", tenantCode); + } + } diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakResourcesProperties.java b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakResourcesProperties.java index bf5aaa4a5..41c8d83a8 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakResourcesProperties.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakResourcesProperties.java @@ -10,15 +10,41 @@ import java.util.HashMap; @ConditionalOnProperty(prefix = "keycloak-resources", name = "enabled", havingValue = "true") public class KeycloakResourcesProperties { - private final HashMap authorities; - - @ConstructorBinding - public KeycloakResourcesProperties(HashMap authorities) { - this.authorities = authorities; - } + private HashMap authorities; + private HashMap tenantAuthorities; + + private String tenantGroupsNamingStrategy; + private String tenantRoleAttributeName; public HashMap getAuthorities() { return authorities; } + public void setAuthorities(HashMap authorities) { + this.authorities = authorities; + } + + public HashMap getTenantAuthorities() { + return tenantAuthorities; + } + + public void setTenantAuthorities(HashMap tenantAuthorities) { + this.tenantAuthorities = tenantAuthorities; + } + + public String getTenantGroupsNamingStrategy() { + return tenantGroupsNamingStrategy; + } + + public void setTenantGroupsNamingStrategy(String tenantGroupsNamingStrategy) { + this.tenantGroupsNamingStrategy = tenantGroupsNamingStrategy; + } + + public String getTenantRoleAttributeName() { + return tenantRoleAttributeName; + } + + public void setTenantRoleAttributeName(String tenantRoleAttributeName) { + this.tenantRoleAttributeName = tenantRoleAttributeName; + } } diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakService.java b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakService.java index 89be4440c..7c10b622e 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakService.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakService.java @@ -1,17 +1,23 @@ package eu.eudat.service.keycloak; import org.jetbrains.annotations.NotNull; +import org.keycloak.representations.idm.GroupRepresentation; import java.util.HashMap; import java.util.UUID; public interface KeycloakService { - void addUserToGroup(UUID subjectId, String groupId); - void removeUserFromGroup(@NotNull UUID subjectId, String groupId); - void addUserToGroup(UUID subjectId, KeycloakRole role); - void removeUserFromGroup(@NotNull UUID subjectId, KeycloakRole role); - void assignClientRoleToUser(UUID subjectId, String clientId, KeycloakRole role); - void removeClientRoleFromUser(UUID subjectId, String clientId, KeycloakRole role); + void addUserToGroup(@NotNull String subjectId, String groupId); + void removeUserFromGroup(@NotNull String subjectId, String groupId); + + void addUserToGlobalRoleGroup(String subjectId, String role); + void removeUserGlobalRoleGroup(@NotNull String subjectId, String role); + + void addUserToTenantRoleGroup(String subjectId, String tenantCode, String tenantRole); + + void removeUserTenantRoleGroup(String subjectId, String tenantCode, String tenantRole); + + void createTenantGroups(String tenantCode); } diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakServiceImpl.java index 1ae69554f..09b4fc4e7 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakServiceImpl.java @@ -4,7 +4,6 @@ import gr.cite.commons.web.keycloak.api.configuration.KeycloakClientConfiguratio import gr.cite.tools.logging.LoggerService; import org.jetbrains.annotations.NotNull; import org.keycloak.representations.idm.GroupRepresentation; -import org.keycloak.representations.idm.UserRepresentation; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -27,56 +26,83 @@ public class KeycloakServiceImpl implements KeycloakService { } @Override - public void addUserToGroup(@NotNull UUID subjectId, String groupId) { - api.users().addUserToGroup(subjectId.toString(), groupId); + public void addUserToGroup(@NotNull String subjectId, String groupId) { + api.users().addUserToGroup(subjectId, groupId); } @Override - public void removeUserFromGroup(@NotNull UUID subjectId, String groupId) { - api.users().removeUserFromGroup(subjectId.toString(), groupId); + public void removeUserFromGroup(@NotNull String subjectId, String groupId) { + api.users().removeUserFromGroup(subjectId, groupId); } @Override - public void addUserToGroup(UUID subjectId, KeycloakRole role) { - KeycloakAuthorityProperties properties = this.configuration.getProperties().getAuthorities().get(role.name()); - if (properties != null) - addUserToGroup(subjectId, properties.getGroupId()); + public void addUserToGlobalRoleGroup(String subjectId, String role) { + KeycloakAuthorityProperties properties = this.configuration.getProperties().getAuthorities().get(role); + if (properties != null) addUserToGroup(subjectId, properties.getGroupId()); } @Override - public void removeUserFromGroup(@NotNull UUID subjectId, KeycloakRole role) { - KeycloakAuthorityProperties properties = this.configuration.getProperties().getAuthorities().get(role.name()); + public void removeUserGlobalRoleGroup(@NotNull String subjectId, String role) { + KeycloakAuthorityProperties properties = this.configuration.getProperties().getAuthorities().get(role); if (properties != null) removeUserFromGroup(subjectId, properties.getGroupId()); } +// +// @Override +// public void assignClientRoleToUser(UUID subjectId, String clientId, KeycloakRole role) { +// if (clientId == null) +// clientId = clientConfiguration.getProperties().getClientId(); +// UserRepresentation user = api.users().findUserById(subjectId.toString()); +// user.getClientRoles().computeIfAbsent(clientId, k -> new ArrayList<>()); +// Set clientRoles = new HashSet<>(Set.copyOf(user.getClientRoles().get(clientId))); +// clientRoles.add(role.name()); +// user.getClientRoles().get(clientId).clear(); +// user.getClientRoles().get(clientId).addAll(clientRoles); +// api.users().updateUser(subjectId.toString(), user); +// } +// +// @Override +// public void removeClientRoleFromUser(UUID subjectId, String clientId, KeycloakRole role) { +// if (clientId == null) clientId = clientConfiguration.getProperties().getClientId(); +// UserRepresentation user = api.users().findUserById(subjectId.toString()); +// user.getClientRoles().computeIfAbsent(clientId, k -> new ArrayList<>()); +// Set clientRoles = new HashSet<>(Set.copyOf(user.getClientRoles().get(clientId))); +// clientRoles.remove(role.name()); +// user.getClientRoles().get(clientId).clear(); +// user.getClientRoles().get(clientId).addAll(clientRoles); +// api.users().updateUser(subjectId.toString(), user); +// } +// +// public List getUserGroups(UUID subjectId) { +// return api.users().getGroups(subjectId.toString()); +// } @Override - public void assignClientRoleToUser(UUID subjectId, String clientId, KeycloakRole role) { - if (clientId == null) - clientId = clientConfiguration.getProperties().getClientId(); - UserRepresentation user = api.users().findUserById(subjectId.toString()); - user.getClientRoles().computeIfAbsent(clientId, k -> new ArrayList<>()); - Set clientRoles = new HashSet<>(Set.copyOf(user.getClientRoles().get(clientId))); - clientRoles.add(role.name()); - user.getClientRoles().get(clientId).clear(); - user.getClientRoles().get(clientId).addAll(clientRoles); - api.users().updateUser(subjectId.toString(), user); + public void addUserToTenantRoleGroup(String subjectId, String tenantCode, String tenantRole) { + GroupRepresentation group = api.groups().findGroupByPath(getTenantAuthorityParentPath(tenantRole) + "/" + configuration.getTenantGroupName(tenantCode)); + addUserToGroup(subjectId, group.getId()); } @Override - public void removeClientRoleFromUser(UUID subjectId, String clientId, KeycloakRole role) { - if (clientId == null) clientId = clientConfiguration.getProperties().getClientId(); - UserRepresentation user = api.users().findUserById(subjectId.toString()); - user.getClientRoles().computeIfAbsent(clientId, k -> new ArrayList<>()); - Set clientRoles = new HashSet<>(Set.copyOf(user.getClientRoles().get(clientId))); - clientRoles.remove(role.name()); - user.getClientRoles().get(clientId).clear(); - user.getClientRoles().get(clientId).addAll(clientRoles); - api.users().updateUser(subjectId.toString(), user); + public void removeUserTenantRoleGroup(String subjectId, String tenantCode, String tenantRole) { + GroupRepresentation group = api.groups().findGroupByPath(getTenantAuthorityParentPath(tenantRole) + "/" + configuration.getTenantGroupName(tenantCode)); + removeUserFromGroup(subjectId, group.getId()); + } + + private String getTenantAuthorityParentPath(String tenantRole) { + GroupRepresentation parent = api.groups().findGroupById(configuration.getProperties().getTenantAuthorities().get(tenantRole).getParent()); + return parent.getPath(); } - public List getUserGroups(UUID subjectId) { - return api.users().getGroups(subjectId.toString()); + @Override + public void createTenantGroups(String tenantCode) { + for (Map.Entry entry :configuration.getProperties().getTenantAuthorities().entrySet()){ + GroupRepresentation group = new GroupRepresentation(); + group.setName(configuration.getTenantGroupName(tenantCode)); + HashMap> user_attributes = new HashMap<>(); + user_attributes.put(this.configuration.getProperties().getTenantRoleAttributeName(), List.of(configuration.getTenantRoleAttributeValue(tenantCode, entry.getKey()))); + group.setAttributes(user_attributes); + api.groups().addGroupWithParent(group, configuration.getProperties().getTenantAuthorities().get(entry.getKey()).getParent()); + } } - } diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakTenantAuthorityProperties.java b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakTenantAuthorityProperties.java index e69de29bb..520d7755f 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakTenantAuthorityProperties.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakTenantAuthorityProperties.java @@ -0,0 +1,26 @@ +package eu.eudat.service.keycloak; + +public class KeycloakTenantAuthorityProperties { + + private String parent; + private String roleAttributeValueStrategy; + + public KeycloakTenantAuthorityProperties() { + } + + public String getParent() { + return parent; + } + + public void setParent(String parent) { + this.parent = parent; + } + + public String getRoleAttributeValueStrategy() { + return roleAttributeValueStrategy; + } + + public void setRoleAttributeValueStrategy(String roleAttributeValueStrategy) { + this.roleAttributeValueStrategy = roleAttributeValueStrategy; + } +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/tenant/TenantServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/tenant/TenantServiceImpl.java index 63428733a..fb59f08a8 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/tenant/TenantServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/tenant/TenantServiceImpl.java @@ -1,25 +1,34 @@ package eu.eudat.service.tenant; import eu.eudat.authorization.AuthorizationFlags; +import eu.eudat.authorization.AuthorizationProperties; import eu.eudat.authorization.Permission; import eu.eudat.commons.XmlHandlingService; import eu.eudat.commons.enums.IsActive; +import eu.eudat.commons.scope.tenant.TenantScope; import eu.eudat.commons.types.tenant.*; import eu.eudat.convention.ConventionService; import eu.eudat.data.TenantEntity; import eu.eudat.data.TenantEntityManager; +import eu.eudat.data.UserCredentialEntity; +import eu.eudat.data.UserRoleEntity; import eu.eudat.errorcode.ErrorThesaurusProperties; import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEvent; import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEventHandler; import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEvent; import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEventHandler; import eu.eudat.model.Tenant; +import eu.eudat.model.UserCredential; import eu.eudat.model.builder.TenantBuilder; import eu.eudat.model.deleter.TenantDeleter; +import eu.eudat.model.deleter.UserRoleDeleter; import eu.eudat.model.persist.TenantPersist; import eu.eudat.model.persist.tenantconfig.*; import eu.eudat.model.tenantconfig.TenantSource; +import eu.eudat.query.UserCredentialQuery; +import eu.eudat.query.UserRoleQuery; import eu.eudat.service.encryption.EncryptionService; +import eu.eudat.service.keycloak.KeycloakService; import eu.eudat.service.responseutils.ResponseUtilsService; import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.BuilderFactory; @@ -73,6 +82,11 @@ public class TenantServiceImpl implements TenantService { private final TenantProperties properties; private final TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler; private final TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler; + private final KeycloakService keycloakService; + private final AuthorizationProperties authorizationProperties; + private final TenantScope tenantScope; + private final QueryFactory queryFactory; + @Autowired public TenantServiceImpl( @@ -84,7 +98,7 @@ public class TenantServiceImpl implements TenantService { MessageSource messageSource, XmlHandlingService xmlHandlingService, ErrorThesaurusProperties errors, - EncryptionService encryptionService, TenantProperties properties, TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler, TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler) { + EncryptionService encryptionService, TenantProperties properties, TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler, TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler, KeycloakService keycloakService, AuthorizationProperties authorizationProperties, TenantScope tenantScope, QueryFactory queryFactory) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -97,6 +111,10 @@ public class TenantServiceImpl implements TenantService { this.properties = properties; this.tenantTouchedIntegrationEventHandler = tenantTouchedIntegrationEventHandler; this.tenantRemovalIntegrationEventHandler = tenantRemovalIntegrationEventHandler; + this.keycloakService = keycloakService; + this.authorizationProperties = authorizationProperties; + this.tenantScope = tenantScope; + this.queryFactory = queryFactory; } @Override @@ -131,6 +149,11 @@ public class TenantServiceImpl implements TenantService { else this.entityManager.persist(data); this.entityManager.flush(); + + if (!isUpdate) { + this.keycloakService.createTenantGroups(data.getCode()); + this.autoAssignGlobalAdminsToNewTenant(data); + } TenantTouchedIntegrationEvent tenantTouchedIntegrationEvent = new TenantTouchedIntegrationEvent(); tenantTouchedIntegrationEvent.setId(data.getId()); @@ -139,6 +162,39 @@ public class TenantServiceImpl implements TenantService { return this.builderFactory.builder(TenantBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Tenant._id), data); } + + private void autoAssignGlobalAdminsToNewTenant(TenantEntity tenant){ + if (!this.authorizationProperties.getAutoAssignGlobalAdminToNewTenants()) return; + List existingItems; + List userCredentialEntities; + try { + this.tenantScope.setTempTenant(this.entityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode()); + + existingItems = this.queryFactory.query(UserRoleQuery.class).tenantIsSet(false).roles(this.authorizationProperties.getGlobalAdminRole()).collect(); + userCredentialEntities = this.queryFactory.query(UserCredentialQuery.class).userIds(existingItems.stream().map(UserRoleEntity::getUserId).distinct().toList()).collect(); + } finally { + this.tenantScope.removeTempTenant(this.entityManager.getEntityManager()); + } + try { + this.tenantScope.setTempTenant(this.entityManager.getEntityManager(), tenant.getId(), tenant.getCode()); + for (UUID userId : existingItems.stream().map(UserRoleEntity::getUserId).distinct().toList()) { + UserCredentialEntity userCredential = userCredentialEntities.stream().filter(x-> !this.conventionService.isNullOrEmpty(x.getExternalId()) && x.getUserId().equals(userId)).findFirst().orElse(null); + if (userCredential == null) continue; + UserRoleEntity item = new UserRoleEntity(); + item.setId(UUID.randomUUID()); + item.setUserId(userId); + item.setRole(this.authorizationProperties.getGlobalAdminRole()); + item.setCreatedAt(Instant.now()); + this.entityManager.persist(item); + this.keycloakService.addUserToGlobalRoleGroup(userCredential.getExternalId(), this.authorizationProperties.getGlobalAdminRole()); + } + } finally { + this.tenantScope.removeTempTenant(this.entityManager.getEntityManager()); + } + + this.entityManager.flush(); + + } private @NotNull TenantConfigEntity buildConfigEntity(TenantConfigPersist persist) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { TenantConfigEntity data = new TenantConfigEntity(); diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/user/UserServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/user/UserServiceImpl.java index 4be24d789..e966174b5 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/user/UserServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/user/UserServiceImpl.java @@ -29,7 +29,6 @@ import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEvent; import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEventHandler; import eu.eudat.integrationevent.outbox.userremoval.UserRemovalIntegrationEventHandler; import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler; -import eu.eudat.model.Tenant; import eu.eudat.model.User; import eu.eudat.model.UserContactInfo; import eu.eudat.model.UserCredential; @@ -41,7 +40,6 @@ import eu.eudat.model.persist.actionconfirmation.RemoveCredentialRequestPersist; import eu.eudat.query.*; import eu.eudat.service.actionconfirmation.ActionConfirmationService; import eu.eudat.service.elastic.ElasticService; -import eu.eudat.service.keycloak.KeycloakRole; import eu.eudat.service.keycloak.KeycloakService; import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.BuilderFactory; @@ -252,7 +250,7 @@ public class UserServiceImpl implements UserService { throw new MyApplicationException("Currently cannot update roles for this user"); if (userCredentials.getFirst().getExternalId() == null) throw new MyApplicationException("Currently cannot update roles for this user"); - UUID subjectId = UUID.fromString(userCredentials.getFirst().getExternalId()); + String subjectId = userCredentials.getFirst().getExternalId(); this.applyGlobalRoles(data.getId(), subjectId, model); @@ -266,33 +264,39 @@ public class UserServiceImpl implements UserService { return this.builderFactory.builder(UserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, User._id), data); } - private void applyGlobalRoles(UUID userId, UUID subjectId, UserRolePatchPersist model) throws InvalidApplicationException { - List existingItems = this.queryFactory.query(UserRoleQuery.class).userIds(userId).tenantIsSet(false).roles(this.authorizationProperties.getAllowedGlobalRoles()).collect(); - List foundIds = new ArrayList<>(); - for (String roleName : model.getRoles().stream().filter(x-> x != null && !x.isBlank() && this.authorizationProperties.getAllowedGlobalRoles().contains(x)).distinct().toList()) { - UserRoleEntity item = existingItems.stream().filter(x-> x.getRole().equals(roleName)).findFirst().orElse(null); - if (item == null) { - item = new UserRoleEntity(); - item.setId(UUID.randomUUID()); - item.setUserId(userId); - item.setRole(roleName); - item.setCreatedAt(Instant.now()); - this.entityManager.persist(item); - this.keycloakService.addUserToGroup(subjectId, KeycloakRole.valueOf(roleName)); + private void applyGlobalRoles(UUID userId, String subjectId, UserRolePatchPersist model) throws InvalidApplicationException { + try { + this.tenantScope.setTempTenant(this.entityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode()); + + List existingItems = this.queryFactory.query(UserRoleQuery.class).userIds(userId).tenantIsSet(false).roles(this.authorizationProperties.getAllowedGlobalRoles()).collect(); + List foundIds = new ArrayList<>(); + for (String roleName : model.getRoles().stream().filter(x -> x != null && !x.isBlank() && this.authorizationProperties.getAllowedGlobalRoles().contains(x)).distinct().toList()) { + UserRoleEntity item = existingItems.stream().filter(x -> x.getRole().equals(roleName)).findFirst().orElse(null); + if (item == null) { + item = new UserRoleEntity(); + item.setId(UUID.randomUUID()); + item.setUserId(userId); + item.setRole(roleName); + item.setCreatedAt(Instant.now()); + this.entityManager.persist(item); + this.keycloakService.addUserToGlobalRoleGroup(subjectId, roleName); + } + foundIds.add(item.getId()); } - foundIds.add(item.getId()); + + this.entityManager.flush(); + + List toDelete = existingItems.stream().filter(x -> foundIds.stream().noneMatch(y -> y.equals(x.getId()))).collect(Collectors.toList()); + toDelete.forEach(x -> this.keycloakService.removeUserGlobalRoleGroup(subjectId, x.getRole())); + this.deleterFactory.deleter(UserRoleDeleter.class).deleteAndSave(toDelete); + + this.entityManager.flush(); + } finally { + this.tenantScope.removeTempTenant(this.entityManager.getEntityManager()); } - - this.entityManager.flush(); - - List toDelete = existingItems.stream().filter(x-> foundIds.stream().noneMatch(y-> y.equals(x.getId()))).collect(Collectors.toList()); - toDelete.forEach(x -> this.keycloakService.removeUserFromGroup(subjectId, KeycloakRole.valueOf(x.getRole()))); - this.deleterFactory.deleter(UserRoleDeleter.class).deleteAndSave(toDelete); - - this.entityManager.flush(); } - private void applyTenantRoles(UUID userId, UUID subjectId, UserRolePatchPersist model) throws InvalidApplicationException { + private void applyTenantRoles(UUID userId, String subjectId, UserRolePatchPersist model) throws InvalidApplicationException { if (!tenantScope.isSet()) throw new MyForbiddenException("tenant scope required"); UserRoleQuery userRoleQuery = this.queryFactory.query(UserRoleQuery.class).userIds(userId).roles(this.authorizationProperties.getAllowedTenantRoles()); @@ -311,7 +315,7 @@ public class UserServiceImpl implements UserService { item.setCreatedAt(Instant.now()); item.setTenantId(this.tenantScope.getTenant()); this.entityManager.persist(item); - this.keycloakService.addUserToGroup(subjectId, KeycloakRole.valueOf(roleName)); + this.keycloakService.addUserToTenantRoleGroup(subjectId, this.tenantScope.getTenantCode(), roleName); } foundIds.add(item.getId()); } @@ -319,7 +323,13 @@ public class UserServiceImpl implements UserService { this.entityManager.flush(); List toDelete = existingItems.stream().filter(x-> foundIds.stream().noneMatch(y-> y.equals(x.getId()))).collect(Collectors.toList()); - toDelete.forEach(x -> this.keycloakService.removeUserFromGroup(subjectId, KeycloakRole.valueOf(x.getRole()))); + toDelete.forEach(x -> { + try { + this.keycloakService.removeUserTenantRoleGroup(subjectId, this.tenantScope.getTenantCode(), x.getRole()); + } catch (InvalidApplicationException e) { + throw new RuntimeException(e); + } + }); this.deleterFactory.deleter(UserRoleDeleter.class).deleteAndSave(toDelete); this.entityManager.flush(); diff --git a/dmp-backend/web/src/main/resources/config/authorization.yml b/dmp-backend/web/src/main/resources/config/authorization.yml index 8fde55603..acd4c2182 100644 --- a/dmp-backend/web/src/main/resources/config/authorization.yml +++ b/dmp-backend/web/src/main/resources/config/authorization.yml @@ -1,4 +1,6 @@ authorization: + globalAdminRole: Admin + autoAssignGlobalAdminToNewTenants: true allowedTenantRoles: - TenantAdmin - TenantUser diff --git a/dmp-backend/web/src/main/resources/config/keycloak-devel.yml b/dmp-backend/web/src/main/resources/config/keycloak-devel.yml index 83303e735..1ee728d13 100644 --- a/dmp-backend/web/src/main/resources/config/keycloak-devel.yml +++ b/dmp-backend/web/src/main/resources/config/keycloak-devel.yml @@ -1,14 +1,21 @@ keycloak-resources: + tenantGroupsNamingStrategy: 'tenant-{tenantCode}' + tenantRoleAttributeName: 'tenant_role' authorities: User: groupId: a04fd333-f127-449e-8fc2-0626570a3899 - groupTitle: role-user Admin: groupId: 299f18fe-e271-4625-a4c1-9c3eb313b2ea - groupTitle: role-admin - Manager: - groupId: 1753f7a7-cedb-4ad4-ae5f-96fe9bdabe3e - groupTitle: role-manager - DescriptionTemplateEditor: - groupId: b0ea3cf3-21b0-4c6b-9c42-fb09f0e09dbb - groupTitle: role-description-template-editor \ No newline at end of file + tenantAuthorities: + TenantAdmin: + parent: 1e650f57-8b7c-4f32-bf5b-e1a9147c597b + roleAttributeValueStrategy: 'TenantAdmin:{tenantCode}' + TenantUser: + parent: c7057c4d-e7dc-49ef-aa5d-02ad3a22bff8 + roleAttributeValueStrategy: 'TenantUser:{tenantCode}' + TenantManager: + parent: d111bb2f-b4a6-4de7-ad22-5151ee1a508b + roleAttributeValueStrategy: 'TenantManager:{tenantCode}' + TenantDescriptionTemplateEditor: + parent: 55cf7b17-c025-4065-8906-49f9f430f038 + roleAttributeValueStrategy: 'TenantDescriptionTemplateEditor:{tenantCode}' \ No newline at end of file diff --git a/dmp-backend/web/src/main/resources/config/keycloak.yml b/dmp-backend/web/src/main/resources/config/keycloak.yml index 934a3e5f0..b66dcbefe 100644 --- a/dmp-backend/web/src/main/resources/config/keycloak.yml +++ b/dmp-backend/web/src/main/resources/config/keycloak.yml @@ -1,10 +1,10 @@ keycloak-client: - serverUrl: ${KEYCLOAK_API_SERVER_URL:} - realm: ${KEYCLOAK_API_REALM:} - username: ${KEYCLOAK_API_USERNAME:} - password: ${KEYCLOAK_API_PASSWORD:} - clientId: ${KEYCLOAK_API_CLIENT_ID:} - clientSecret: ${KEYCLOAK_API_CLIENT_SECRET:} + serverUrl: ${KEYCLOAK_API_SERVER_URL} + realm: ${KEYCLOAK_API_REALM} + username: ${KEYCLOAK_API_USERNAME} + password: ${KEYCLOAK_API_PASSWORD} + clientId: ${KEYCLOAK_API_CLIENT_ID} + clientSecret: ${KEYCLOAK_API_CLIENT_SECRET} keycloak-resources: authorities: null \ No newline at end of file