auto create keycloak tenant groups

This commit is contained in:
Efstratios Giannopoulos 2024-04-16 13:17:58 +03:00
parent 3e4316a627
commit 2e083df185
12 changed files with 281 additions and 97 deletions

View File

@ -9,6 +9,25 @@ import java.util.List;
@ConfigurationProperties(prefix = "authorization") @ConfigurationProperties(prefix = "authorization")
public class AuthorizationProperties { 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<String> allowedTenantRoles; private List<String> allowedTenantRoles;
public List<String> getAllowedTenantRoles() { public List<String> getAllowedTenantRoles() {

View File

@ -1,22 +1,18 @@
package eu.eudat.service.keycloak; package eu.eudat.service.keycloak;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
public class KeycloakAuthorityProperties { public class KeycloakAuthorityProperties {
private final String groupId, groupTitle; private String groupId;
@ConstructorBinding public KeycloakAuthorityProperties() {
public KeycloakAuthorityProperties(String groupId, String groupTitle) {
this.groupId = groupId;
this.groupTitle = groupTitle;
} }
public String getGroupId() { public String getGroupId() {
return groupId; return groupId;
} }
public String getGroupTitle() { public void setGroupId(String groupId) {
return groupTitle; this.groupId = groupId;
} }
} }

View File

@ -19,4 +19,14 @@ public class KeycloakResourcesConfiguration {
return properties; 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);
}
} }

View File

@ -10,15 +10,41 @@ import java.util.HashMap;
@ConditionalOnProperty(prefix = "keycloak-resources", name = "enabled", havingValue = "true") @ConditionalOnProperty(prefix = "keycloak-resources", name = "enabled", havingValue = "true")
public class KeycloakResourcesProperties { public class KeycloakResourcesProperties {
private final HashMap<String, KeycloakAuthorityProperties> authorities; private HashMap<String, KeycloakAuthorityProperties> authorities;
private HashMap<String, KeycloakTenantAuthorityProperties> tenantAuthorities;
@ConstructorBinding
public KeycloakResourcesProperties(HashMap<String, KeycloakAuthorityProperties> authorities) { private String tenantGroupsNamingStrategy;
this.authorities = authorities; private String tenantRoleAttributeName;
}
public HashMap<String, KeycloakAuthorityProperties> getAuthorities() { public HashMap<String, KeycloakAuthorityProperties> getAuthorities() {
return authorities; return authorities;
} }
public void setAuthorities(HashMap<String, KeycloakAuthorityProperties> authorities) {
this.authorities = authorities;
}
public HashMap<String, KeycloakTenantAuthorityProperties> getTenantAuthorities() {
return tenantAuthorities;
}
public void setTenantAuthorities(HashMap<String, KeycloakTenantAuthorityProperties> 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;
}
} }

View File

@ -1,17 +1,23 @@
package eu.eudat.service.keycloak; package eu.eudat.service.keycloak;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.keycloak.representations.idm.GroupRepresentation;
import java.util.HashMap; import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
public interface KeycloakService { public interface KeycloakService {
void addUserToGroup(UUID subjectId, String groupId); void addUserToGroup(@NotNull String 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 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);
} }

View File

@ -4,7 +4,6 @@ import gr.cite.commons.web.keycloak.api.configuration.KeycloakClientConfiguratio
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -27,56 +26,83 @@ public class KeycloakServiceImpl implements KeycloakService {
} }
@Override @Override
public void addUserToGroup(@NotNull UUID subjectId, String groupId) { public void addUserToGroup(@NotNull String subjectId, String groupId) {
api.users().addUserToGroup(subjectId.toString(), groupId); api.users().addUserToGroup(subjectId, groupId);
} }
@Override @Override
public void removeUserFromGroup(@NotNull UUID subjectId, String groupId) { public void removeUserFromGroup(@NotNull String subjectId, String groupId) {
api.users().removeUserFromGroup(subjectId.toString(), groupId); api.users().removeUserFromGroup(subjectId, groupId);
} }
@Override @Override
public void addUserToGroup(UUID subjectId, KeycloakRole role) { public void addUserToGlobalRoleGroup(String subjectId, String role) {
KeycloakAuthorityProperties properties = this.configuration.getProperties().getAuthorities().get(role.name()); KeycloakAuthorityProperties properties = this.configuration.getProperties().getAuthorities().get(role);
if (properties != null) if (properties != null) addUserToGroup(subjectId, properties.getGroupId());
addUserToGroup(subjectId, properties.getGroupId());
} }
@Override @Override
public void removeUserFromGroup(@NotNull UUID subjectId, KeycloakRole role) { public void removeUserGlobalRoleGroup(@NotNull String subjectId, String role) {
KeycloakAuthorityProperties properties = this.configuration.getProperties().getAuthorities().get(role.name()); KeycloakAuthorityProperties properties = this.configuration.getProperties().getAuthorities().get(role);
if (properties != null) if (properties != null)
removeUserFromGroup(subjectId, properties.getGroupId()); 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<String> 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<String> 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<GroupRepresentation> getUserGroups(UUID subjectId) {
// return api.users().getGroups(subjectId.toString());
// }
@Override @Override
public void assignClientRoleToUser(UUID subjectId, String clientId, KeycloakRole role) { public void addUserToTenantRoleGroup(String subjectId, String tenantCode, String tenantRole) {
if (clientId == null) GroupRepresentation group = api.groups().findGroupByPath(getTenantAuthorityParentPath(tenantRole) + "/" + configuration.getTenantGroupName(tenantCode));
clientId = clientConfiguration.getProperties().getClientId(); addUserToGroup(subjectId, group.getId());
UserRepresentation user = api.users().findUserById(subjectId.toString());
user.getClientRoles().computeIfAbsent(clientId, k -> new ArrayList<>());
Set<String> 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 @Override
public void removeClientRoleFromUser(UUID subjectId, String clientId, KeycloakRole role) { public void removeUserTenantRoleGroup(String subjectId, String tenantCode, String tenantRole) {
if (clientId == null) clientId = clientConfiguration.getProperties().getClientId(); GroupRepresentation group = api.groups().findGroupByPath(getTenantAuthorityParentPath(tenantRole) + "/" + configuration.getTenantGroupName(tenantCode));
UserRepresentation user = api.users().findUserById(subjectId.toString()); removeUserFromGroup(subjectId, group.getId());
user.getClientRoles().computeIfAbsent(clientId, k -> new ArrayList<>()); }
Set<String> clientRoles = new HashSet<>(Set.copyOf(user.getClientRoles().get(clientId)));
clientRoles.remove(role.name()); private String getTenantAuthorityParentPath(String tenantRole) {
user.getClientRoles().get(clientId).clear(); GroupRepresentation parent = api.groups().findGroupById(configuration.getProperties().getTenantAuthorities().get(tenantRole).getParent());
user.getClientRoles().get(clientId).addAll(clientRoles); return parent.getPath();
api.users().updateUser(subjectId.toString(), user);
} }
public List<GroupRepresentation> getUserGroups(UUID subjectId) { @Override
return api.users().getGroups(subjectId.toString()); public void createTenantGroups(String tenantCode) {
for (Map.Entry<String,KeycloakTenantAuthorityProperties> entry :configuration.getProperties().getTenantAuthorities().entrySet()){
GroupRepresentation group = new GroupRepresentation();
group.setName(configuration.getTenantGroupName(tenantCode));
HashMap<String, List<String>> 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());
}
} }
} }

View File

@ -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;
}
}

View File

@ -1,25 +1,34 @@
package eu.eudat.service.tenant; package eu.eudat.service.tenant;
import eu.eudat.authorization.AuthorizationFlags; import eu.eudat.authorization.AuthorizationFlags;
import eu.eudat.authorization.AuthorizationProperties;
import eu.eudat.authorization.Permission; import eu.eudat.authorization.Permission;
import eu.eudat.commons.XmlHandlingService; import eu.eudat.commons.XmlHandlingService;
import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.enums.IsActive;
import eu.eudat.commons.scope.tenant.TenantScope;
import eu.eudat.commons.types.tenant.*; import eu.eudat.commons.types.tenant.*;
import eu.eudat.convention.ConventionService; import eu.eudat.convention.ConventionService;
import eu.eudat.data.TenantEntity; import eu.eudat.data.TenantEntity;
import eu.eudat.data.TenantEntityManager; import eu.eudat.data.TenantEntityManager;
import eu.eudat.data.UserCredentialEntity;
import eu.eudat.data.UserRoleEntity;
import eu.eudat.errorcode.ErrorThesaurusProperties; import eu.eudat.errorcode.ErrorThesaurusProperties;
import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEvent; import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEvent;
import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEventHandler; import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEventHandler;
import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEvent; import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEvent;
import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEventHandler; import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEventHandler;
import eu.eudat.model.Tenant; import eu.eudat.model.Tenant;
import eu.eudat.model.UserCredential;
import eu.eudat.model.builder.TenantBuilder; import eu.eudat.model.builder.TenantBuilder;
import eu.eudat.model.deleter.TenantDeleter; import eu.eudat.model.deleter.TenantDeleter;
import eu.eudat.model.deleter.UserRoleDeleter;
import eu.eudat.model.persist.TenantPersist; import eu.eudat.model.persist.TenantPersist;
import eu.eudat.model.persist.tenantconfig.*; import eu.eudat.model.persist.tenantconfig.*;
import eu.eudat.model.tenantconfig.TenantSource; 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.encryption.EncryptionService;
import eu.eudat.service.keycloak.KeycloakService;
import eu.eudat.service.responseutils.ResponseUtilsService; import eu.eudat.service.responseutils.ResponseUtilsService;
import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.builder.BuilderFactory;
@ -73,6 +82,11 @@ public class TenantServiceImpl implements TenantService {
private final TenantProperties properties; private final TenantProperties properties;
private final TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler; private final TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler;
private final TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler; private final TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler;
private final KeycloakService keycloakService;
private final AuthorizationProperties authorizationProperties;
private final TenantScope tenantScope;
private final QueryFactory queryFactory;
@Autowired @Autowired
public TenantServiceImpl( public TenantServiceImpl(
@ -84,7 +98,7 @@ public class TenantServiceImpl implements TenantService {
MessageSource messageSource, MessageSource messageSource,
XmlHandlingService xmlHandlingService, XmlHandlingService xmlHandlingService,
ErrorThesaurusProperties errors, 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.entityManager = entityManager;
this.authorizationService = authorizationService; this.authorizationService = authorizationService;
this.deleterFactory = deleterFactory; this.deleterFactory = deleterFactory;
@ -97,6 +111,10 @@ public class TenantServiceImpl implements TenantService {
this.properties = properties; this.properties = properties;
this.tenantTouchedIntegrationEventHandler = tenantTouchedIntegrationEventHandler; this.tenantTouchedIntegrationEventHandler = tenantTouchedIntegrationEventHandler;
this.tenantRemovalIntegrationEventHandler = tenantRemovalIntegrationEventHandler; this.tenantRemovalIntegrationEventHandler = tenantRemovalIntegrationEventHandler;
this.keycloakService = keycloakService;
this.authorizationProperties = authorizationProperties;
this.tenantScope = tenantScope;
this.queryFactory = queryFactory;
} }
@Override @Override
@ -131,6 +149,11 @@ public class TenantServiceImpl implements TenantService {
else this.entityManager.persist(data); else this.entityManager.persist(data);
this.entityManager.flush(); this.entityManager.flush();
if (!isUpdate) {
this.keycloakService.createTenantGroups(data.getCode());
this.autoAssignGlobalAdminsToNewTenant(data);
}
TenantTouchedIntegrationEvent tenantTouchedIntegrationEvent = new TenantTouchedIntegrationEvent(); TenantTouchedIntegrationEvent tenantTouchedIntegrationEvent = new TenantTouchedIntegrationEvent();
tenantTouchedIntegrationEvent.setId(data.getId()); 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); 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<UserRoleEntity> existingItems;
List<UserCredentialEntity> 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 { private @NotNull TenantConfigEntity buildConfigEntity(TenantConfigPersist persist) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
TenantConfigEntity data = new TenantConfigEntity(); TenantConfigEntity data = new TenantConfigEntity();

View File

@ -29,7 +29,6 @@ import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEvent;
import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEventHandler; import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEventHandler;
import eu.eudat.integrationevent.outbox.userremoval.UserRemovalIntegrationEventHandler; import eu.eudat.integrationevent.outbox.userremoval.UserRemovalIntegrationEventHandler;
import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler; import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler;
import eu.eudat.model.Tenant;
import eu.eudat.model.User; import eu.eudat.model.User;
import eu.eudat.model.UserContactInfo; import eu.eudat.model.UserContactInfo;
import eu.eudat.model.UserCredential; import eu.eudat.model.UserCredential;
@ -41,7 +40,6 @@ import eu.eudat.model.persist.actionconfirmation.RemoveCredentialRequestPersist;
import eu.eudat.query.*; import eu.eudat.query.*;
import eu.eudat.service.actionconfirmation.ActionConfirmationService; import eu.eudat.service.actionconfirmation.ActionConfirmationService;
import eu.eudat.service.elastic.ElasticService; import eu.eudat.service.elastic.ElasticService;
import eu.eudat.service.keycloak.KeycloakRole;
import eu.eudat.service.keycloak.KeycloakService; import eu.eudat.service.keycloak.KeycloakService;
import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.data.builder.BuilderFactory; 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"); throw new MyApplicationException("Currently cannot update roles for this user");
if (userCredentials.getFirst().getExternalId() == null) if (userCredentials.getFirst().getExternalId() == null)
throw new MyApplicationException("Currently cannot update roles for this user"); 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); 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); 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 { private void applyGlobalRoles(UUID userId, String subjectId, UserRolePatchPersist model) throws InvalidApplicationException {
List<UserRoleEntity> existingItems = this.queryFactory.query(UserRoleQuery.class).userIds(userId).tenantIsSet(false).roles(this.authorizationProperties.getAllowedGlobalRoles()).collect(); try {
List<UUID> foundIds = new ArrayList<>(); this.tenantScope.setTempTenant(this.entityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
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); List<UserRoleEntity> existingItems = this.queryFactory.query(UserRoleQuery.class).userIds(userId).tenantIsSet(false).roles(this.authorizationProperties.getAllowedGlobalRoles()).collect();
if (item == null) { List<UUID> foundIds = new ArrayList<>();
item = new UserRoleEntity(); for (String roleName : model.getRoles().stream().filter(x -> x != null && !x.isBlank() && this.authorizationProperties.getAllowedGlobalRoles().contains(x)).distinct().toList()) {
item.setId(UUID.randomUUID()); UserRoleEntity item = existingItems.stream().filter(x -> x.getRole().equals(roleName)).findFirst().orElse(null);
item.setUserId(userId); if (item == null) {
item.setRole(roleName); item = new UserRoleEntity();
item.setCreatedAt(Instant.now()); item.setId(UUID.randomUUID());
this.entityManager.persist(item); item.setUserId(userId);
this.keycloakService.addUserToGroup(subjectId, KeycloakRole.valueOf(roleName)); 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<UserRoleEntity> 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<UserRoleEntity> 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"); if (!tenantScope.isSet()) throw new MyForbiddenException("tenant scope required");
UserRoleQuery userRoleQuery = this.queryFactory.query(UserRoleQuery.class).userIds(userId).roles(this.authorizationProperties.getAllowedTenantRoles()); 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.setCreatedAt(Instant.now());
item.setTenantId(this.tenantScope.getTenant()); item.setTenantId(this.tenantScope.getTenant());
this.entityManager.persist(item); this.entityManager.persist(item);
this.keycloakService.addUserToGroup(subjectId, KeycloakRole.valueOf(roleName)); this.keycloakService.addUserToTenantRoleGroup(subjectId, this.tenantScope.getTenantCode(), roleName);
} }
foundIds.add(item.getId()); foundIds.add(item.getId());
} }
@ -319,7 +323,13 @@ public class UserServiceImpl implements UserService {
this.entityManager.flush(); this.entityManager.flush();
List<UserRoleEntity> toDelete = existingItems.stream().filter(x-> foundIds.stream().noneMatch(y-> y.equals(x.getId()))).collect(Collectors.toList()); List<UserRoleEntity> 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.deleterFactory.deleter(UserRoleDeleter.class).deleteAndSave(toDelete);
this.entityManager.flush(); this.entityManager.flush();

View File

@ -1,4 +1,6 @@
authorization: authorization:
globalAdminRole: Admin
autoAssignGlobalAdminToNewTenants: true
allowedTenantRoles: allowedTenantRoles:
- TenantAdmin - TenantAdmin
- TenantUser - TenantUser

View File

@ -1,14 +1,21 @@
keycloak-resources: keycloak-resources:
tenantGroupsNamingStrategy: 'tenant-{tenantCode}'
tenantRoleAttributeName: 'tenant_role'
authorities: authorities:
User: User:
groupId: a04fd333-f127-449e-8fc2-0626570a3899 groupId: a04fd333-f127-449e-8fc2-0626570a3899
groupTitle: role-user
Admin: Admin:
groupId: 299f18fe-e271-4625-a4c1-9c3eb313b2ea groupId: 299f18fe-e271-4625-a4c1-9c3eb313b2ea
groupTitle: role-admin tenantAuthorities:
Manager: TenantAdmin:
groupId: 1753f7a7-cedb-4ad4-ae5f-96fe9bdabe3e parent: 1e650f57-8b7c-4f32-bf5b-e1a9147c597b
groupTitle: role-manager roleAttributeValueStrategy: 'TenantAdmin:{tenantCode}'
DescriptionTemplateEditor: TenantUser:
groupId: b0ea3cf3-21b0-4c6b-9c42-fb09f0e09dbb parent: c7057c4d-e7dc-49ef-aa5d-02ad3a22bff8
groupTitle: role-description-template-editor roleAttributeValueStrategy: 'TenantUser:{tenantCode}'
TenantManager:
parent: d111bb2f-b4a6-4de7-ad22-5151ee1a508b
roleAttributeValueStrategy: 'TenantManager:{tenantCode}'
TenantDescriptionTemplateEditor:
parent: 55cf7b17-c025-4065-8906-49f9f430f038
roleAttributeValueStrategy: 'TenantDescriptionTemplateEditor:{tenantCode}'

View File

@ -1,10 +1,10 @@
keycloak-client: keycloak-client:
serverUrl: ${KEYCLOAK_API_SERVER_URL:} serverUrl: ${KEYCLOAK_API_SERVER_URL}
realm: ${KEYCLOAK_API_REALM:} realm: ${KEYCLOAK_API_REALM}
username: ${KEYCLOAK_API_USERNAME:} username: ${KEYCLOAK_API_USERNAME}
password: ${KEYCLOAK_API_PASSWORD:} password: ${KEYCLOAK_API_PASSWORD}
clientId: ${KEYCLOAK_API_CLIENT_ID:} clientId: ${KEYCLOAK_API_CLIENT_ID}
clientSecret: ${KEYCLOAK_API_CLIENT_SECRET:} clientSecret: ${KEYCLOAK_API_CLIENT_SECRET}
keycloak-resources: keycloak-resources:
authorities: null authorities: null