Skip to content
Snippets Groups Projects
Commit 33522bef authored by OZGCloud's avatar OZGCloud
Browse files

OZG-3961 OZG-4082 do add client roles on update user; tiny cleanup

parent 2e8cab84
No related branches found
No related tags found
No related merge requests found
...@@ -38,20 +38,20 @@ class KeycloakUserPreconditionService { ...@@ -38,20 +38,20 @@ class KeycloakUserPreconditionService {
@Autowired @Autowired
private KeycloakGenericRemoteService keycloakGenericRemoteService; private KeycloakGenericRemoteService keycloakGenericRemoteService;
Optional<String> getReconcilePreconditionErrors(OzgKeycloakUser user) { public Optional<String> getReconcilePreconditionErrors(OzgKeycloakUser user) {
String namespace = user.getMetadata().getNamespace(); var namespace = user.getMetadata().getNamespace();
if (!keycloakGenericRemoteService.realmExists(namespace)) { if (!keycloakGenericRemoteService.realmExists(namespace)) {
return Optional.of("Realm " + namespace + " does not yet exist"); return Optional.of(String.format("Realm %s does not yet exist", namespace));
} }
Optional<String> clientErorr = clientsExists(user, namespace); var clientErorr = clientsExists(user, namespace);
if (clientErorr.isPresent()) { if (clientErorr.isPresent()) {
return clientErorr; return clientErorr;
} }
Optional<String> groupError = groupsExists(user, namespace); var groupError = groupsExists(user, namespace);
if (groupError.isPresent()) { if (groupError.isPresent()) {
return groupError; return groupError;
} }
...@@ -64,7 +64,7 @@ class KeycloakUserPreconditionService { ...@@ -64,7 +64,7 @@ class KeycloakUserPreconditionService {
.map(KeycloakUserSpecClientRole::getClientId) .map(KeycloakUserSpecClientRole::getClientId)
.map(clientId -> keycloakGenericRemoteService.getByClientId(clientId, realm)) .map(clientId -> keycloakGenericRemoteService.getByClientId(clientId, realm))
.filter(Optional::isEmpty) .filter(Optional::isEmpty)
.map(clientId -> "Client " + clientId + " does not yet exist") .map(clientId -> String.format("Client %s does not yet exist", clientId))
.findAny(); .findAny();
} }
...@@ -72,7 +72,7 @@ class KeycloakUserPreconditionService { ...@@ -72,7 +72,7 @@ class KeycloakUserPreconditionService {
return user.getSpec().getKeycloakUser().getGroups().stream() return user.getSpec().getKeycloakUser().getGroups().stream()
.map(KeycloakUserSpecUserGroup::getName) .map(KeycloakUserSpecUserGroup::getName)
.filter(groupName -> !keycloakGenericRemoteService.groupExists(groupName, realm)) .filter(groupName -> !keycloakGenericRemoteService.groupExists(groupName, realm))
.map(groupName -> "Group " + groupName + " for realm " + realm + " does not yet exist") .map(groupName -> String.format("Group %s for realm %s does not exist yet", groupName, realm))
.findAny(); .findAny();
} }
} }
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
package de.ozgcloud.operator.keycloak.user; package de.ozgcloud.operator.keycloak.user;
import java.time.Duration; import java.time.Duration;
import java.util.Optional;
import java.util.logging.Level; import java.util.logging.Level;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -40,9 +39,9 @@ import io.javaoperatorsdk.operator.api.reconciler.Reconciler; ...@@ -40,9 +39,9 @@ import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log
@ControllerConfiguration @ControllerConfiguration
@Component @Component
@Log
public class KeycloakUserReconciler implements Reconciler<OzgKeycloakUser>, Cleaner<OzgKeycloakUser> { public class KeycloakUserReconciler implements Reconciler<OzgKeycloakUser>, Cleaner<OzgKeycloakUser> {
@Autowired @Autowired
...@@ -53,54 +52,67 @@ public class KeycloakUserReconciler implements Reconciler<OzgKeycloakUser>, Clea ...@@ -53,54 +52,67 @@ public class KeycloakUserReconciler implements Reconciler<OzgKeycloakUser>, Clea
@Override @Override
public UpdateControl<OzgKeycloakUser> reconcile(OzgKeycloakUser resource, Context<OzgKeycloakUser> context) { public UpdateControl<OzgKeycloakUser> reconcile(OzgKeycloakUser resource, Context<OzgKeycloakUser> context) {
try { try {
log.info("Reconciling KeycloakUser " + resource.getMetadata().getName()); log.info(String.format("Reconciling user %s...", resource.getMetadata().getName()));
String namespace = resource.getMetadata().getNamespace();
Optional<String> preconditionError = preconditionService.getReconcilePreconditionErrors(resource); var preconditionError = preconditionService.getReconcilePreconditionErrors(resource);
if (preconditionError.isPresent()) { if (preconditionError.isPresent()) {
return buildStatusInProgress(resource, preconditionError.get()); return buildStatusInProgress(resource, preconditionError.get());
} }
keycloakUserService.createOrUpdateUser(resource.getSpec(), namespace); keycloakUserService.createOrUpdateUser(resource.getSpec(), resource.getMetadata().getNamespace());
return buildStatusOk(resource); resource.setStatus(buildOzgKeycloakUserOkStatus());
return UpdateControl.updateStatus(resource);
} catch (Exception e) { } catch (Exception e) {
log.log(Level.SEVERE, log.log(Level.SEVERE, String.format("Could not reconcile user %s for namespace %s: %s", resource.getMetadata().getName(),
"Could not reconcile user " + resource.getMetadata().getName() + " in namespace " + resource.getMetadata().getNamespace() + ": " resource.getMetadata().getNamespace(), e.getMessage()), e);
+ e.getMessage(),
e); resource.setStatus(buildOzgKeycloakUserErrorStatus(e.getMessage()));
resource.setStatus(OzgKeycloakUserStatus.builder().status(OzgCustomResourceStatus.ERROR).message(e.getMessage()).build()); return createRescheduleUpdateControl(resource);
return UpdateControl.updateStatus(resource).rescheduleAfter(Duration.ofSeconds(Config.RECONCILER_RETRY_SECONDS));
} }
} }
private UpdateControl<OzgKeycloakUser> buildStatusOk(OzgKeycloakUser resource) { private OzgKeycloakUserStatus buildOzgKeycloakUserOkStatus() {
resource.setStatus(OzgKeycloakUserStatus.builder().status(OzgCustomResourceStatus.OK).message(null).build()); return buildOzgKeycloakUserStatus(OzgCustomResourceStatus.OK, null);
return UpdateControl.updateStatus(resource); }
private OzgKeycloakUserStatus buildOzgKeycloakUserErrorStatus(String message) {
return buildOzgKeycloakUserStatus(OzgCustomResourceStatus.ERROR, message);
} }
private UpdateControl<OzgKeycloakUser> buildStatusInProgress(OzgKeycloakUser resource, String errorMessage) { private UpdateControl<OzgKeycloakUser> buildStatusInProgress(OzgKeycloakUser resource, String errorMessage) {
log.log(Level.INFO, log.log(Level.WARNING, String.format("Could not reconcile user %s in namespace %s: %s", resource.getMetadata().getName(),
"Could not yet reconcile user " + resource.getMetadata().getName() + " in namespace " + resource.getMetadata().getNamespace() + ": " resource.getMetadata().getNamespace(), errorMessage));
+ errorMessage);
resource.setStatus(OzgKeycloakUserStatus.builder().status(OzgCustomResourceStatus.IN_PROGRESS).message(errorMessage).build()); resource.setStatus(buildOzgKeycloakUserInProgressStatus(errorMessage));
return UpdateControl.updateStatus(resource).rescheduleAfter(Duration.ofSeconds(Config.RECONCILER_RETRY_SECONDS)); return createRescheduleUpdateControl(resource);
} }
@Override private OzgKeycloakUserStatus buildOzgKeycloakUserInProgressStatus(String message) {
public DeleteControl cleanup(OzgKeycloakUser crd, Context<OzgKeycloakUser> context) { return buildOzgKeycloakUserStatus(OzgCustomResourceStatus.IN_PROGRESS, message);
}
private OzgKeycloakUserStatus buildOzgKeycloakUserStatus(OzgCustomResourceStatus status, String message) {
return OzgKeycloakUserStatus.builder().status(status).message(message).build();
}
private UpdateControl<OzgKeycloakUser> createRescheduleUpdateControl(OzgKeycloakUser userResource) {
return UpdateControl.updateStatus(userResource).rescheduleAfter(Duration.ofSeconds(Config.RECONCILER_RETRY_SECONDS));
}
@Override
public DeleteControl cleanup(OzgKeycloakUser userResource, Context<OzgKeycloakUser> context) {
try { try {
log.info("Deleting KeycloakUser " + crd.getMetadata().getName()); log.info(String.format("Deleting KeycloakUser %s", userResource.getMetadata().getName()));
keycloakUserService.deleteUser(crd.getSpec(), crd.getMetadata().getNamespace()); keycloakUserService.deleteUser(userResource.getSpec(), userResource.getMetadata().getNamespace());
return DeleteControl.defaultDelete(); return DeleteControl.defaultDelete();
} catch (Exception e) { } catch (Exception e) {
log.log(Level.SEVERE, "Could not delete user " + crd.getMetadata().getName() + " in namespace " + crd.getMetadata().getNamespace(), e); log.log(Level.SEVERE, String.format("Could not delete user %s in namespace %s", userResource.getMetadata().getName(),
userResource.getMetadata().getNamespace()), e);
return DeleteControl.defaultDelete(); return DeleteControl.defaultDelete();
} }
} }
......
...@@ -26,8 +26,6 @@ package de.ozgcloud.operator.keycloak.user; ...@@ -26,8 +26,6 @@ package de.ozgcloud.operator.keycloak.user;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import javax.ws.rs.core.Response;
import org.keycloak.admin.client.CreatedResponseUtil; import org.keycloak.admin.client.CreatedResponseUtil;
import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
...@@ -50,30 +48,46 @@ class KeycloakUserRemoteService { ...@@ -50,30 +48,46 @@ class KeycloakUserRemoteService {
@Autowired @Autowired
private KeycloakGenericRemoteService keycloakGenericRemoteService; private KeycloakGenericRemoteService keycloakGenericRemoteService;
void createUser(UserRepresentation user, String realm) { public void createUser(UserRepresentation user, String namespace) {
var realmResource = getRealm(namespace);
RealmResource realmResource = keycloak.realm(realm);
Response response = realmResource.users().create(user); var response = realmResource.users().create(user);
KeycloakResultParser.parseCreatedResponse(response); KeycloakResultParser.parseCreatedResponse(response);
String userId = CreatedResponseUtil.getCreatedId(response); var userId = CreatedResponseUtil.getCreatedId(response);
addClientRoles(userId, realmResource, namespace, user);
}
public Optional<UserRepresentation> getUserByName(String userName, String namespace) {
return getRealm(namespace).users().search(userName, true).stream().findFirst();
}
public void deleteUser(String userId, String namespace) {
getRealm(namespace).users().delete(userId);
}
public void updateUser(UserRepresentation user, String namespace) {
var realmResource = getRealm(namespace);
addClientRoles(userId, realmResource, realm, user); realmResource.users().get(user.getId()).update(user);
addClientRoles(user.getId(), realmResource, namespace, user);
} }
void addClientRoles(String userId, RealmResource realmResource, String realm, UserRepresentation user) { private RealmResource getRealm(String namespace) {
return keycloak.realm(namespace);
}
user.getClientRoles().keySet() void addClientRoles(String userId, RealmResource realmResource, String namespace, UserRepresentation user) {
.forEach(clientId -> { user.getClientRoles().keySet().forEach(clientId -> {
ClientRepresentation appClient = getRealmClient(realmResource, clientId); var realmClient = getRealmClient(realmResource, clientId);
user.getClientRoles().get(clientId).stream() user.getClientRoles().get(clientId).stream()
.map(clientRoleName -> keycloakGenericRemoteService.getClientRole(clientRoleName, appClient.getId(), realm) .map(clientRoleName -> keycloakGenericRemoteService.getClientRole(clientRoleName, realmClient.getId(), namespace)
.orElseThrow(() -> new KeycloakException( .orElseThrow(() -> new KeycloakException(
"Role " + clientRoleName + " not found for client with clientId " + clientId + " in realm " + realm))) "Role " + clientRoleName + " not found for client with clientId " + clientId + " in realm " + namespace)))
.forEach(clientRole -> addClientRoleToUser(clientRole, realmResource, userId, appClient)); .forEach(clientRole -> addClientRoleToUser(clientRole, realmResource, userId, realmClient));
}); });
} }
ClientRepresentation getRealmClient(RealmResource realmResource, String clientId) { ClientRepresentation getRealmClient(RealmResource realmResource, String clientId) {
...@@ -85,16 +99,4 @@ class KeycloakUserRemoteService { ...@@ -85,16 +99,4 @@ class KeycloakUserRemoteService {
void addClientRoleToUser(RoleRepresentation clientRole, RealmResource realmResource, String userId, ClientRepresentation appClient) { void addClientRoleToUser(RoleRepresentation clientRole, RealmResource realmResource, String userId, ClientRepresentation appClient) {
realmResource.users().get(userId).roles().clientLevel(appClient.getId()).add(Arrays.asList(clientRole)); realmResource.users().get(userId).roles().clientLevel(appClient.getId()).add(Arrays.asList(clientRole));
} }
Optional<UserRepresentation> getUserByName(String username, String realm) {
return keycloak.realm(realm).users().search(username, true).stream().findFirst();
}
void deleteUser(String userId, String realm) {
keycloak.realm(realm).users().delete(userId);
}
void updateUser(UserRepresentation user, String realm) {
keycloak.realm(realm).users().get(user.getId()).update(user);
}
} }
\ No newline at end of file
...@@ -37,13 +37,13 @@ class KeycloakUserService { ...@@ -37,13 +37,13 @@ class KeycloakUserService {
@Autowired @Autowired
private KeycloakUserMapper userMapper; private KeycloakUserMapper userMapper;
void createOrUpdateUser(OzgKeycloakUserSpec userSpec, String namespace) { public void createOrUpdateUser(OzgKeycloakUserSpec userSpec, String namespace) {
remoteService.getUserByName(userSpec.getKeycloakUser().getUsername(), namespace) remoteService.getUserByName(userSpec.getKeycloakUser().getUsername(), namespace)
.ifPresentOrElse(existingUser -> remoteService.updateUser(userMapper.update(existingUser, userSpec), namespace), .ifPresentOrElse(existingUser -> remoteService.updateUser(userMapper.update(existingUser, userSpec), namespace),
() -> remoteService.createUser(userMapper.map(userSpec), namespace)); () -> remoteService.createUser(userMapper.map(userSpec), namespace));
} }
void deleteUser(OzgKeycloakUserSpec userSpec, String namespace) { public void deleteUser(OzgKeycloakUserSpec userSpec, String namespace) {
Optional.of(userSpec) Optional.of(userSpec)
.map(userMapper::map) .map(userMapper::map)
.flatMap(keycloakUser -> remoteService.getUserByName(keycloakUser.getUsername(), namespace)) .flatMap(keycloakUser -> remoteService.getUserByName(keycloakUser.getUsername(), namespace))
......
...@@ -35,16 +35,14 @@ import java.util.Optional; ...@@ -35,16 +35,14 @@ import java.util.Optional;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource; import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource; import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.admin.client.resource.RoleScopeResource; import org.keycloak.admin.client.resource.RoleScopeResource;
import org.keycloak.admin.client.resource.RolesResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource; import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
...@@ -84,16 +82,10 @@ class KeycloakUserRemoteServiceTest { ...@@ -84,16 +82,10 @@ class KeycloakUserRemoteServiceTest {
@Mock @Mock
private ClientsResource clientsResource; private ClientsResource clientsResource;
@Mock @Mock
private ClientResource clientResource;
@Mock
private ClientRepresentation clientRepresentation; private ClientRepresentation clientRepresentation;
@Mock @Mock
private RoleRepresentation roleRepresentation; private RoleRepresentation roleRepresentation;
@Mock @Mock
private RolesResource rolesResource;
@Mock
private RoleResource roleResource;
@Mock
private RoleScopeResource roleScopeResource; private RoleScopeResource roleScopeResource;
@Mock @Mock
private RoleMappingResource roleMappingResource; private RoleMappingResource roleMappingResource;
...@@ -113,8 +105,6 @@ class KeycloakUserRemoteServiceTest { ...@@ -113,8 +105,6 @@ class KeycloakUserRemoteServiceTest {
@Test @Test
void shouldThrowOnResponseError() { void shouldThrowOnResponseError() {
when(response.getStatusInfo()).thenReturn(Response.Status.INTERNAL_SERVER_ERROR); when(response.getStatusInfo()).thenReturn(Response.Status.INTERNAL_SERVER_ERROR);
assertThrows(KeycloakException.class, () -> userRemoteService.createUser(USER_REPRESENTATION, REALM)); assertThrows(KeycloakException.class, () -> userRemoteService.createUser(USER_REPRESENTATION, REALM));
...@@ -209,6 +199,7 @@ class KeycloakUserRemoteServiceTest { ...@@ -209,6 +199,7 @@ class KeycloakUserRemoteServiceTest {
} }
} }
@DisplayName("Update user")
@Nested @Nested
class TestUpdateUser { class TestUpdateUser {
...@@ -218,7 +209,9 @@ class KeycloakUserRemoteServiceTest { ...@@ -218,7 +209,9 @@ class KeycloakUserRemoteServiceTest {
void init() { void init() {
when(keycloak.realm(REALM)).thenReturn(realmResource); when(keycloak.realm(REALM)).thenReturn(realmResource);
when(realmResource.users()).thenReturn(usersResource); when(realmResource.users()).thenReturn(usersResource);
when(usersResource.get(USERID)).thenReturn(userResource); when(usersResource.get(any())).thenReturn(userResource);
doNothing().when(userRemoteService).addClientRoles(any(), any(), eq(REALM), any());
} }
@Test @Test
...@@ -230,6 +223,13 @@ class KeycloakUserRemoteServiceTest { ...@@ -230,6 +223,13 @@ class KeycloakUserRemoteServiceTest {
verify(userResource).update(user); verify(userResource).update(user);
} }
@Test
void shouldCallAddClientRoles() {
userRemoteService.updateUser(USER_REPRESENTATION, REALM);
verify(userRemoteService).addClientRoles(isNull(), eq(realmResource), eq(REALM), eq(USER_REPRESENTATION));
}
} }
@Nested @Nested
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment