From baaddb6284a074c26ab491d73393d46e7f7b012c Mon Sep 17 00:00:00 2001 From: Thomas Georgios Giannos Date: Wed, 29 Nov 2023 16:12:56 +0200 Subject: [PATCH] Adding keycloak service and dependency --- .../keycloak/KeycloakAuthorities.java | 8 ++ .../KeycloakResourcesConfiguration.java | 56 +++++++++++ .../keycloak/KeycloakResourcesProperties.java | 65 +++++++++++++ .../service/keycloak/KeycloakService.java | 20 ++++ .../service/keycloak/KeycloakServiceImpl.java | 92 +++++++++++++++++++ .../src/main/resources/config/application.yml | 1 + .../main/resources/config/keycloak-devel.yml | 13 +++ .../src/main/resources/config/keycloak.yml | 13 +++ 8 files changed, 268 insertions(+) create mode 100644 dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakAuthorities.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakResourcesConfiguration.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakResourcesProperties.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakService.java create mode 100644 dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakServiceImpl.java create mode 100644 dmp-backend/web/src/main/resources/config/keycloak-devel.yml create mode 100644 dmp-backend/web/src/main/resources/config/keycloak.yml diff --git a/dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakAuthorities.java b/dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakAuthorities.java new file mode 100644 index 000000000..886ed615c --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakAuthorities.java @@ -0,0 +1,8 @@ +package eu.eudat.configurations.keycloak; + +public class KeycloakAuthorities { + + public static final String ADMIN = "admin"; + public static final String USER = "user"; + +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakResourcesConfiguration.java b/dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakResourcesConfiguration.java new file mode 100644 index 000000000..1bb9804cd --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakResourcesConfiguration.java @@ -0,0 +1,56 @@ +package eu.eudat.configurations.keycloak; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +@EnableConfigurationProperties(KeycloakResourcesProperties.class) +public class KeycloakResourcesConfiguration { + + private final KeycloakResourcesProperties properties; + + @Autowired + public KeycloakResourcesConfiguration(KeycloakResourcesProperties properties) { + this.properties = properties; + } + + public KeycloakResourcesProperties getProperties() { + return properties; + } + + public String getGroupName(String tenantCode, String tenantId) { + return properties.getTenantGroupsNamingStrategy() + .replace("{tenantCode}", tenantCode) + .replace("{tenantId}", tenantId); + } + + public String getAuthorityName(String tenantCode, String key) { + return properties.getAuthorities().get(key).getNamingStrategy() + .replace("{tenantCode}", tenantCode); + } + + public boolean hasAuthority(String authority, String tenantCode, String key) { + return getAuthorityName(tenantCode, key).equals(authority); + } + + public List extractAuthoritiesForTenant(List authorities, String tenantCode) { + List extractedAuthorities = new ArrayList<>(); + List markedForRemoval = new ArrayList<>(); + authorities.forEach(auth -> { + properties.getAuthorities().keySet().forEach(key -> { + if (hasAuthority(auth, tenantCode, key)) { + extractedAuthorities.add(properties.getAuthorities().get(key).getTitle()); + markedForRemoval.add(auth); + } + }); + }); + authorities.removeAll(markedForRemoval); + authorities.addAll(extractedAuthorities); + return extractedAuthorities; + } + +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakResourcesProperties.java b/dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakResourcesProperties.java new file mode 100644 index 000000000..c5688e1ad --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/configurations/keycloak/KeycloakResourcesProperties.java @@ -0,0 +1,65 @@ +package eu.eudat.configurations.keycloak; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +import java.util.HashMap; + +@ConfigurationProperties(prefix = "keycloak-resources") +@ConditionalOnProperty(prefix = "keycloak-resources", name = "enabled", havingValue = "true") +public class KeycloakResourcesProperties { + + private final String tenantGroupsNamingStrategy, guestsGroup, administratorsGroup; + + private final HashMap authorities; + + @ConstructorBinding + public KeycloakResourcesProperties(String tenantGroupsNamingStrategy, String guestsGroup, String administratorsGroup, HashMap authorities) { + this.tenantGroupsNamingStrategy = tenantGroupsNamingStrategy; + this.guestsGroup = guestsGroup; + this.administratorsGroup = administratorsGroup; + this.authorities = authorities; + } + + public String getTenantGroupsNamingStrategy() { + return tenantGroupsNamingStrategy; + } + + public String getGuestsGroup() { + return guestsGroup; + } + + public String getAdministratorsGroup() { + return administratorsGroup; + } + + public HashMap getAuthorities() { + return authorities; + } + + public static class TenantAuthorityGroupProperties { + + private final String parent, namingStrategy, title; + + @ConstructorBinding + public TenantAuthorityGroupProperties(String parent, String namingStrategy, String title) { + this.parent = parent; + this.namingStrategy = namingStrategy; + this.title = title; + } + + public String getParent() { + return parent; + } + + public String getNamingStrategy() { + return namingStrategy; + } + + public String getTitle() { + return title; + } + } + +} 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 new file mode 100644 index 000000000..fd1784e3e --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakService.java @@ -0,0 +1,20 @@ +package eu.eudat.service.keycloak; + +import eu.eudat.data.TenantEntity; +import org.jetbrains.annotations.NotNull; +import org.keycloak.representations.idm.GroupRepresentation; + +import java.util.HashMap; +import java.util.UUID; + +public interface KeycloakService { + + HashMap createTenantGroups(TenantEntity tenant); + void addUserToGroup(UUID subjectId, String groupId); + void removeUserFromGroup(@NotNull UUID subjectId, String groupId); + void addUserToAdministratorsGroup(UUID subjectId); + void removeUserFromAdministratorsGroup(@NotNull UUID subjectId); + void addUserToTenantAuthorityGroup(UUID subjectId, TenantEntity tenant, String key); + void removeUserFromTenantAuthorityGroup(UUID subjectId, TenantEntity tenant, String key); + +} 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 new file mode 100644 index 000000000..6fd2d40a4 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/service/keycloak/KeycloakServiceImpl.java @@ -0,0 +1,92 @@ +package eu.eudat.service.keycloak; + +import com.google.common.collect.Lists; +import eu.eudat.configurations.keycloak.KeycloakResourcesConfiguration; +import eu.eudat.data.TenantEntity; +import gr.cite.commons.web.keycloak.api.KeycloakAdminRestApi; +import gr.cite.tools.logging.LoggerService; +import org.jetbrains.annotations.NotNull; +import org.keycloak.representations.idm.GroupRepresentation; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +@Service +public class KeycloakServiceImpl implements KeycloakService { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(KeycloakServiceImpl.class)); + private final KeycloakAdminRestApi api; + private final KeycloakResourcesConfiguration configuration; + + @Autowired + public KeycloakServiceImpl(KeycloakAdminRestApi api, KeycloakResourcesConfiguration configuration) { + this.api = api; + this.configuration = configuration; + logger.info("Keycloak service initialized. Tenant authorities configured -> {}", configuration.getProperties().getAuthorities().size()); + } + + @Override + public HashMap createTenantGroups(TenantEntity tenant) { + HashMap groups = new HashMap<>(); + + configuration.getProperties().getAuthorities().keySet().forEach(key -> { + GroupRepresentation group = new GroupRepresentation(); + group.setName(configuration.getGroupName(tenant.getCode(), tenant.getId().toString())); + HashMap> user_attributes = new HashMap<>(); + user_attributes.put("auth", Lists.newArrayList(configuration.getAuthorityName(tenant.getCode(), key))); + group.setAttributes(user_attributes); + groups.put(key, api.groups().addGroupWithParent(group, configuration.getProperties().getAuthorities().get(key).getParent())); + }); + + return groups; + } + + @Override + public void addUserToGroup(@NotNull UUID subjectId, String groupId) { + api.users().removeUserFromGroup(subjectId.toString(), configuration.getProperties().getGuestsGroup()); + api.users().addUserToGroup(subjectId.toString(), groupId); + } + + @Override + public void removeUserFromGroup(@NotNull UUID subjectId, String groupId) { + api.users().removeUserFromGroup(subjectId.toString(), groupId); + } + + @Override + public void addUserToAdministratorsGroup(@NotNull UUID subjectId) { + api.users().removeUserFromGroup(subjectId.toString(), configuration.getProperties().getGuestsGroup()); + api.users().addUserToGroup(subjectId.toString(), configuration.getProperties().getAdministratorsGroup()); + } + + @Override + public void removeUserFromAdministratorsGroup(@NotNull UUID subjectId) { + api.users().removeUserFromGroup(subjectId.toString(), configuration.getProperties().getAdministratorsGroup()); + } + + @Override + public void addUserToTenantAuthorityGroup(UUID subjectId, TenantEntity tenant, String key) { + api.users().removeUserFromGroup(subjectId.toString(), configuration.getProperties().getGuestsGroup()); + GroupRepresentation group = api.groups().findGroupByPath(getTenantAuthorityParentPath(key) + "/" + configuration.getGroupName(tenant.getCode(), tenant.getId().toString())); + addUserToGroup(subjectId, group.getId()); + } + + @Override + public void removeUserFromTenantAuthorityGroup(UUID subjectId, TenantEntity tenant, String key) { + GroupRepresentation group = api.groups().findGroupByPath(getTenantAuthorityParentPath(key) + "/" + configuration.getGroupName(tenant.getCode(), tenant.getId().toString())); + removeUserFromGroup(subjectId, group.getId()); + } + + public List getUserGroups(UUID subjectId) { + return api.users().getGroups(subjectId.toString()); + } + + private String getTenantAuthorityParentPath(String key) { + GroupRepresentation parent = api.groups().findGroupById(configuration.getProperties().getAuthorities().get(key).getParent()); + return parent.getPath(); + } + +} diff --git a/dmp-backend/web/src/main/resources/config/application.yml b/dmp-backend/web/src/main/resources/config/application.yml index 0fce2f3d3..a4491436a 100644 --- a/dmp-backend/web/src/main/resources/config/application.yml +++ b/dmp-backend/web/src/main/resources/config/application.yml @@ -7,6 +7,7 @@ spring: optional:classpath:config/permissions.yml[.yml], optional:classpath:config/permissions-${spring.profiles.active}.yml[.yml], optional:file:../config/permissions-${spring.profiles.active}.yml[.yml], optional:classpath:config/security.yml[.yml], optional:classpath:config/security-${spring.profiles.active}.yml[.yml], optional:file:../config/security-${spring.profiles.active}.yml[.yml], optional:classpath:config/server.yml[.yml], optional:classpath:config/server-${spring.profiles.active}.yml[.yml], optional:file:../config/server-${spring.profiles.active}.yml[.yml], + optional:classpath:config/keycloak.yml[.yml], optional:classpath:config/keycloak-${spring.profiles.active}.yml[.yml], optional:file:../config/keycloak-${spring.profiles.active}.yml[.yml], optional:classpath:config/logging.yml[.yml], optional:classpath:config/logging-${spring.profiles.active}.yml[.yml], optional:file:../config/logging-${spring.profiles.active}.yml[.yml], optional:classpath:config/cache.yml[.yml], optional:classpath:config/cache-${spring.profiles.active}.yml[.yml], optional:file:../config/cache-${spring.profiles.active}.yml[.yml], optional:classpath:config/actuator.yml[.yml], optional:classpath:config/actuator-${spring.profiles.active}.yml[.yml], optional:file:../config/actuator-${spring.profiles.active}.yml[.yml], diff --git a/dmp-backend/web/src/main/resources/config/keycloak-devel.yml b/dmp-backend/web/src/main/resources/config/keycloak-devel.yml new file mode 100644 index 000000000..28489cb10 --- /dev/null +++ b/dmp-backend/web/src/main/resources/config/keycloak-devel.yml @@ -0,0 +1,13 @@ +keycloak-resources: + authorities: + user: + parent: beba276b-5e9e-42b6-8012-8d2653312818 + namingStrategy: 'tenantuser:{tenantCode}' + title: tenantuser + admin: + parent: 4432316d-75e3-410d-9934-590b95e76e44 + namingStrategy: 'tenantadmin:{tenantCode}' + title: tenantadmin + tenantGroupsNamingStrategy: 'tenant-{tenantCode}' + guestsGroup: efa9cea5-0c7f-4c83-afb1-cc4ec9b2f1ee + administratorsGroup: 14d8f80a-6f88-4ad4-a42d-3e5023bac7b3 \ 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 new file mode 100644 index 000000000..f62a50749 --- /dev/null +++ b/dmp-backend/web/src/main/resources/config/keycloak.yml @@ -0,0 +1,13 @@ +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} + +keycloak-resources: + authorities: null + tenantGroupsNamingStrategy: null + guestsGroup: null + administratorsGroup: null \ No newline at end of file