Refactor and cleanup

* simplify AttributeEndpointsResourceProvider
* improve explanation of properties in AdminUiPage
* add example for mailing lists and map endpoint
* better explain configuration and operation of provider in main readme
* adjust the Keycloak realm
* Add unit test for endpoint class
This commit is contained in:
Stefan Bethke 2026-07-18 17:39:01 +02:00
commit cd31ec8a20
10 changed files with 523 additions and 200 deletions

View file

@ -28,6 +28,19 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.23.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>

View file

@ -1,15 +1,12 @@
package de.ccc.hamburg.keycloak.attribute_endpoints;
import java.util.List;
import java.util.regex.Pattern;
import com.google.auto.service.AutoService;
import org.keycloak.Config;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.representations.userprofile.config.UPConfig;
@ -17,7 +14,8 @@ import org.keycloak.services.ui.extend.UiPageProvider;
import org.keycloak.services.ui.extend.UiPageProviderFactory;
import org.keycloak.userprofile.UserProfileProvider;
import com.google.auto.service.AutoService;
import java.util.List;
import java.util.regex.Pattern;
/**
* Implements UiPageProvider to show a config page in the admin
@ -43,6 +41,7 @@ public class AdminUiPage implements UiPageProvider, UiPageProviderFactory<Compon
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return "Configure endpoints of the Attribute Endpoint Provider.";
}
@ -50,7 +49,7 @@ public class AdminUiPage implements UiPageProvider, UiPageProviderFactory<Compon
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) {
String errorString = "\n";
Boolean hasError = false;
boolean hasError = false;
Pattern slugPattern = Pattern.compile("^[a-zA-Z0-9_-]*$");
String configAttributeSlug = model.getConfig().getFirst("slug");
@ -86,13 +85,15 @@ public class AdminUiPage implements UiPageProvider, UiPageProviderFactory<Compon
if (configAttributeGroup == null) {
hasError = true;
errorString += " • [Attribute Group] can not be empty\n";
} else if (!upconfig.getGroups().stream().anyMatch(g -> g.getName().equals(configAttributeGroup))) {
} else if (upconfig.getAttributes().stream().noneMatch(a ->
a.getName().equals(configAttributeGroup)
|| (a.getGroup() != null && a.getGroup().equals(configAttributeGroup)))) {
hasError = true;
errorString += " • [Attribute Group] does not exist\n";
errorString += " • [Attribute Group] no matching Attribute or Attribute Group\n";
}
String configAttributeRegex = model.getConfig().getFirst("attribute-regex");
Boolean regexIsBlank = configAttributeRegex == null;
boolean regexIsBlank = configAttributeRegex == null;
if (!regexIsBlank) {
try {
@ -113,37 +114,37 @@ public class AdminUiPage implements UiPageProvider, UiPageProviderFactory<Compon
return ProviderConfigurationBuilder.create()
.property()
.name("slug")
.label("Slug")
.label("Endpoint URL Slug")
.helpText(
"The slug in the path of the API endpoint (e.g. /realms/:realm/attribute-endpoint-provider/export/:slug)")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property()
.name("auth-role")
.label("Endpoint Role")
.helpText("Calling this endpoint configuration requires an authenticated user that has this role.")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property()
.name("attribute-group")
.label("Attribute Group")
.helpText("The attribute group to export.")
.label("Attribute Name or Group")
.helpText("The attribute or attribute group to export.")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property()
.name("match-role")
.label("Match Role")
.helpText("Export only attributes of users with this role.")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property()
.name("auth-role")
.label("Auth Role")
.helpText("Role needeed by the authenticated account to be able to use this endpoint.")
.label("User Match Role")
.helpText("Only users with this role will have the attributes exported.")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property()
.name("attribute-regex")
.label("Attribute RegEx")
.helpText("A RegEx Rule used to verify each attribute value. Only matching values are returned.")
.label("Validation Regex")
.helpText("Only values matching this regex will be included in the result. Optional.")
.type(ProviderConfigProperty.STRING_TYPE)
.add()

View file

@ -6,6 +6,7 @@ import jakarta.ws.rs.core.Response;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.*;
import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth;
@ -16,7 +17,6 @@ import org.keycloak.userprofile.UserProfileProvider;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -39,7 +39,7 @@ public class AttributeEndpointsResourceProvider implements RealmResourceProvider
}
/**
* Returns a list of all atrribute values selected by the attribute group attributeGroupName.
* Returns a list of all attribute values selected by the attribute group attributeGroupName.
*
* @param attributeGroupName attribute group name
* @return a list of attribute values.
@ -61,14 +61,7 @@ public class AttributeEndpointsResourceProvider implements RealmResourceProvider
.toList();
})
.flatMap(List::stream)
.filter(attribute -> {
if (ctx.regexIsBlank) {
return true;
}
final Pattern pattern = Pattern.compile(ctx.configAttributeRegex);
final Matcher matcher = pattern.matcher(attribute);
return matcher.find();
})
.filter(ctx.filter::matches)
.toList();
return Response.ok(attribute_list).build();
@ -80,7 +73,7 @@ public class AttributeEndpointsResourceProvider implements RealmResourceProvider
* values as a list as the values.
*
* @param attributeGroupName attribute group name
* @return
* @return a map of attribute names and their values
*/
@GET
@Path("export/{attributeGroupName}/map")
@ -96,14 +89,7 @@ public class AttributeEndpointsResourceProvider implements RealmResourceProvider
attributeName -> userList.stream()
.flatMap(user -> user.getAttributeStream(attributeName))
.filter(attribute -> !attribute.isEmpty())
.filter(attribute -> {
if (ctx.regexIsBlank) {
return true;
}
final Pattern pattern = Pattern.compile(ctx.configAttributeRegex);
final Matcher matcher = pattern.matcher(attribute);
return matcher.find();
})
.filter(ctx.filter::matches)
.toList()));
return Response.ok(attributeMap).build();
@ -115,87 +101,69 @@ public class AttributeEndpointsResourceProvider implements RealmResourceProvider
* values for a given slug, exposing the results as member variables.
*/
private class AttributeExportContext {
KeycloakContext context;
RealmModel realm;
List<ComponentModel> componentList;
Auth auth;
ComponentModel component;
String configAuthRole;
RoleModel authRole;
String configMatchRole;
RoleModel matchRole;
UserProfileProvider profileProvider;
UPConfig upconfig;
String configAttributeGroup;
String configAttributeRegex;
Boolean regexIsBlank;
UserModel authUser;
List<String> attributeNames;
UserModel authUser;
String configAttributeGroup;
RegExFilter filter;
UPConfig upconfig;
Stream<UserModel> users;
AttributeExportContext(String slug) {
context = session.getContext();
realm = context.getRealm();
RealmModel realm = session.getContext().getRealm();
componentList = realm.getComponentsStream()
List<ComponentModel> componentList = realm.getComponentsStream()
.filter(c -> c.getProviderId().equals(AdminUiPage.PROVIDER_ID))
.filter(c -> c.getConfig().getFirst("slug").equals(slug))
.toList();
auth = AttributeEndpointsResourceProvider.getAuth(session);
if (componentList.isEmpty()) {
throw new NotFoundException("Endpoint not found.");
throw new NotFoundException("Attribute Endpoint " + slug + " not found");
}
if (componentList.size() > 1) {
throw new NotFoundException(
"Endpoint Configuration Error - Multiple configurations exist for this endpoint.");
"Attribute Endpoint Configuration Error - Multiple configurations exist for " + slug);
}
component = componentList.get(0);
ComponentModel component = componentList.get(0);
configAuthRole = component.getConfig().getFirst("auth-role");
authRole = realm.getRole(configAuthRole);
RoleModel authRole = realm.getRole(component.getConfig().getFirst("auth-role"));
if (authRole == null) {
throw new ServerErrorException("Endpoint Configuration Error - auth-role does not exist.", 500);
throw new ServerErrorException("Attribute Endpoint " + slug + " Configuration Error - auth-role does not exist.", 500);
}
configMatchRole = component.getConfig().getFirst("match-role");
matchRole = realm.getRole(configMatchRole);
RoleModel matchRole = realm.getRole(component.getConfig().getFirst("match-role"));
if (matchRole == null) {
throw new ServerErrorException("Endpoint Configuration Error - match-role does not exist.", 500);
throw new ServerErrorException("Attribute Endpoint " + slug + " Configuration Error - match-role does not exist.", 500);
}
profileProvider = session.getProvider(UserProfileProvider.class);
upconfig = profileProvider.getConfiguration();
upconfig = session.getProvider(UserProfileProvider.class).getConfiguration();
configAttributeGroup = component.getConfig().getFirst("attribute-group");
if (!upconfig.getGroups().stream().anyMatch(g -> g.getName().equals(configAttributeGroup))) {
throw new ServerErrorException("Endpoint Configuration Error - attribute-group does not exist.", 500);
if (upconfig.getGroups().stream().noneMatch(g -> g.getName().equals(configAttributeGroup)) &&
upconfig.getAttributes().stream().noneMatch(a -> a.getName().equals(configAttributeGroup))) {
throw new ServerErrorException("Attribute Endpoint " + slug + " Configuration Error - no attribute or attribute group named " + configAttributeGroup + " found", 500);
}
configAttributeRegex = component.getConfig().getFirst("attribute-regex");
regexIsBlank = configAttributeRegex == null;
if (!regexIsBlank) {
try {
Pattern.compile(configAttributeRegex);
} catch (Exception e) {
throw new ServerErrorException(
"Endpoint Configuration Error - attribute-regex is not a valid regex pattern.", 500);
}
try {
filter = new RegExFilter(component.getConfig().getFirst("attribute-regex"));
} catch (Exception e) {
throw new ServerErrorException(
"Attribute Endpoint " + slug + " Configuration Error - attribute-regex is not a valid regex pattern.", 500);
}
authUser = auth.getUser();
authUser = AttributeEndpointsResourceProvider.getAuth(session).getUser();
if (!authUser.hasRole(authRole)) {
LOG.info("User " + authUser.getUsername() + " does not have required role " + authRole.getName());
LOG.info("User " + authUser.getUsername() + " does not have required role " + authRole.getName() + " for attribute endpoint " + slug);
throw new ForbiddenException("User does not have required auth role.");
}
// select all attributes that match configAttributeGroup, or that are in a group that matches configAttributeGroup
attributeNames = upconfig.getAttributes()
.stream()
.filter(a -> a.getGroup() != null && a.getGroup().equals(configAttributeGroup))
.map(a -> a.getName())
.filter(a ->
a.getName().equals(configAttributeGroup)
|| (a.getGroup() != null && a.getGroup().equals(configAttributeGroup)))
.map(UPAttribute::getName)
.toList();
UserProvider userProvider = session.users();
@ -212,8 +180,25 @@ public class AttributeEndpointsResourceProvider implements RealmResourceProvider
}
RealmModel realm = session.getContext().getRealm();
ClientModel client = auth.getClient();
return new Auth(realm, auth.getToken(), auth.getUser(), client, auth.getSession(), false);
ClientModel client = auth.client();
return new Auth(realm, auth.token(), auth.user(), client, auth.session(), false);
}
private static class RegExFilter {
Pattern pattern;
RegExFilter(String regex) {
if (regex == null)
pattern = null;
else
pattern = Pattern.compile(regex);
}
boolean matches(String input) {
if (pattern == null)
return true;
return pattern.matcher(input).matches();
}
}
}

View file

@ -0,0 +1,306 @@
package de.ccc.hamburg.keycloak.attribute_endpoints;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.ServerErrorException;
import jakarta.ws.rs.core.Response;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.*;
import org.keycloak.representations.userprofile.config.UPAttribute;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.userprofile.config.UPGroup;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager.AuthResult;
import org.keycloak.userprofile.UserProfileProvider;
import org.mockito.MockedConstruction;
import org.mockito.Mockito;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Tests the two endpoints of {@link AttributeEndpointsResourceProvider} against the realm
* configuration and expected results documented in {@code testing/README.md}.
*/
public class AttributeEndpointsResourceProviderTest {
private static final String SSH_KEY_1 = "ssh-key-1";
private static final String SSH_KEY_2 = "ssh-key-2";
private static final String MAILING_LIST_CHAOS = "mailing-list-address-chaos";
private static final String MAILING_LIST_INTERN = "mailing-list-address-intern";
private KeycloakSession session;
private RealmModel realm;
private Map<String, RoleModel> roles;
private UserModel hacker;
private UserModel tester;
private UserModel noone;
@Before
public void setUp() {
session = mock(KeycloakSession.class);
KeycloakContext context = mock(KeycloakContext.class);
when(session.getContext()).thenReturn(context);
realm = mock(RealmModel.class);
when(context.getRealm()).thenReturn(realm);
roles = new HashMap<>();
for (String name : List.of("dooris-authorized", "export-dooris-ssh-keys",
"mailing-list-chaos-member", "mailing-list-intern-member",
"export-mailing-list-addresses")) {
roles.put(name, mock(RoleModel.class));
}
when(realm.getRole(anyString())).thenAnswer(inv -> roles.get(inv.getArgument(0, String.class)));
List<ComponentModel> components = List.of(
attributeEndpointComponent("dooris-ssh-keys",
"export-dooris-ssh-keys", "dooris-authorized", "dooris-ssh-keys"),
attributeEndpointComponent("mailing-list-addresses-chaos",
"export-mailing-list-addresses", "mailing-list-chaos-member", MAILING_LIST_CHAOS),
attributeEndpointComponent("mailing-list-addresses-intern",
"export-mailing-list-addresses", "mailing-list-intern-member", MAILING_LIST_INTERN));
when(realm.getComponentsStream()).thenAnswer(inv -> components.stream());
UserProfileProvider userProfileProvider = mock(UserProfileProvider.class);
when(session.getProvider(UserProfileProvider.class)).thenReturn(userProfileProvider);
when(userProfileProvider.getConfiguration()).thenReturn(testUserProfileConfig());
hacker = user("hacker", Map.of(
SSH_KEY_1, "hacker-ssh-key-1",
SSH_KEY_2, "hacker-ssh-key-2",
MAILING_LIST_CHAOS, "hacker+chaos@example.net",
MAILING_LIST_INTERN, "hacker+intern@example.net"),
List.of("dooris-authorized", "mailing-list-chaos-member", "mailing-list-intern-member"));
tester = user("tester", Map.of(
SSH_KEY_1, "tester-ssh-key-1",
SSH_KEY_2, "tester-ssh-key-2",
MAILING_LIST_CHAOS, "tester+chaos@example.com",
MAILING_LIST_INTERN, "tester+intern@example.com"),
List.of("mailing-list-chaos-member"));
noone = user("noone", Map.of(
SSH_KEY_1, "noone-ssh-key-1",
SSH_KEY_2, "noone-ssh-key-2",
MAILING_LIST_CHAOS, "noone+chaos@example.org",
MAILING_LIST_INTERN, "noone+intern@example.org"),
Collections.emptyList());
UserProvider userProvider = mock(UserProvider.class);
when(session.users()).thenReturn(userProvider);
when(userProvider.searchForUserStream(eq(realm), eq(Map.of())))
.thenAnswer(inv -> Stream.of(hacker, tester, noone));
}
@Test
public void exportAttributeValues_doorisSlug_returnsSshKeysOfMatchingUser() {
Response response = callAuthenticatedAs("export-dooris-ssh-keys",
() -> new AttributeEndpointsResourceProvider(session).exportAttributeValues("dooris-ssh-keys"));
assertEquals(200, response.getStatus());
assertEquals(List.of("hacker-ssh-key-1", "hacker-ssh-key-2"), response.getEntity());
}
@Test
public void exportAttributeValuesMap_doorisSlug_returnsSshKeysPerAttribute() {
try (Response response = callAuthenticatedAs("export-dooris-ssh-keys",
() -> new AttributeEndpointsResourceProvider(session).exportAttributeValuesMap("dooris-ssh-keys"))) {
assertEquals(200, response.getStatus());
assertEquals(Map.of(
SSH_KEY_1, List.of("hacker-ssh-key-1"),
SSH_KEY_2, List.of("hacker-ssh-key-2")),
response.getEntity());
}
}
@Test
public void exportAttributeValues_mailingListChaosSlug_returnsAddressesOfAllMatchingUsers() {
try (Response response = callAuthenticatedAs("export-mailing-list-addresses",
() -> new AttributeEndpointsResourceProvider(session)
.exportAttributeValues("mailing-list-addresses-chaos"))) {
assertEquals(200, response.getStatus());
assertEquals(List.of("hacker+chaos@example.net", "tester+chaos@example.com"), response.getEntity());
}
}
@Test
public void exportAttributeValues_mailingListInternSlug_returnsAddressOfSingleMatchingUser() {
try (Response response = callAuthenticatedAs("export-mailing-list-addresses",
() -> new AttributeEndpointsResourceProvider(session)
.exportAttributeValues("mailing-list-addresses-intern"))) {
assertEquals(200, response.getStatus());
assertEquals(List.of("hacker+intern@example.net"), response.getEntity());
}
}
@Test
public void exportAttributeValuesMap_mailingListChaosSlug_returnsAddressesPerAttribute() {
try (Response response = callAuthenticatedAs("export-mailing-list-addresses",
() -> new AttributeEndpointsResourceProvider(session)
.exportAttributeValuesMap("mailing-list-addresses-chaos"))) {
assertEquals(200, response.getStatus());
assertEquals(Map.of(MAILING_LIST_CHAOS,
List.of("hacker+chaos@example.net", "tester+chaos@example.com")),
response.getEntity());
}
}
@Test
public void exportAttributeValues_unknownSlug_throwsNotFound() {
NotFoundException exception = assertThrows(NotFoundException.class,
() -> callAuthenticatedAs("export-dooris-ssh-keys",
() -> new AttributeEndpointsResourceProvider(session).exportAttributeValues("unknown-slug")));
assertEquals(404, exception.getResponse().getStatus());
assertTrue(exception.getMessage().contains("not found"));
}
@Test
public void exportAttributeValues_duplicateSlugConfiguration_throwsNotFound() {
List<ComponentModel> duplicateComponents = List.of(
attributeEndpointComponent("duplicate-slug",
"export-dooris-ssh-keys", "dooris-authorized", "dooris-ssh-keys"),
attributeEndpointComponent("duplicate-slug",
"export-dooris-ssh-keys", "dooris-authorized", "dooris-ssh-keys"));
when(realm.getComponentsStream()).thenAnswer(inv -> duplicateComponents.stream());
NotFoundException exception = assertThrows(NotFoundException.class,
() -> callAuthenticatedAs("export-dooris-ssh-keys",
() -> new AttributeEndpointsResourceProvider(session).exportAttributeValues("duplicate-slug")));
assertEquals(404, exception.getResponse().getStatus());
assertTrue(exception.getMessage().contains("Multiple configurations exist"));
}
@Test
public void exportAttributeValues_missingAuthRole_throwsServerError() {
List<ComponentModel> components = List.of(
attributeEndpointComponent("bad-auth-role",
"nonexistent-role", "dooris-authorized", "dooris-ssh-keys"));
when(realm.getComponentsStream()).thenAnswer(inv -> components.stream());
ServerErrorException exception = assertThrows(ServerErrorException.class,
() -> callAuthenticatedAs("export-dooris-ssh-keys",
() -> new AttributeEndpointsResourceProvider(session).exportAttributeValues("bad-auth-role")));
assertEquals(500, exception.getResponse().getStatus());
assertTrue(exception.getMessage().contains("auth-role does not exist"));
}
@Test
public void exportAttributeValues_missingMatchRole_throwsServerError() {
List<ComponentModel> components = List.of(
attributeEndpointComponent("bad-match-role",
"export-dooris-ssh-keys", "nonexistent-role", "dooris-ssh-keys"));
when(realm.getComponentsStream()).thenAnswer(inv -> components.stream());
ServerErrorException exception = assertThrows(ServerErrorException.class,
() -> callAuthenticatedAs("export-dooris-ssh-keys",
() -> new AttributeEndpointsResourceProvider(session).exportAttributeValues("bad-match-role")));
assertEquals(500, exception.getResponse().getStatus());
assertTrue(exception.getMessage().contains("match-role does not exist"));
}
@Test
public void exportAttributeValues_invalidAttributeRegex_throwsServerError() {
ComponentModel component = attributeEndpointComponent("bad-regex",
"export-dooris-ssh-keys", "dooris-authorized", "dooris-ssh-keys");
component.put("attribute-regex", "[");
when(realm.getComponentsStream()).thenAnswer(inv -> Stream.of(component));
ServerErrorException exception = assertThrows(ServerErrorException.class,
() -> callAuthenticatedAs("export-dooris-ssh-keys",
() -> new AttributeEndpointsResourceProvider(session).exportAttributeValues("bad-regex")));
assertEquals(500, exception.getResponse().getStatus());
assertTrue(exception.getMessage().contains("attribute-regex is not a valid regex pattern"));
}
@Test
public void exportAttributeValues_callerMissingAuthRole_throwsForbidden() {
ForbiddenException exception = assertThrows(ForbiddenException.class,
() -> callAuthenticatedAs("not-a-configured-role",
() -> new AttributeEndpointsResourceProvider(session).exportAttributeValues("dooris-ssh-keys")));
assertEquals(403, exception.getResponse().getStatus());
}
/**
* Runs {@code call} as if authenticated by a bearer token belonging to a service account
* that holds {@code requiredRole}, by intercepting construction of the
* {@link AppAuthManager.BearerTokenAuthenticator} the provider creates internally.
*/
private Response callAuthenticatedAs(String requiredRole, Supplier<Response> call) {
UserModel serviceAccount = mock(UserModel.class);
when(serviceAccount.hasRole(roles.get(requiredRole))).thenReturn(true);
try (MockedConstruction<AppAuthManager.BearerTokenAuthenticator> ignored = Mockito.mockConstruction(
AppAuthManager.BearerTokenAuthenticator.class,
(mock, ctx) -> when(mock.authenticate())
.thenReturn(new AuthResult(serviceAccount, null, null, null)))) {
return call.get();
}
}
private static ComponentModel attributeEndpointComponent(String slug, String authRole, String matchRole,
String attributeGroup) {
ComponentModel component = new ComponentModel();
component.setProviderId(AdminUiPage.PROVIDER_ID);
component.put("slug", slug);
component.put("auth-role", authRole);
component.put("match-role", matchRole);
component.put("attribute-group", attributeGroup);
return component;
}
/**
* Mirrors the User Profile configuration in testing.
*/
private static UPConfig testUserProfileConfig() {
UPConfig upConfig = new UPConfig();
upConfig.setGroups(List.of(new UPGroup("dooris-ssh-keys"), new UPGroup("mailing-list-addresses")));
upConfig.setAttributes(List.of(
attribute(SSH_KEY_1, "dooris-ssh-keys"),
attribute(SSH_KEY_2, "dooris-ssh-keys"),
attribute(MAILING_LIST_CHAOS, "mailing-list-addresses"),
attribute(MAILING_LIST_INTERN, "mailing-list-addresses")));
return upConfig;
}
private static UPAttribute attribute(String name, String group) {
UPAttribute attribute = new UPAttribute(name);
attribute.setGroup(group);
return attribute;
}
private UserModel user(String username, Map<String, String> attributes, List<String> roleNames) {
UserModel user = mock(UserModel.class);
when(user.getUsername()).thenReturn(username);
when(user.getAttributeStream(anyString())).thenAnswer(inv -> Stream.empty());
attributes.forEach((name, value) ->
when(user.getAttributeStream(eq(name))).thenAnswer(inv -> Stream.of(value)));
when(user.hasRole(any(RoleModel.class))).thenReturn(false);
roleNames.forEach(name -> when(user.hasRole(roles.get(name))).thenReturn(true));
return user;
}
}