Supporting client roles for users on Keycloak API service
This commit is contained in:
parent
0c6e800118
commit
08a5b49d1d
|
@ -0,0 +1,7 @@
|
||||||
|
package eu.eudat.service.keycloak;
|
||||||
|
|
||||||
|
public enum KeycloakRole {
|
||||||
|
|
||||||
|
Admin, DatasetTemplateEditor, Manager, User
|
||||||
|
|
||||||
|
}
|
|
@ -16,5 +16,7 @@ public interface KeycloakService {
|
||||||
void removeUserFromAdministratorsGroup(@NotNull UUID subjectId);
|
void removeUserFromAdministratorsGroup(@NotNull UUID subjectId);
|
||||||
void addUserToTenantAuthorityGroup(UUID subjectId, TenantEntity tenant, String key);
|
void addUserToTenantAuthorityGroup(UUID subjectId, TenantEntity tenant, String key);
|
||||||
void removeUserFromTenantAuthorityGroup(UUID subjectId, TenantEntity tenant, String key);
|
void removeUserFromTenantAuthorityGroup(UUID subjectId, TenantEntity tenant, String key);
|
||||||
|
void assignClientRoleToUser(UUID subjectId, String clientId, KeycloakRole role);
|
||||||
|
void removeClientRoleFromUser(UUID subjectId, String clientId, KeycloakRole role);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,29 +4,31 @@ import com.google.common.collect.Lists;
|
||||||
import eu.eudat.configurations.keycloak.KeycloakResourcesConfiguration;
|
import eu.eudat.configurations.keycloak.KeycloakResourcesConfiguration;
|
||||||
import eu.eudat.data.TenantEntity;
|
import eu.eudat.data.TenantEntity;
|
||||||
import gr.cite.commons.web.keycloak.api.KeycloakAdminRestApi;
|
import gr.cite.commons.web.keycloak.api.KeycloakAdminRestApi;
|
||||||
|
import gr.cite.commons.web.keycloak.api.configuration.KeycloakClientConfiguration;
|
||||||
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;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class KeycloakServiceImpl implements KeycloakService {
|
public class KeycloakServiceImpl implements KeycloakService {
|
||||||
|
|
||||||
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(KeycloakServiceImpl.class));
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(KeycloakServiceImpl.class));
|
||||||
private final KeycloakAdminRestApi api;
|
private final MyKeycloakAdminRestApi api;
|
||||||
private final KeycloakResourcesConfiguration configuration;
|
private final KeycloakResourcesConfiguration configuration;
|
||||||
|
private final KeycloakClientConfiguration clientConfiguration;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public KeycloakServiceImpl(KeycloakAdminRestApi api, KeycloakResourcesConfiguration configuration) {
|
public KeycloakServiceImpl(MyKeycloakAdminRestApi api, KeycloakResourcesConfiguration configuration, KeycloakClientConfiguration clientConfiguration) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
//logger.info("Keycloak service initialized. Tenant authorities configured -> {}", configuration.getProperties().getAuthorities().size());
|
//logger.info("Keycloak service initialized. Tenant authorities configured -> {}", configuration.getProperties().getAuthorities().size());
|
||||||
|
this.clientConfiguration = clientConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -80,6 +82,30 @@ public class KeycloakServiceImpl implements KeycloakService {
|
||||||
removeUserFromGroup(subjectId, group.getId());
|
removeUserFromGroup(subjectId, group.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 -> Lists.newArrayList());
|
||||||
|
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 -> Lists.newArrayList());
|
||||||
|
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) {
|
public List<GroupRepresentation> getUserGroups(UUID subjectId) {
|
||||||
return api.users().getGroups(subjectId.toString());
|
return api.users().getGroups(subjectId.toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package eu.eudat.service.keycloak;
|
||||||
|
|
||||||
|
import gr.cite.commons.web.keycloak.api.configuration.KeycloakClientConfiguration;
|
||||||
|
import gr.cite.commons.web.keycloak.api.modules.ClientsModule;
|
||||||
|
import gr.cite.commons.web.keycloak.api.modules.GroupsModule;
|
||||||
|
import gr.cite.commons.web.keycloak.api.modules.Modules;
|
||||||
|
import gr.cite.tools.logging.LoggerService;
|
||||||
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
import org.keycloak.admin.client.KeycloakBuilder;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class MyKeycloakAdminRestApi {
|
||||||
|
|
||||||
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(MyKeycloakAdminRestApi.class));
|
||||||
|
|
||||||
|
private final KeycloakClientConfiguration configuration;
|
||||||
|
|
||||||
|
protected RealmResource realm;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public MyKeycloakAdminRestApi(KeycloakClientConfiguration configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
try (Keycloak client = this.initClient()) {
|
||||||
|
this.realm = client.realm(configuration.getProperties().getRealm());
|
||||||
|
}
|
||||||
|
logger.info("Custom Keycloak client initialized. Keycloak serving from {}", configuration.getProperties().getServerUrl().replace("/auth", "").replaceAll("https?://", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyUsersModule users() {
|
||||||
|
return MyModules.getUsersModule(this.realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupsModule groups() {
|
||||||
|
return Modules.getGroupsModule(this.realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientsModule clients() {
|
||||||
|
return Modules.getClientsModule(this.realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Keycloak initClient() {
|
||||||
|
return KeycloakBuilder.builder().serverUrl(this.configuration.getProperties().getServerUrl()).realm(this.configuration.getProperties().getRealm()).username(this.configuration.getProperties().getUsername()).password(this.configuration.getProperties().getPassword()).clientId(this.configuration.getProperties().getClientId()).clientSecret(this.configuration.getProperties().getClientSecret()).grantType("password").build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package eu.eudat.service.keycloak;
|
||||||
|
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
|
||||||
|
public class MyModules {
|
||||||
|
|
||||||
|
public static MyUsersModule getUsersModule(RealmResource realm) {
|
||||||
|
return new MyUsersModule(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package eu.eudat.service.keycloak;
|
||||||
|
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MyUsersModule {
|
||||||
|
private final RealmResource realm;
|
||||||
|
|
||||||
|
MyUsersModule(RealmResource realm) {
|
||||||
|
this.realm = realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UserRepresentation> getUsers() {
|
||||||
|
return this.realm.users().list();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserRepresentation findUserById(String id) {
|
||||||
|
return this.realm.users().get(id).toRepresentation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UserRepresentation> findUsersByUsername(String username) {
|
||||||
|
return this.realm.users().search(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserRepresentation findUserByUsername(String username) {
|
||||||
|
return this.realm.users().search(username, true).stream().findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserRepresentation addUser(UserRepresentation user) {
|
||||||
|
return this.realm.users().create(user).readEntity(UserRepresentation.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUserToGroup(String userId, String groupId) {
|
||||||
|
this.realm.users().get(userId).joinGroup(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeUserFromGroup(String userId, String groupId) {
|
||||||
|
this.realm.users().get(userId).leaveGroup(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GroupRepresentation> getGroups(String userId) {
|
||||||
|
return this.realm.users().get(userId).groups();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateUser(String userId, UserRepresentation user) {
|
||||||
|
UserRepresentation existing = this.realm.users().get(userId).toRepresentation();
|
||||||
|
existing.setFirstName(user.getFirstName());
|
||||||
|
existing.setLastName(user.getLastName());
|
||||||
|
existing.setEnabled(user.isEnabled());
|
||||||
|
existing.setAttributes(user.getAttributes());
|
||||||
|
existing.setClientRoles(user.getClientRoles());
|
||||||
|
this.realm.users().get(userId).update(existing);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,8 @@ import eu.eudat.model.persist.UserPersist;
|
||||||
import eu.eudat.model.persist.UserRolePatchPersist;
|
import eu.eudat.model.persist.UserRolePatchPersist;
|
||||||
import eu.eudat.query.UserQuery;
|
import eu.eudat.query.UserQuery;
|
||||||
import eu.eudat.query.UserRoleQuery;
|
import eu.eudat.query.UserRoleQuery;
|
||||||
|
import eu.eudat.service.keycloak.KeycloakRole;
|
||||||
|
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;
|
||||||
import gr.cite.tools.data.deleter.DeleterFactory;
|
import gr.cite.tools.data.deleter.DeleterFactory;
|
||||||
|
@ -80,6 +82,9 @@ public class UserServiceImpl implements UserService {
|
||||||
private final JsonHandlingService jsonHandlingService;
|
private final JsonHandlingService jsonHandlingService;
|
||||||
private final QueryFactory queryFactory;
|
private final QueryFactory queryFactory;
|
||||||
private final UserScope userScope;
|
private final UserScope userScope;
|
||||||
|
|
||||||
|
private final KeycloakService keycloakService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public UserServiceImpl(
|
public UserServiceImpl(
|
||||||
EntityManager entityManager,
|
EntityManager entityManager,
|
||||||
|
@ -92,7 +97,7 @@ public class UserServiceImpl implements UserService {
|
||||||
EventBroker eventBroker,
|
EventBroker eventBroker,
|
||||||
JsonHandlingService jsonHandlingService,
|
JsonHandlingService jsonHandlingService,
|
||||||
QueryFactory queryFactory,
|
QueryFactory queryFactory,
|
||||||
UserScope userScope) {
|
UserScope userScope, KeycloakService keycloakService) {
|
||||||
this.entityManager = entityManager;
|
this.entityManager = entityManager;
|
||||||
this.authorizationService = authorizationService;
|
this.authorizationService = authorizationService;
|
||||||
this.deleterFactory = deleterFactory;
|
this.deleterFactory = deleterFactory;
|
||||||
|
@ -104,6 +109,7 @@ public class UserServiceImpl implements UserService {
|
||||||
this.jsonHandlingService = jsonHandlingService;
|
this.jsonHandlingService = jsonHandlingService;
|
||||||
this.queryFactory = queryFactory;
|
this.queryFactory = queryFactory;
|
||||||
this.userScope = userScope;
|
this.userScope = userScope;
|
||||||
|
this.keycloakService = keycloakService;
|
||||||
}
|
}
|
||||||
|
|
||||||
//region persist
|
//region persist
|
||||||
|
@ -211,6 +217,7 @@ public class UserServiceImpl implements UserService {
|
||||||
item.setRole(roleName);
|
item.setRole(roleName);
|
||||||
item.setCreatedAt(Instant.now());
|
item.setCreatedAt(Instant.now());
|
||||||
this.entityManager.persist(item);
|
this.entityManager.persist(item);
|
||||||
|
this.keycloakService.assignClientRoleToUser(data.getId(), null, KeycloakRole.valueOf(roleName));
|
||||||
}
|
}
|
||||||
foundIds.add(item.getId());
|
foundIds.add(item.getId());
|
||||||
}
|
}
|
||||||
|
@ -218,6 +225,7 @@ 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.removeClientRoleFromUser(data.getId(), null, KeycloakRole.valueOf(x.getRole())));
|
||||||
this.deleterFactory.deleter(UserRoleDeleter.class).deleteAndSave(toDelete);
|
this.deleterFactory.deleter(UserRoleDeleter.class).deleteAndSave(toDelete);
|
||||||
|
|
||||||
this.entityManager.flush();
|
this.entityManager.flush();
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue