Skip to content

Commit

Permalink
Merge pull request #46 from mgdgl/master
Browse files Browse the repository at this point in the history
Ignoring role name case on service account role synchronization
  • Loading branch information
manu11th authored Jul 26, 2021
2 parents 5e3f0ac + e366874 commit 5f07e29
Show file tree
Hide file tree
Showing 8 changed files with 820 additions and 53 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@
</exclusions>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.20.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
Expand All @@ -19,8 +18,6 @@
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.admin.client.resource.RolesResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
Expand All @@ -30,15 +27,18 @@ public class ClientController extends KubernetesController<ClientResource> {

final KeycloakController keycloak;
final AssignedClientScopesSyncer assignedClientScopesSyncer;
final ServiceAccountRoleAssignmentSynchronizer serviceAccountRoleAssignmentSynchronizer;

public ClientController(KeycloakController keycloak,
KubernetesClient kubernetes,
AssignedClientScopesSyncer assignedClientScopesSyncer) {
AssignedClientScopesSyncer assignedClientScopesSyncer,
ServiceAccountRoleAssignmentSynchronizer serviceAccountRoleAssignmentSynchronizer) {

super(kubernetes, ClientResource.DEFINITION, ClientResource.class, ClientResource.ClientResourceList.class,
ClientResource.ClientResourceDoneable.class);
this.keycloak = keycloak;
this.assignedClientScopesSyncer = assignedClientScopesSyncer;
this.serviceAccountRoleAssignmentSynchronizer = serviceAccountRoleAssignmentSynchronizer;
}

@Override
Expand Down Expand Up @@ -94,6 +94,7 @@ public void apply(ClientResource clientResource) {

if (clientResource.getSpec().getServiceAccountsEnabled() == Boolean.TRUE) {
manageServiceAccountRealmRoles(realmResource, clientUuid, clientResource);

}
updateStatus(clientResource, null);
} catch (RuntimeException e) {
Expand Down Expand Up @@ -364,57 +365,9 @@ void manageMapper(RealmResource realmResource, String clientUuid, ClientResource
}

private void manageServiceAccountRealmRoles(RealmResource realmResource, String clientUuid, ClientResource clientResource) {
var keycloak = clientResource.getSpec().getKeycloak();
var realm = clientResource.getSpec().getRealm();
var clientId = clientResource.getSpec().getClientId();

org.keycloak.admin.client.resource.ClientResource keycloakClientResource = realmResource.clients().get(clientUuid);
RoleMappingResource serviceAccountRolesMapping = realmResource.users()
.get(keycloakClientResource.getServiceAccountUser().getId())
.roles();

List<String> requestedRealmRoles = clientResource.getSpec().getServiceAccountRealmRoles();

removeRoleMappingNotRequestedAnymore(keycloak, realm, clientId, serviceAccountRolesMapping, requestedRealmRoles);

List<String> realmRoleNames = realmResource.roles().list().stream().map(RoleRepresentation::getName).collect(Collectors.toList());
List<String> rolesToCreate = requestedRealmRoles.stream().filter(role -> !realmRoleNames.contains(role)).collect(Collectors.toList());
createRolesInRealm(keycloak, realm, clientId, realmResource.roles(), rolesToCreate);

List<RoleRepresentation> rolesToBind = realmResource.roles().list().stream()
.filter(roleInRealm -> requestedRealmRoles.contains(roleInRealm.getName()))
.collect(Collectors.toList());
this.serviceAccountRoleAssignmentSynchronizer.synchronizeServiceAccountRealmRoles(realmResource, clientResource, clientUuid);

serviceAccountRolesMapping
.realmLevel()
.add(rolesToBind);
}

private void createRolesInRealm(String keycloak, String realm, String clientId, RolesResource rolesResource, List<String> rolesToCreate) {
for (String roleToCreate : rolesToCreate) {
var representation = new RoleRepresentation();
representation.setName(roleToCreate);
representation.setClientRole(false);
representation.setComposite(false);
rolesResource.create(representation);
log.info("{}/{}/{}: created realm role {}", keycloak, realm, clientId, roleToCreate);
}
}

private void removeRoleMappingNotRequestedAnymore(String keycloak, String realm, String clientId, RoleMappingResource serviceAccountRoleMapping, List<String> requestedRealmRoles) {
List rolesToRemove = new ArrayList();

List<RoleRepresentation> currentlyMappedRealmRoles = serviceAccountRoleMapping.getAll().getRealmMappings();
for (RoleRepresentation currentlyMappedRole : currentlyMappedRealmRoles) {
if (!requestedRealmRoles.contains(currentlyMappedRole.getName())) {
rolesToRemove.add(currentlyMappedRole);
log.info("{}/{}/{}: deleted role not requested anymore {}",
keycloak, realm, clientId, currentlyMappedRole.getName());
}
}
serviceAccountRoleMapping
.realmLevel()
.remove(rolesToRemove);
}

private void manageRoles(RealmResource realmResource, String clientUuid, ClientResource clientResource) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.kiwigrid.keycloak.controller.client;

import java.util.List;
import java.util.stream.Collectors;

import javax.inject.Singleton;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.representations.idm.RoleRepresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class ServiceAccountRoleAssignment {
private final Logger LOG = LoggerFactory.getLogger(getClass());

public List<RoleRepresentation> findAssignedRolesToRemoveWith(RealmResource realmResource,
ClientResource clientResource, String clientUuid)
{
List<RoleRepresentation> assignedServiceAccountRoles = getAssignedServiceAccountRoles(
realmResource,
clientUuid);
List<String> requestedServiceAccountRoleNames = getRequestedServiceAccountRoleNamesFrom(
clientResource);

return assignedServiceAccountRoles
.stream()
.filter(roleRepresentation -> !requestedServiceAccountRoleNames
.stream()
.anyMatch(roleName -> roleName.equalsIgnoreCase(roleRepresentation.getName())))
.collect(Collectors.toList());
}

private List<String> getRequestedServiceAccountRoleNamesFrom(ClientResource clientResourceDefinition) {
return clientResourceDefinition
.getSpec()
.getServiceAccountRealmRoles();
}

private List<RoleRepresentation> getAssignedServiceAccountRoles(RealmResource realmResource, String clientUuid) {
org.keycloak.admin.client.resource.ClientResource keycloakClientResource = realmResource.clients()
.get(clientUuid);
return realmResource.users()
.get(keycloakClientResource.getServiceAccountUser().getId())
.roles().getAll().getRealmMappings();
}

public List<RoleRepresentation> findRolesToAssignWith(RealmResource realmResource,
ClientResource clientResourceDefinition, String clientUuid)
{
List<RoleRepresentation> assignedServiceAccountRoles = getAssignedServiceAccountRoles(
realmResource,
clientUuid);
List<String> requestedServiceAccountRoleNames = getRequestedServiceAccountRoleNamesFrom(
clientResourceDefinition);
List<RoleRepresentation> serviceAccountRealmRoles = realmResource.roles().list();

var unassignedRequestedRoleNames = requestedServiceAccountRoleNames.stream()
.filter(roleName -> !assignedServiceAccountRoles.stream()
.anyMatch(roleRepresentation -> roleName.equalsIgnoreCase(roleRepresentation.getName())))
.collect(Collectors.toList());

return serviceAccountRealmRoles.stream()
.filter(roleRepresentation -> unassignedRequestedRoleNames.stream()
.anyMatch(roleName -> roleRepresentation.getName().equalsIgnoreCase(roleName)))
.collect(Collectors.toList());
}

public List<RoleRepresentation> findRequestedRolesToCreateWith(RealmResource realmResource,
ClientResource clientResourceDefinition)
{
List<RoleRepresentation> serviceAccountRealmRoleMappings = realmResource.roles().list();

return getRequestedServiceAccountRoleNamesFrom(clientResourceDefinition)
.stream()
.filter(roleName -> !serviceAccountRealmRoleMappings
.stream()
.anyMatch(roleRepresentation -> roleRepresentation
.getName()
.equalsIgnoreCase(roleName)))
.map(this::createRoleRepresentation)
.collect(Collectors.toList());
}

RoleRepresentation createRoleRepresentation(String roleName) {
var roleRepresentation = new RoleRepresentation();
roleRepresentation.setName(roleName);
roleRepresentation.setClientRole(false);
roleRepresentation.setComposite(false);
return roleRepresentation;
}

public RoleMappingResource getServiceAccountRoleMappingsFor(RealmResource realmResource, String clientUuid) {
return realmResource.users()
.get(realmResource
.clients()
.get(clientUuid)
.getServiceAccountUser()
.getId()).roles();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.kiwigrid.keycloak.controller.client;

import java.util.List;
import java.util.stream.Collectors;

import javax.inject.Singleton;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.representations.idm.RoleRepresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class ServiceAccountRoleAssignmentSynchronizer {
private final Logger LOG = LoggerFactory.getLogger(getClass());
private final ServiceAccountRoleAssignment serviceAccountRoleAssignment;

public ServiceAccountRoleAssignmentSynchronizer(ServiceAccountRoleAssignment serviceAccountRoleAssignment) {
this.serviceAccountRoleAssignment = serviceAccountRoleAssignment;
}

public void synchronizeServiceAccountRealmRoles(RealmResource realmResource, ClientResource clientResourceDefinition, String clientUuid) {
var keycloak = clientResourceDefinition.getSpec().getKeycloak();
var realm = clientResourceDefinition.getSpec().getRealm();
var clientId = clientResourceDefinition.getSpec().getClientId();

RoleMappingResource serviceAccountRoleMappings = serviceAccountRoleAssignment.getServiceAccountRoleMappingsFor(
realmResource,
clientUuid);

List<RoleRepresentation> assignedServiceAccountRolesToRemove = serviceAccountRoleAssignment
.findAssignedRolesToRemoveWith(
realmResource,
clientResourceDefinition,
clientUuid);

if (!assignedServiceAccountRolesToRemove.isEmpty()) {
removeAssignedRolesFromServiceAccount(keycloak,
realm,
clientId,
serviceAccountRoleMappings,
assignedServiceAccountRolesToRemove);
}

List<RoleRepresentation> serviceAccountRealmRolesToCreate = serviceAccountRoleAssignment
.findRequestedRolesToCreateWith(
realmResource,
clientResourceDefinition);

if (!serviceAccountRealmRolesToCreate.isEmpty()) {
createNewRealmRoles(realmResource, keycloak, realm, clientId, serviceAccountRealmRolesToCreate);
}

List<RoleRepresentation> serviceAccountRolesToAssign = serviceAccountRoleAssignment
.findRolesToAssignWith(
realmResource,
clientResourceDefinition,
clientUuid);

if (!serviceAccountRolesToAssign.isEmpty()) {
assignRequestedRolesToServiceAccount(keycloak,
realm,
clientId,
serviceAccountRoleMappings,
serviceAccountRolesToAssign);
}
}

private void removeAssignedRolesFromServiceAccount(String keycloak, String realm, String clientId, RoleMappingResource serviceAccountRoleMappings, List<RoleRepresentation> assignedServiceAccountRolesToRemove) {
serviceAccountRoleMappings.realmLevel().remove(assignedServiceAccountRolesToRemove);
LOG.info("{}/{}/{}: deleted roles not requested anymore {}",
keycloak,
realm,
clientId,
assignedServiceAccountRolesToRemove.stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toList()));
}

private void createNewRealmRoles(RealmResource realmResource, String keycloak, String realm, String clientId, List<RoleRepresentation> serviceAccountRealmRolesToCreate) {
serviceAccountRealmRolesToCreate.stream()
.forEach(roleRepresentation -> realmResource.roles().create(roleRepresentation));
LOG.info("{}/{}/{}: created realm roles {}",
keycloak,
realm,
clientId,
serviceAccountRealmRolesToCreate.stream()
.map(RoleRepresentation::getName)
.collect(Collectors.toList()));
}

private void assignRequestedRolesToServiceAccount(String keycloak, String realm, String clientId, RoleMappingResource serviceAccountRoleMappings, List<RoleRepresentation> serviceAccountRolesToAssign) {
serviceAccountRoleMappings.realmLevel().add(serviceAccountRolesToAssign);
LOG.info("{}/{}/{}: assigned realm roles {}",
keycloak,
realm,
clientId,
serviceAccountRolesToAssign.stream().map(RoleRepresentation::getName).collect(Collectors.toList()));
}
}
Loading

0 comments on commit 5f07e29

Please sign in to comment.