mirror of
https://github.com/bmlong137/alfresco-keycloak.git
synced 2025-09-10 14:11:09 +00:00
Add inverse mapping / check
This commit is contained in:
@@ -130,7 +130,22 @@ public interface IDMClient
|
|||||||
* the processor handling the loaded roles
|
* the processor handling the loaded roles
|
||||||
* @return the number of processed roles
|
* @return the number of processed roles
|
||||||
*/
|
*/
|
||||||
int processRoles(int offset, int roleBatchSize, Consumer<RoleRepresentation> roleProcessor);
|
int processRealmRoles(int offset, int roleBatchSize, Consumer<RoleRepresentation> roleProcessor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and processes a batch of realm roles from Keycloak using an externally specified processor.
|
||||||
|
*
|
||||||
|
* @param search
|
||||||
|
* a search term to filter roles
|
||||||
|
* @param offset
|
||||||
|
* the index of the first role to retrieve
|
||||||
|
* @param roleBatchSize
|
||||||
|
* the number of roles to load in one batch
|
||||||
|
* @param roleProcessor
|
||||||
|
* the processor handling the loaded roles
|
||||||
|
* @return the number of processed roles
|
||||||
|
*/
|
||||||
|
int processRealmRoles(String search, int offset, int roleBatchSize, Consumer<RoleRepresentation> roleProcessor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and processes a batch of client roles from Keycloak using an externally specified processor.
|
* Loads and processes a batch of client roles from Keycloak using an externally specified processor.
|
||||||
@@ -145,5 +160,22 @@ public interface IDMClient
|
|||||||
* the processor handling the loaded roles
|
* the processor handling the loaded roles
|
||||||
* @return the number of processed roles
|
* @return the number of processed roles
|
||||||
*/
|
*/
|
||||||
int processRoles(String clientId, int offset, int roleBatchSize, Consumer<RoleRepresentation> roleProcessor);
|
int processClientRoles(String clientId, int offset, int roleBatchSize, Consumer<RoleRepresentation> roleProcessor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and processes a batch of client roles from Keycloak using an externally specified processor.
|
||||||
|
*
|
||||||
|
* @param clientId
|
||||||
|
* the {@link ClientRepresentation#getId() (technical) ID} of a client from which to process defined roles
|
||||||
|
* @param search
|
||||||
|
* a search term to filter roles
|
||||||
|
* @param offset
|
||||||
|
* the index of the first role to retrieve
|
||||||
|
* @param roleBatchSize
|
||||||
|
* the number of roles to load in one batch
|
||||||
|
* @param roleProcessor
|
||||||
|
* the processor handling the loaded roles
|
||||||
|
* @return the number of processed roles
|
||||||
|
*/
|
||||||
|
int processClientRoles(String clientId, String search, int offset, int roleBatchSize, Consumer<RoleRepresentation> roleProcessor);
|
||||||
}
|
}
|
||||||
|
@@ -310,7 +310,7 @@ public class IDMClientImpl implements InitializingBean, IDMClient
|
|||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int processRoles(final int offset, final int userBatchSize, final Consumer<RoleRepresentation> roleProcessor)
|
public int processRealmRoles(final int offset, final int userBatchSize, final Consumer<RoleRepresentation> roleProcessor)
|
||||||
{
|
{
|
||||||
ParameterCheck.mandatory("roleProcessor", roleProcessor);
|
ParameterCheck.mandatory("roleProcessor", roleProcessor);
|
||||||
|
|
||||||
@@ -335,7 +335,35 @@ public class IDMClientImpl implements InitializingBean, IDMClient
|
|||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int processRoles(final String clientId, final int offset, final int userBatchSize,
|
public int processRealmRoles(final String search, final int offset, final int userBatchSize,
|
||||||
|
final Consumer<RoleRepresentation> roleProcessor)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatory("roleProcessor", roleProcessor);
|
||||||
|
ParameterCheck.mandatoryString("search", search);
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("offset must be a non-negative integer");
|
||||||
|
}
|
||||||
|
if (userBatchSize <= 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("userBatchSize must be a positive integer");
|
||||||
|
}
|
||||||
|
|
||||||
|
final URI uri = KeycloakUriBuilder.fromUri(this.deployment.getAuthServerBaseUrl()).path("/admin/realms/{realm}/roles")
|
||||||
|
.queryParam("first", offset).queryParam("max", userBatchSize).queryParam("search", search)
|
||||||
|
.build(this.deployment.getRealm());
|
||||||
|
|
||||||
|
final int processedRoles = this.processEntityBatch(uri, roleProcessor, RoleRepresentation.class);
|
||||||
|
return processedRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int processClientRoles(final String clientId, final int offset, final int userBatchSize,
|
||||||
final Consumer<RoleRepresentation> roleProcessor)
|
final Consumer<RoleRepresentation> roleProcessor)
|
||||||
{
|
{
|
||||||
ParameterCheck.mandatoryString("clientId", clientId);
|
ParameterCheck.mandatoryString("clientId", clientId);
|
||||||
@@ -358,6 +386,35 @@ public class IDMClientImpl implements InitializingBean, IDMClient
|
|||||||
return processedRoles;
|
return processedRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int processClientRoles(final String clientId, final String search, final int offset, final int userBatchSize,
|
||||||
|
final Consumer<RoleRepresentation> roleProcessor)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatoryString("clientId", clientId);
|
||||||
|
ParameterCheck.mandatoryString("search", search);
|
||||||
|
ParameterCheck.mandatory("roleProcessor", roleProcessor);
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("offset must be a non-negative integer");
|
||||||
|
}
|
||||||
|
if (userBatchSize <= 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("userBatchSize must be a positive integer");
|
||||||
|
}
|
||||||
|
|
||||||
|
final URI uri = KeycloakUriBuilder.fromUri(this.deployment.getAuthServerBaseUrl())
|
||||||
|
.path("/admin/realms/{realm}/clients/{clientId}/roles").queryParam("first", offset).queryParam("max", userBatchSize)
|
||||||
|
.queryParam("search", search).build(this.deployment.getRealm(), clientId);
|
||||||
|
|
||||||
|
final int processedRoles = this.processEntityBatch(uri, roleProcessor, RoleRepresentation.class);
|
||||||
|
return processedRoles;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and processes a batch of generic entities from Keycloak.
|
* Loads and processes a batch of generic entities from Keycloak.
|
||||||
*
|
*
|
||||||
|
@@ -19,6 +19,7 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.alfresco.util.ParameterCheck;
|
||||||
import org.alfresco.util.PropertyCheck;
|
import org.alfresco.util.PropertyCheck;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -73,6 +74,7 @@ public class AggregateRoleNameMapper implements InitializingBean, RoleNameMapper
|
|||||||
@Override
|
@Override
|
||||||
public Optional<String> mapRoleName(final String roleName)
|
public Optional<String> mapRoleName(final String roleName)
|
||||||
{
|
{
|
||||||
|
ParameterCheck.mandatoryString("roleName", roleName);
|
||||||
LOGGER.debug("Mapping role {} using granular mappers {}", roleName, this.granularMappers);
|
LOGGER.debug("Mapping role {} using granular mappers {}", roleName, this.granularMappers);
|
||||||
Optional<String> mappedName = Optional.empty();
|
Optional<String> mappedName = Optional.empty();
|
||||||
for (final RoleNameMapper mapper : this.granularMappers)
|
for (final RoleNameMapper mapper : this.granularMappers)
|
||||||
@@ -87,4 +89,24 @@ public class AggregateRoleNameMapper implements InitializingBean, RoleNameMapper
|
|||||||
return mappedName;
|
return mappedName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<String> mapAuthorityName(final String authorityName)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatoryString("authorityName", authorityName);
|
||||||
|
LOGGER.debug("Mapping authority name {} using granular mappers {}", authorityName, this.granularMappers);
|
||||||
|
Optional<String> mappedName = Optional.empty();
|
||||||
|
for (final RoleNameMapper mapper : this.granularMappers)
|
||||||
|
{
|
||||||
|
mappedName = mapper.mapAuthorityName(authorityName).map(name -> this.upperCaseRoles ? name.toLowerCase(Locale.ENGLISH) : name);
|
||||||
|
if (mappedName.isPresent())
|
||||||
|
{
|
||||||
|
LOGGER.debug("Mapped authority name {} to {} using granular mapper {}", authorityName, mappedName, mapper);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mappedName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ package de.acosix.alfresco.keycloak.repo.roles;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This no-op implementation class of a role service may be used as a default implemenation in a subsystem proxy to avoid failing if no
|
* This no-op implementation class of a role service may be used as a default implemenation in a subsystem proxy to avoid failing if no
|
||||||
@@ -87,4 +88,33 @@ public class NoOpRoleServiceImpl implements RoleService
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isMappedFromKeycloak(final String authorityName)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<String> getRoleName(final String authorityName)
|
||||||
|
{
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<String> getClientFromRole(final String authorityName)
|
||||||
|
{
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ package de.acosix.alfresco.keycloak.repo.roles;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.alfresco.util.ParameterCheck;
|
import org.alfresco.util.ParameterCheck;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -35,6 +36,8 @@ public class PatternRoleNameMapper implements RoleNameMapper
|
|||||||
|
|
||||||
protected Map<String, String> patternMappings;
|
protected Map<String, String> patternMappings;
|
||||||
|
|
||||||
|
protected Map<String, String> patternInverseMappings;
|
||||||
|
|
||||||
protected boolean upperCaseRoles;
|
protected boolean upperCaseRoles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,6 +49,15 @@ public class PatternRoleNameMapper implements RoleNameMapper
|
|||||||
this.patternMappings = patternMappings;
|
this.patternMappings = patternMappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param patternInverseMappings
|
||||||
|
* the patternInverseMappings to set
|
||||||
|
*/
|
||||||
|
public void setPatternInverseMappings(final Map<String, String> patternInverseMappings)
|
||||||
|
{
|
||||||
|
this.patternInverseMappings = patternInverseMappings;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param upperCaseRoles
|
* @param upperCaseRoles
|
||||||
* the upperCaseRoles to set
|
* the upperCaseRoles to set
|
||||||
@@ -75,7 +87,6 @@ public class PatternRoleNameMapper implements RoleNameMapper
|
|||||||
LOGGER.debug("Mapped role {} to {}", roleName, mappedName);
|
LOGGER.debug("Mapped role {} to {}", roleName, mappedName);
|
||||||
return mappedName;
|
return mappedName;
|
||||||
}).map(name -> this.upperCaseRoles ? name.toUpperCase(Locale.ENGLISH) : name);
|
}).map(name -> this.upperCaseRoles ? name.toUpperCase(Locale.ENGLISH) : name);
|
||||||
;
|
|
||||||
|
|
||||||
if (!result.isPresent())
|
if (!result.isPresent())
|
||||||
{
|
{
|
||||||
@@ -86,4 +97,32 @@ public class PatternRoleNameMapper implements RoleNameMapper
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<String> mapAuthorityName(final String authorityName)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatoryString("authorityName", authorityName);
|
||||||
|
|
||||||
|
Optional<String> result = Optional.empty();
|
||||||
|
|
||||||
|
if (this.patternInverseMappings != null)
|
||||||
|
{
|
||||||
|
final Optional<String> matchingPattern = this.patternMappings.keySet().stream().filter(pattern -> Pattern
|
||||||
|
.compile(pattern, this.upperCaseRoles ? Pattern.CASE_INSENSITIVE : 0).matcher(authorityName).matches()).findFirst();
|
||||||
|
|
||||||
|
result = matchingPattern.map(pattern -> {
|
||||||
|
final String replacement = this.patternMappings.get(pattern);
|
||||||
|
LOGGER.debug("Authority name {} matches inverse mapping pattern {} - applying replacement pattern {}", authorityName,
|
||||||
|
pattern, replacement);
|
||||||
|
final String mappedName = Pattern.compile(pattern, this.upperCaseRoles ? Pattern.CASE_INSENSITIVE : 0)
|
||||||
|
.matcher(authorityName).replaceAll(replacement);
|
||||||
|
LOGGER.debug("Mapped authority name {} to {}", authorityName, mappedName);
|
||||||
|
return mappedName;
|
||||||
|
}).map(name -> this.upperCaseRoles ? name.toLowerCase(Locale.ENGLISH) : name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -70,10 +70,30 @@ public class PrefixAttachingRoleNameMapper implements RoleNameMapper
|
|||||||
final String mappedName = this.prefix + roleName;
|
final String mappedName = this.prefix + roleName;
|
||||||
LOGGER.debug("Mapped role {} to {} using prefix attachment", roleName, mappedName);
|
LOGGER.debug("Mapped role {} to {} using prefix attachment", roleName, mappedName);
|
||||||
result = Optional.of(mappedName).map(name -> this.upperCaseRoles ? name.toUpperCase(Locale.ENGLISH) : name);
|
result = Optional.of(mappedName).map(name -> this.upperCaseRoles ? name.toUpperCase(Locale.ENGLISH) : name);
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> mapAuthorityName(final String authorityName)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatoryString("authorityName", authorityName);
|
||||||
|
|
||||||
|
Optional<String> result = Optional.empty();
|
||||||
|
|
||||||
|
if (this.prefix != null)
|
||||||
|
{
|
||||||
|
final String ciAuthorityName = authorityName.toLowerCase(Locale.ENGLISH);
|
||||||
|
final String ciPrefix = this.prefix.toLowerCase(Locale.ENGLISH);
|
||||||
|
if (ciAuthorityName.startsWith(ciPrefix))
|
||||||
|
{
|
||||||
|
final String mappedName = authorityName.substring(this.prefix.length());
|
||||||
|
LOGGER.debug("Mapped authority name {} to {} using prefix removal", authorityName, mappedName);
|
||||||
|
result = Optional.of(mappedName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -37,4 +37,14 @@ public interface RoleNameMapper
|
|||||||
* operation
|
* operation
|
||||||
*/
|
*/
|
||||||
Optional<String> mapRoleName(String roleName);
|
Optional<String> mapRoleName(String roleName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the name of an Alfresco authority to the name of a Keycloak role. This operation should act like the inverse of the
|
||||||
|
* {@link #mapRoleName(String) original inbound mapping}.
|
||||||
|
*
|
||||||
|
* @param authorityName
|
||||||
|
* the Alfresco authority name
|
||||||
|
* @return the name of the Keycloak role
|
||||||
|
*/
|
||||||
|
Optional<String> mapAuthorityName(String authorityName);
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
package de.acosix.alfresco.keycloak.repo.roles;
|
package de.acosix.alfresco.keycloak.repo.roles;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instances of this interface allow for lookup / retrieval of Keycloak roles.
|
* Instances of this interface allow for lookup / retrieval of Keycloak roles.
|
||||||
@@ -88,4 +89,31 @@ public interface RoleService
|
|||||||
*/
|
*/
|
||||||
List<Role> findRoles(String resourceName, String shortNameFilter);
|
List<Role> findRoles(String resourceName, String shortNameFilter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the specified authority name is a role mapped from Keycloak.
|
||||||
|
*
|
||||||
|
* @param authorityName
|
||||||
|
* the Alfresco authority name to check
|
||||||
|
* @return {@code true} if the authority name matches any expected patterns of roles mapped from Keycloak, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
boolean isMappedFromKeycloak(String authorityName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the name of the original Keycloak role from which the specified authority name was mapped from within Keycloak.
|
||||||
|
*
|
||||||
|
* @param authorityName
|
||||||
|
* the Alfresco authority name to process
|
||||||
|
* @return the name of the Keycloak role from which the authority name was mapped unless the role was not mapped from Keycloak
|
||||||
|
*/
|
||||||
|
Optional<String> getRoleName(String authorityName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the name of the client the specified authority name was mapped from within Keycloak.
|
||||||
|
*
|
||||||
|
* @param authorityName
|
||||||
|
* the Alfresco authority name to process
|
||||||
|
* @return the name of the client which defines the role unless the role is either not mapped from Keycloak or mapped from the realm
|
||||||
|
* scope
|
||||||
|
*/
|
||||||
|
Optional<String> getClientFromRole(String authorityName);
|
||||||
}
|
}
|
||||||
|
@@ -18,11 +18,16 @@ package de.acosix.alfresco.keycloak.repo.roles;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.alfresco.service.cmr.security.AuthorityType;
|
import org.alfresco.service.cmr.security.AuthorityType;
|
||||||
@@ -36,7 +41,7 @@ import org.springframework.beans.factory.InitializingBean;
|
|||||||
|
|
||||||
import de.acosix.alfresco.keycloak.repo.client.IDMClient;
|
import de.acosix.alfresco.keycloak.repo.client.IDMClient;
|
||||||
|
|
||||||
public class RoleServiceImpl implements InitializingBean, RoleService
|
public class RoleServiceImpl implements RoleService, InitializingBean
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(RoleServiceImpl.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(RoleServiceImpl.class);
|
||||||
@@ -280,6 +285,134 @@ public class RoleServiceImpl implements InitializingBean, RoleService
|
|||||||
return this.doFindRoles(resourceName, shortNameFilter);
|
return this.doFindRoles(resourceName, shortNameFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isMappedFromKeycloak(final String authorityName)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatoryString("authorityName", authorityName);
|
||||||
|
|
||||||
|
Optional<String> role = Optional.empty();
|
||||||
|
|
||||||
|
if (this.processRealmRoles)
|
||||||
|
{
|
||||||
|
role = this.realmRoleNameMapper.mapAuthorityName(authorityName);
|
||||||
|
}
|
||||||
|
if (this.processResourceRoles)
|
||||||
|
{
|
||||||
|
final Iterator<String> resourceIterator = this.resourceRoleNameMapper.keySet().iterator();
|
||||||
|
while (!role.isPresent() && resourceIterator.hasNext())
|
||||||
|
{
|
||||||
|
final RoleNameMapper roleNameMapper = this.resourceRoleNameMapper.get(resourceIterator.next());
|
||||||
|
role = roleNameMapper.mapAuthorityName(authorityName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return role.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<String> getRoleName(final String authorityName)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatoryString("authorityName", authorityName);
|
||||||
|
|
||||||
|
Optional<String> role = Optional.empty();
|
||||||
|
|
||||||
|
if (this.processRealmRoles)
|
||||||
|
{
|
||||||
|
final UnaryOperator<String> realmRoleResolver = rn -> {
|
||||||
|
final Set<String> matchingRoles = new HashSet<>();
|
||||||
|
this.idmClient.processRealmRoles(rn, 0, Integer.MAX_VALUE, roleResult -> {
|
||||||
|
if (roleResult.getName().equalsIgnoreCase(rn))
|
||||||
|
{
|
||||||
|
matchingRoles.add(roleResult.getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String matchingRole = null;
|
||||||
|
if (matchingRoles.size() == 1)
|
||||||
|
{
|
||||||
|
matchingRole = matchingRoles.iterator().next();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGER.warn("Failed to match apparent Keycloak realm role {} to unique role via admin API", rn);
|
||||||
|
}
|
||||||
|
return matchingRole;
|
||||||
|
};
|
||||||
|
role = this.realmRoleNameMapper.mapAuthorityName(authorityName).map(realmRoleResolver);
|
||||||
|
}
|
||||||
|
if (this.processResourceRoles)
|
||||||
|
{
|
||||||
|
final BinaryOperator<String> clientRoleResolver = (client, rn) -> {
|
||||||
|
final Set<String> matchingRoles = new HashSet<>();
|
||||||
|
this.idmClient.processClientRoles(client, rn, 0, Integer.MAX_VALUE, roleResult -> {
|
||||||
|
if (roleResult.getName().equalsIgnoreCase(rn))
|
||||||
|
{
|
||||||
|
matchingRoles.add(roleResult.getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String matchingRole = null;
|
||||||
|
if (matchingRoles.size() == 1)
|
||||||
|
{
|
||||||
|
matchingRole = matchingRoles.iterator().next();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGER.warn("Failed to match apparent Keycloak role {} from client {} to unique role via admin API", rn, client);
|
||||||
|
}
|
||||||
|
return matchingRole;
|
||||||
|
};
|
||||||
|
final Iterator<String> resourceIterator = this.resourceRoleNameMapper.keySet().iterator();
|
||||||
|
while (!role.isPresent() && resourceIterator.hasNext())
|
||||||
|
{
|
||||||
|
final String resource = resourceIterator.next();
|
||||||
|
final RoleNameMapper roleNameMapper = this.resourceRoleNameMapper.get(resource);
|
||||||
|
role = roleNameMapper.mapAuthorityName(authorityName).map(rn -> clientRoleResolver.apply(resource, rn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<String> getClientFromRole(final String authorityName)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatoryString("authorityName", authorityName);
|
||||||
|
Optional<String> client = Optional.empty();
|
||||||
|
Optional<String> role = Optional.empty();
|
||||||
|
|
||||||
|
if (this.processRealmRoles)
|
||||||
|
{
|
||||||
|
role = this.realmRoleNameMapper.mapAuthorityName(authorityName);
|
||||||
|
}
|
||||||
|
if (!role.isPresent() && this.processResourceRoles)
|
||||||
|
{
|
||||||
|
final Iterator<String> resourceIterator = this.resourceRoleNameMapper.keySet().iterator();
|
||||||
|
while (!role.isPresent() && resourceIterator.hasNext())
|
||||||
|
{
|
||||||
|
final String resource = resourceIterator.next();
|
||||||
|
final RoleNameMapper roleNameMapper = this.resourceRoleNameMapper.get(resource);
|
||||||
|
role = roleNameMapper.mapAuthorityName(authorityName);
|
||||||
|
if (role.isPresent())
|
||||||
|
{
|
||||||
|
client = Optional.of(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
protected List<Role> doFindRoles(final String shortNameFilter, final boolean realmOnly)
|
protected List<Role> doFindRoles(final String shortNameFilter, final boolean realmOnly)
|
||||||
{
|
{
|
||||||
final List<Role> roles;
|
final List<Role> roles;
|
||||||
@@ -480,11 +613,11 @@ public class RoleServiceImpl implements InitializingBean, RoleService
|
|||||||
|
|
||||||
if (clientId != null)
|
if (clientId != null)
|
||||||
{
|
{
|
||||||
this.idmClient.processRoles(clientId, 0, Integer.MAX_VALUE, processor);
|
this.idmClient.processClientRoles(clientId, 0, Integer.MAX_VALUE, processor);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.idmClient.processRoles(0, Integer.MAX_VALUE, processor);
|
this.idmClient.processRealmRoles(0, Integer.MAX_VALUE, processor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
@@ -508,7 +641,6 @@ public class RoleServiceImpl implements InitializingBean, RoleService
|
|||||||
{
|
{
|
||||||
LOGGER.debug("Excluding role {} as it maps to group authority name {}", role.getName(), r);
|
LOGGER.debug("Excluding role {} as it maps to group authority name {}", role.getName(), r);
|
||||||
}
|
}
|
||||||
;
|
|
||||||
return allowed;
|
return allowed;
|
||||||
}).map(r -> new Role(r, role.getName(), role.getDescription()));
|
}).map(r -> new Role(r, role.getName(), role.getDescription()));
|
||||||
|
|
||||||
|
@@ -145,6 +145,31 @@ public class ScriptRoleService extends BaseScopableProcessorExtension implements
|
|||||||
return roleArray;
|
return roleArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the specified authority name is a role mapped from Keycloak.
|
||||||
|
*
|
||||||
|
* @param authorityName
|
||||||
|
* the Alfresco authority name to check
|
||||||
|
* @return {@code true} if the authority name matches any expected patterns of roles mapped from Keycloak, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
public boolean isMappedFromKeycloak(final String authorityName)
|
||||||
|
{
|
||||||
|
return this.roleService.isMappedFromKeycloak(authorityName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the name of the client the specified authority name was mapped from within Keycloak.
|
||||||
|
*
|
||||||
|
* @param authorityName
|
||||||
|
* the Alfresco authority name to process
|
||||||
|
* @return the name of the client which defines the role unless the role is either not mapped from Keycloak or mapped from the realm
|
||||||
|
* scope
|
||||||
|
*/
|
||||||
|
public String getClientFromRole(final String authorityName)
|
||||||
|
{
|
||||||
|
return this.roleService.getClientFromRole(authorityName).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
protected Scriptable makeRoleArray(final List<Role> roles)
|
protected Scriptable makeRoleArray(final List<Role> roles)
|
||||||
{
|
{
|
||||||
final Scriptable sitesArray = Context.getCurrentContext().newArray(this.getScope(), roles.toArray(new Object[0]));
|
final Scriptable sitesArray = Context.getCurrentContext().newArray(this.getScope(), roles.toArray(new Object[0]));
|
||||||
|
@@ -17,6 +17,7 @@ package de.acosix.alfresco.keycloak.repo.roles;
|
|||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.alfresco.util.ParameterCheck;
|
import org.alfresco.util.ParameterCheck;
|
||||||
@@ -82,4 +83,34 @@ public class StaticRoleNameMapper implements RoleNameMapper
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Optional<String> mapAuthorityName(final String authorityName)
|
||||||
|
{
|
||||||
|
ParameterCheck.mandatoryString("authorityName", authorityName);
|
||||||
|
|
||||||
|
Optional<String> result = Optional.empty();
|
||||||
|
|
||||||
|
if (this.nameMappings != null)
|
||||||
|
{
|
||||||
|
for (final Entry<String, String> entry : this.nameMappings.entrySet())
|
||||||
|
{
|
||||||
|
if (entry.getValue().equals(authorityName) || (this.upperCaseRoles && entry.getValue().equalsIgnoreCase(authorityName)))
|
||||||
|
{
|
||||||
|
final String mappedName = entry.getKey();
|
||||||
|
LOGGER.debug("Mapped authority name {} to {} using static mapping", authorityName, mappedName);
|
||||||
|
result = Optional.of(mappedName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!result.isPresent())
|
||||||
|
{
|
||||||
|
LOGGER.debug("No static mapping applies to authority name {}", authorityName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user