This commit is contained in:
Luca Filipozzi 2021-08-22 11:02:55 -07:00
parent 3e5aa8f6f6
commit 58ac3c5921
9 changed files with 886 additions and 0 deletions

3
.gitignore vendored
View file

@ -1,5 +1,8 @@
# Copyright 2021 Luca Filipozzi. Some rights reserved. See LICENCE. # Copyright 2021 Luca Filipozzi. Some rights reserved. See LICENCE.
# direnv
.envrc
# idea # idea
.idea/ .idea/
*.iml *.iml

47
bundle/pom.xml Normal file
View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2021 Luca Filipozzi. Some rights reserved. See LICENSE. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.lucafilipozzi</groupId>
<artifactId>keycloak-regex-mapper</artifactId>
<version>${revision}</version>
</parent>
<artifactId>keycloak-regex-mapper-bundle</artifactId>
<packaging>ear</packaging>
<name>bundle</name>
<dependencies>
<dependency>
<groupId>${project.parent.groupId}</groupId>
<artifactId>${project.parent.artifactId}-module</artifactId>
</dependency>
</dependencies>
<build>
<!-- drop '-bundle' from finalName of the ear-->
<finalName>${project.parent.artifactId}-${revision}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<configuration>
<displayName>${project.parent.artifactId}</displayName>
<description>${project.parent.description}</description>
<modules>
<jarModule>
<groupId>${project.groupId}</groupId>
<artifactId>${project.parent.artifactId}-module</artifactId>
<!-- drop '-module' from finalName of the jar within the ear -->
<bundleFileName>${project.parent.groupId}-${project.parent.artifactId}-${revision}.jar</bundleFileName>
<includeInApplicationXml>true</includeInApplicationXml>
</jarModule>
</modules>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2021 Luca Filipozzi. Some rights reserved. See LICENSE. -->
<jboss-deployment-structure>
<deployment>
<dependencies>
<module name="com.google.guava" export="true"/>
<module name="org.jboss.logging" export="true"/>
<module name="org.keycloak.keycloak-common" export="true"/>
<module name="org.keycloak.keycloak-core" export="true"/>
<module name="org.keycloak.keycloak-saml-core-public" export="true"/>
<module name="org.keycloak.keycloak-server-spi" export="true"/>
<module name="org.keycloak.keycloak-server-spi-private" export="true"/>
<module name="org.keycloak.keycloak-services" export="true"/>
</dependencies>
</deployment>
</jboss-deployment-structure>

85
module/pom.xml Normal file
View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2021 Luca Filipozzi. Some rights reserved. See LICENSE. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.lucafilipozzi</groupId>
<artifactId>keycloak-regex-mapper</artifactId>
<version>${revision}</version>
</parent>
<artifactId>keycloak-regex-mapper-module</artifactId>
<packaging>jar</packaging>
<name>module</name>
<!-- IMPORTANT: don't forget to update jboss-deployment-structure.xml -->
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-core-public</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- verify -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<configuration>
<ignoredUnusedDeclaredDependencies>
<ignoredUnusedDeclaredDependency>org.keycloak:keycloak-common</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>org.keycloak:keycloak-core</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>org.keycloak:keycloak-server-spi</ignoredUnusedDeclaredDependency>
</ignoredUnusedDeclaredDependencies>
</configuration>
</plugin>
<plugin>
<groupId>org.basepom.maven</groupId>
<artifactId>duplicate-finder-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,138 @@
// Copyright 2021 Luca Filipozzi. Some rights reserved. See LICENSE.
package com.github.lucafilipozzi.keycloak.broker.oidc.mappers;
import com.github.lucafilipozzi.keycloak.broker.util.RegexRealmAndClientRoleMapperUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.oidc.mappers.AbstractClaimMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
/**
* Map a claim to realm and client roles via regex.
*/
public class RegexRealmAndClientRoleClaimMapper extends AbstractClaimMapper {
public static final String PROVIDER_ID = "lucafilipozzi-oidc-regex-claim-mapper";
public static final String OIDC_CLAIM_NAME = "oidc-claim-name";
protected static final String[] COMPATIBLE_PROVIDERS = { KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID };
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
private static final Logger LOG = Logger.getLogger(RegexRealmAndClientRoleClaimMapper.class);
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
ProviderConfigProperty oidcClaimNameConfigProperty = new ProviderConfigProperty();
oidcClaimNameConfigProperty.setName(OIDC_CLAIM_NAME);
oidcClaimNameConfigProperty.setLabel("OIDC claim name");
oidcClaimNameConfigProperty.setHelpText("name of OIDC claim to search");
oidcClaimNameConfigProperty.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(oidcClaimNameConfigProperty);
ProviderConfigProperty clientRolesAttributeNameConfigProperty = new ProviderConfigProperty();
clientRolesAttributeNameConfigProperty.setName(RegexRealmAndClientRoleMapperUtil.CLIENT_ROLES_ATTRIBUTE_NAME);
clientRolesAttributeNameConfigProperty.setLabel("client roles attribute name");
clientRolesAttributeNameConfigProperty.setHelpText("only evaluate client roles having an attribute with this name");
clientRolesAttributeNameConfigProperty.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(clientRolesAttributeNameConfigProperty);
ProviderConfigProperty clientRolesRegularExpressionConfigProperty = new ProviderConfigProperty();
clientRolesRegularExpressionConfigProperty.setName(RegexRealmAndClientRoleMapperUtil.CLIENT_ROLES_REGULAR_EXPRESSION);
clientRolesRegularExpressionConfigProperty.setLabel("client roles regular expression");
clientRolesRegularExpressionConfigProperty.setHelpText("regular expression to apply to the OIDC claim to extract client roles; must specify two named-capturing groups: client and role");
clientRolesRegularExpressionConfigProperty.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(clientRolesRegularExpressionConfigProperty);
ProviderConfigProperty realmRolesAttributeNameConfigProperty = new ProviderConfigProperty();
realmRolesAttributeNameConfigProperty.setName(RegexRealmAndClientRoleMapperUtil.REALM_ROLES_ATTRIBUTE_NAME);
realmRolesAttributeNameConfigProperty.setLabel("realm roles attribute name");
realmRolesAttributeNameConfigProperty.setHelpText("only evaluate realm roles having an attribute with this name");
realmRolesAttributeNameConfigProperty.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(realmRolesAttributeNameConfigProperty);
ProviderConfigProperty realmRolesRegularExpressionConfigProperty = new ProviderConfigProperty();
realmRolesRegularExpressionConfigProperty.setName(RegexRealmAndClientRoleMapperUtil.REALM_ROLES_REGULAR_EXPRESSION);
realmRolesRegularExpressionConfigProperty.setLabel("realm roles regular expression");
realmRolesRegularExpressionConfigProperty.setHelpText("regular expression to apply to the OIDC claim to extract realm roles; must specify one named-capturing groups: role");
realmRolesRegularExpressionConfigProperty.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(realmRolesRegularExpressionConfigProperty);
}
@Override
public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getDisplayCategory() {
return "Role Importer";
}
@Override
public String getDisplayType() {
return "Regex Realm and Client Role Importer";
}
@Override
public String getHelpText() {
return "implements regex realm and client role importer";
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapper, BrokeredIdentityContext context) {
LOG.trace("import user");
processUser(realm, user, mapper, context);
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapper, BrokeredIdentityContext context) {
LOG.trace("update user");
processUser(realm, user, mapper, context);
}
private void processUser(RealmModel realm, UserModel user, IdentityProviderMapperModel mapper, BrokeredIdentityContext context) {
LOG.trace("process user");
String oidcClaimName = mapper.getConfig().getOrDefault(OIDC_CLAIM_NAME, "");
Set<String> assertedValues;
Object claimValue = getClaimValue(context, oidcClaimName);
if (claimValue instanceof List) {
assertedValues = ((List<?>) claimValue).stream().map(String.class::cast).collect(Collectors.toSet());
} else {
assertedValues = Collections.emptySet();
}
RegexRealmAndClientRoleMapperUtil.processUser(realm, user, mapper, assertedValues);
}
}

View file

@ -0,0 +1,138 @@
// Copyright 2021 Luca Filipozzi. Some rights reserved. See LICENSE.
package com.github.lucafilipozzi.keycloak.broker.saml.mappers;
import com.github.lucafilipozzi.keycloak.broker.util.RegexRealmAndClientRoleMapperUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.saml.SAMLEndpoint;
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
/**
* Map an attribute to realm and client roles via regex.
*/
public class RegexRealmAndClientRoleAttributeMapper extends AbstractIdentityProviderMapper {
public static final String PROVIDER_ID = "lucafilipozzi-saml-regex-attribute-mapper";
public static final String SAML_ATTRIBUTE_NAME = "saml-attribute-name";
protected static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));
private static final Logger LOG = Logger.getLogger(RegexRealmAndClientRoleAttributeMapper.class);
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
ProviderConfigProperty samlAttributeNameConfigProperty = new ProviderConfigProperty();
samlAttributeNameConfigProperty.setName(SAML_ATTRIBUTE_NAME);
samlAttributeNameConfigProperty.setLabel("SAML attribute name");
samlAttributeNameConfigProperty.setHelpText("name of SAML attribute to search (friendly or otherwise)");
samlAttributeNameConfigProperty.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(samlAttributeNameConfigProperty);
ProviderConfigProperty clientRolesAttributeNameConfigProperty = new ProviderConfigProperty();
clientRolesAttributeNameConfigProperty.setName(RegexRealmAndClientRoleMapperUtil.CLIENT_ROLES_ATTRIBUTE_NAME);
clientRolesAttributeNameConfigProperty.setLabel("client roles attribute name");
clientRolesAttributeNameConfigProperty.setHelpText("only evaluate client roles having an attribute with this name");
clientRolesAttributeNameConfigProperty.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(clientRolesAttributeNameConfigProperty);
ProviderConfigProperty clientRolesRegularExpressionConfigProperty = new ProviderConfigProperty();
clientRolesRegularExpressionConfigProperty.setName(RegexRealmAndClientRoleMapperUtil.CLIENT_ROLES_REGULAR_EXPRESSION);
clientRolesRegularExpressionConfigProperty.setLabel("client roles regular expression");
clientRolesRegularExpressionConfigProperty.setHelpText("regular expression to apply to the SAML attribute to extract client roles; must specify two named-capturing groups: client and role");
clientRolesRegularExpressionConfigProperty.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(clientRolesRegularExpressionConfigProperty);
ProviderConfigProperty realmRolesAttributeNameConfigProperty = new ProviderConfigProperty();
realmRolesAttributeNameConfigProperty.setName(RegexRealmAndClientRoleMapperUtil.REALM_ROLES_ATTRIBUTE_NAME);
realmRolesAttributeNameConfigProperty.setLabel("realm roles attribute name");
realmRolesAttributeNameConfigProperty.setHelpText("only evaluate realm roles having an attribute with this name");
realmRolesAttributeNameConfigProperty.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(realmRolesAttributeNameConfigProperty);
ProviderConfigProperty realmRolesRegularExpressionConfigProperty = new ProviderConfigProperty();
realmRolesRegularExpressionConfigProperty.setName(RegexRealmAndClientRoleMapperUtil.REALM_ROLES_REGULAR_EXPRESSION);
realmRolesRegularExpressionConfigProperty.setLabel("realm roles regular expression");
realmRolesRegularExpressionConfigProperty.setHelpText("regular expression to apply to the SAML attribute to extract realm roles; must specify one named-capturing group: role");
realmRolesRegularExpressionConfigProperty.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(realmRolesRegularExpressionConfigProperty);
}
@Override
public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getDisplayCategory() {
return "Role Importer";
}
@Override
public String getDisplayType() {
return "Regex Realm and Client Role Importer";
}
@Override
public String getHelpText() {
return "implements regex realm and client role importer";
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) {
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode);
}
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapper, BrokeredIdentityContext context) {
LOG.trace("import user");
processUser(realm, user, mapper, context);
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapper, BrokeredIdentityContext context) {
LOG.trace("update user");
processUser(realm, user, mapper, context);
}
private void processUser(RealmModel realm, UserModel user, IdentityProviderMapperModel mapper, BrokeredIdentityContext context) {
LOG.trace("process user");
String samlAttributeName = mapper.getConfig().getOrDefault(SAML_ATTRIBUTE_NAME, "");
AssertionType assertion = (AssertionType) context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
Set<String> assertedValues = assertion.getAttributeStatements().stream()
.flatMap(statement -> statement.getAttributes().stream())
.filter(choice -> choice.getAttribute().getFriendlyName().equals(samlAttributeName) || choice.getAttribute().getName().equals(samlAttributeName))
.flatMap(choice -> choice.getAttribute().getAttributeValue().stream())
.map(Object::toString)
.collect(Collectors.toSet());
RegexRealmAndClientRoleMapperUtil.processUser(realm, user, mapper, assertedValues);
}
}

View file

@ -0,0 +1,113 @@
// Copyright 2021 Luca Filipozzi. Some rights reserved. See LICENSE.
package com.github.lucafilipozzi.keycloak.broker.util;
import com.google.common.collect.Sets;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
/**
* Utilities for adjusting user's realm and client role assignments.
*/
public final class RegexRealmAndClientRoleMapperUtil {
public static final String CLIENT_ROLES_ATTRIBUTE_NAME = "client-roles-attribute-name";
public static final String CLIENT_ROLES_REGULAR_EXPRESSION = "client-roles-regular-expression";
public static final String REALM_ROLES_ATTRIBUTE_NAME = "realm-roles-attribute-name";
public static final String REALM_ROLES_REGULAR_EXPRESSION = "realm-roles-regular-expression";
private static final Logger LOG = Logger.getLogger(RegexRealmAndClientRoleMapperUtil.class);
private RegexRealmAndClientRoleMapperUtil() {
throw new UnsupportedOperationException();
}
public static void processUser(RealmModel realm, UserModel user, IdentityProviderMapperModel mapper, Set<String> assertedValues) {
LOG.trace("process user");
// adjust the user's client role assignments
String clientRolesRegularExpression = mapper.getConfig().getOrDefault(CLIENT_ROLES_REGULAR_EXPRESSION, "");
String clientRolesAttributeName = mapper.getConfig().getOrDefault(CLIENT_ROLES_ATTRIBUTE_NAME, "");
RegexRealmAndClientRoleMapperUtil.adjustUserClientRoleAssignments(realm, user, assertedValues, clientRolesRegularExpression, clientRolesAttributeName);
// adjust the user's realm role assignments
String realmRolesRegularExpression = mapper.getConfig().getOrDefault(REALM_ROLES_REGULAR_EXPRESSION, "");
String realmRolesAttributeName = mapper.getConfig().getOrDefault(REALM_ROLES_ATTRIBUTE_NAME, "");
RegexRealmAndClientRoleMapperUtil.adjustUserRealmRoleAssignments(realm, user, assertedValues, realmRolesRegularExpression, realmRolesAttributeName);
}
private static void adjustUserClientRoleAssignments(RealmModel realm, UserModel user, Set<String> assertedValues, String regularExpression, String attributeName) {
LOG.trace("adjust user client role assignments");
Pattern pattern = Pattern.compile(regularExpression);
// determine the client roles that the user should have
Set<RoleModel> wantRoles = assertedValues.stream()
.map(pattern::matcher)
.filter(Matcher::matches)
.filter(matcher -> matcher.groupCount() == 2)
.filter(matcher -> matcher.group("client") != null)
.filter(matcher -> matcher.group("role") != null)
.flatMap(matcher ->
realm.getClientsStream()
.filter(client -> client.getClientId().equalsIgnoreCase(matcher.group("client")))
.flatMap(ClientModel::getRolesStream)
.filter(clientRole -> clientRole.getAttributeStream(attributeName).findAny().isPresent())
.filter(clientRole -> clientRole.getName().equalsIgnoreCase(matcher.group("role"))))
.collect(Collectors.toSet());
// determine the client roles that user does have
Set<RoleModel> haveRoles = user.getRoleMappingsStream()
.filter(RoleModel::isClientRole)
.filter(clientRole -> clientRole.getAttributes().containsKey(attributeName))
.collect(Collectors.toSet());
// assign the client roles that the user should have but doesn't
Sets.difference(wantRoles, haveRoles).forEach(user::grantRole);
// un-assign the client roles that the user has but shouldn't
Sets.difference(haveRoles, wantRoles).forEach(user::deleteRoleMapping);
}
private static void adjustUserRealmRoleAssignments(RealmModel realm, UserModel user, Set<String> assertedValues, String regularExpression, String attributeName) {
LOG.trace("adjust user realm role assignments");
Pattern pattern = Pattern.compile(regularExpression);
// determine the realm roles that the user should have
Set<RoleModel> wantRoles = assertedValues.stream()
.map(pattern::matcher)
.filter(Matcher::matches)
.filter(matcher -> matcher.groupCount() == 1)
.filter(matcher -> matcher.group("role") != null)
.flatMap(matcher ->
realm.getRolesStream()
.filter(realmRole -> !realmRole.isClientRole())
.filter(realmRole -> realmRole.getAttributeStream(attributeName).findAny().isPresent())
.filter(realmRole -> realmRole.getName().equalsIgnoreCase(matcher.group("role"))))
.collect(Collectors.toSet());
// determine the realm roles that the user does have
Set<RoleModel> haveRoles = user.getRoleMappingsStream()
.filter(realmRole -> !realmRole.isClientRole())
.filter(realmRole -> realmRole.getAttributes().containsKey(attributeName))
.collect(Collectors.toSet());
// assign the realm roles that the user should have but doesn't
Sets.difference(wantRoles, haveRoles).forEach(user::grantRole);
// un-assign the realm roles that the user has but shouldn't
Sets.difference(haveRoles, wantRoles).forEach(user::deleteRoleMapping);
}
}

View file

@ -0,0 +1,4 @@
# Copyright 2021 Luca Filipozzi. Some rights reserved. See LICENSE.
com.github.lucafilipozzi.keycloak.broker.oidc.mappers.RegexRealmAndClientRoleClaimMapper
com.github.lucafilipozzi.keycloak.broker.saml.mappers.RegexRealmAndClientRoleAttributeMapper

342
pom.xml Normal file
View file

@ -0,0 +1,342 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2021 Luca Filipozzi. Some rights reserved. See LICENSE. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>13.0.1</version>
</parent>
<groupId>com.github.lucafilipozzi</groupId>
<artifactId>keycloak-regex-mapper</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<name>parent</name>
<description>Keycloak Extensions Prototype</description>
<developers>
<developer>
<name>Luca Filipozzi</name>
</developer>
</developers>
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/lucafilipozzi/keycloak-regex-mapper/issues</url>
</issueManagement>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:https://github.com/lucafilipozzi/keycloak-regex-mapper</connection>
<developerConnection>scm:git:git://github.com/lucafilipozzi/keycloak-regex-mapper</developerConnection>
<url>https://github.com/lucafilipozzi/keycloak-regex-mapper</url>
</scm>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<revision>1.0.0-SNAPSHOT</revision>
<keycloak.version>13.0.1</keycloak.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${google.guava.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.lucafilipozzi</groupId>
<artifactId>keycloak-regex-mapper-module</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-core-public</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>module</module>
<module>bundle</module>
</modules>
<build>
<pluginManagement>
<plugins>
<!-- clean -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- validate -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<rules>
<!-- org.apache.maven.plugins:maven-enforcer-plugin -->
<requireJavaVersion>
<version>1.8</version>
</requireJavaVersion>
<requireMavenVersion>
<version>3.6.0</version>
</requireMavenVersion>
<requirePluginVersions>
<banLatest>true</banLatest>
<banRelease>true</banRelease>
<banSnapshots>true</banSnapshots>
</requirePluginVersions>
<requireReleaseDeps>
<excludes>
<exclude>${project.groupId}:*</exclude>
</excludes>
</requireReleaseDeps>
<requireSameVersions>
<buildPlugins>
<plugin>org.apache.maven.plugins:maven-surefire-plugin</plugin>
<plugin>org.apache.maven.plugins:maven-failsafe-plugin</plugin>
</buildPlugins>
</requireSameVersions>
<!-- org.codehaus.mojo:extra-enforcer-rules -->
<banCircularDependencies/>
</rules>
</configuration>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>extra-enforcer-rules</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>1.4</version>
</plugin>
<!-- compile -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1-jboss-2</version>
</plugin>
<!-- test -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
<!-- package -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<version>3.2.0</version>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-archiver</artifactId>
<version>4.2.5</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
</plugin>
<!-- verify -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.2</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>8.45.1</version>
</dependency>
</dependencies>
<configuration>
<configLocation>google_checks.xml</configLocation>
<consoleOutput>true</consoleOutput>
<encoding>${project.build.sourceEncoding}</encoding>
<failsOnError>true</failsOnError>
<suppressionsLocation>.checkstyle-suppressions.xml</suppressionsLocation>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>6.2.2</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.8.1</version>
<configuration>
<allowMajorUpdates>false</allowMajorUpdates>
<allowMinorUpdates>false</allowMinorUpdates>
<allowIncrementalUpdates>false</allowIncrementalUpdates>
<allowSnapshots>false</allowSnapshots>
<processDependencyManagement>false</processDependencyManagement>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>display-dependency-updates</goal>
<goal>display-plugin-updates</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>analyze</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.basepom.maven</groupId>
<artifactId>duplicate-finder-maven-plugin</artifactId>
<version>1.5.0</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- install -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.0.0-M1</version>
</plugin>
<!-- site -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.9.1</version>
</plugin>
<!-- deploy -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0-M1</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>