Add just the testing setup from #26.
All checks were successful
/ Verify (pull_request) Successful in 1m12s

Important: there are changes here to adapt the tests and the Keycloak test realm that are incompatible with the changes in #29. Ultimately, the changes in #29 need to be applied over this.
This commit is contained in:
Stefan Bethke 2026-07-19 10:27:51 +02:00
commit f204ab7f28
8 changed files with 428 additions and 27 deletions

View file

@ -1,6 +1,12 @@
install:
mvn -f attribute-endpoints-provider install
verify: verify:
mvn -f attribute-endpoints-provider verify mvn -f attribute-endpoints-provider verify
test:
mvn -f attribute-endpoints-provider test
clean: clean:
mvn -f attribute-endpoints-provider clean mvn -f attribute-endpoints-provider clean

View file

@ -28,6 +28,19 @@
<scope>test</scope> <scope>test</scope>
</dependency> </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> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId> <artifactId>keycloak-server-spi-private</artifactId>

View file

@ -0,0 +1,315 @@
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() {
try (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());
}
}
/* NOTYET
@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());
}
}
*/
/* NOTYET
@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());
}
}
*/
/* NOTYET
@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());
}
}
*/
/* NOTYET
@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;
}
}

View file

@ -1,9 +1,11 @@
# Creating a Testing Keycloak # Integration Testing
This directory contains infrastructure and tests to verify end-to-end operation of the provider.
## Creating a Testing Keycloak
The Docker Compose setup in this directory sets up Keycloak and imports a realm `testing`. The Docker Compose setup in this directory sets up Keycloak and imports a realm `testing`.
## Starting Keycloak
Run `docker compose up -d` to start Keycloak. The admin console will be available at http://localhost:8080/. Run `docker compose up -d` to start Keycloak. The admin console will be available at http://localhost:8080/.
You can attach a Java debugger at `localhost:8081`. You can attach a Java debugger at `localhost:8081`.
@ -11,7 +13,7 @@ You can attach a Java debugger at `localhost:8081`.
The realm export is in `import/testing.json`. The realm export is in `import/testing.json`.
It will be imported automatically when Keycloak starts. It will be imported automatically when Keycloak starts.
## Updating the Realm ### Updating the Realm
If you want to make changes to the testing realm and persist them, you need to export the realm. If you want to make changes to the testing realm and persist them, you need to export the realm.
@ -25,16 +27,36 @@ docker compose exec -it keycloak sh -c "cp -rp /opt/keycloak/data/h2 /tmp && env
## Running Integration Tests ## Running Integration Tests
This directory also contains shell scripts that exercise the attribute endpoint. This directory contains shell scripts that exercise the attribute endpoint.
Bring up Keycloak with `docker compose up -d`, then run one of the `client-test-export-`*`.sh` scripts. Bring up Keycloak with `docker compose up -d`, then run one of the `client-test-export-`*`.sh` scripts.
```shell ```shell
$ ./client-test-export-dooris.sh $ ./client-test-export-dooris.sh
All attributes collated into a single list
[ [
"hacker-ssh-key-1", "hacker-ssh-key-1",
"hacker-ssh-key-2" "hacker-ssh-key-2"
] ]
Results mapped per attribute
{
"ssh-key-1": [
"hacker-ssh-key-1"
],
"ssh-key-2": [
"hacker-ssh-key-2"
]
}
$ ./client-test-export-mailing-lists.sh
chaos@
[
"hacker+chaos@example.net",
"tester+chaos@example.com"
]
intern@
[
"hacker+intern@example.net"
]
``` ```
## Realm `testing` Configuration ## Realm `testing` Configuration
@ -43,16 +65,16 @@ The realm contains these objects:
* Clients: * Clients:
* `export-dooris-ssh-keys` with secret `export-dooris-ssh-keys-secret` and user `service-account-export-dooris-ssh-keys` * `export-dooris-ssh-keys` with secret `export-dooris-ssh-keys-secret` and user `service-account-export-dooris-ssh-keys`
* `export-mailing-list-addresses` with secret `export-mailing-list-addresses-secret` and role `export-mailing-list-addresses` * `export-mailing-list-addresses` with secret `export-mailing-list-addresses-secret` and user `service-account-export-mailing-list-addresses`
* Realm Roles: * Realm Roles:
* `dooris-authorized` * `dooris-authorized` to assign to users whose SSH keys should be exported
* `mailing-list-chaos-member` * `mailing-list-chaos-member` to assign to users who are a member of the chaos mailing list
* `mailing-list-intern-member` * `mailing-list-intern-member` to assign to users who are a member of the intern mailing list
* `export-dooris-ssh-keys` * `export-dooris-ssh-keys` to assign to service account users that should be allowed to use the dooris endpoint config
* `export-mailing-list-addresses` * `export-mailing-list-addresses` to assign to service account users that should be allowed to use the mailing list endpoint configs
* Groups: * Groups:
* `chaos` with role `mailing-list-chaos-member` * `chaos` with role `mailing-list-chaos-member`
* `ìntern` with roles `dooris-authorized` and `mailing-list-intern-member` * `ìntern` with roles `dooris-authorized`, `mailing-list-chaos-member` and `mailing-list-intern-member`
* Users: * Users:
* `tester` (Tony Tester), email `tester@example.com`, member of group `chaos` * `tester` (Tony Tester), email `tester@example.com`, member of group `chaos`
* Mailing List Addresses: * Mailing List Addresses:
@ -81,7 +103,7 @@ The realm contains these objects:
* `mailing-list-address-chaos` * `mailing-list-address-chaos`
* Attribute Group `mailing-list-addresses` * Attribute Group `mailing-list-addresses`
* `mailing-list-address-intern` * `mailing-list-address-intern`
* Attribute Group `mailing-list-intern` * Attribute Group `mailing-list-addresses`
* `ssh-key-1` * `ssh-key-1`
* Attribute Group `dooris-ssh-keys` * Attribute Group `dooris-ssh-keys`
* `ssh-key-2` * `ssh-key-2`

View file

@ -7,14 +7,21 @@
set -e set -e
ACCESS_TOKEN="$(curl -s --request POST \ ACCESS_TOKEN="$(curl -s --request POST \
--url http://localhost:8080/realms/testing/protocol/openid-connect/token \ --url http://localhost:8080/realms/testing/protocol/openid-connect/token \
--header 'content-type: application/x-www-form-urlencoded' \ --header 'content-type: application/x-www-form-urlencoded' \
--data scope=openid \ --data scope=openid \
--data client_id=export-dooris-ssh-keys \ --data client_id=export-dooris-ssh-keys \
--data client_secret=export-dooris-ssh-keys-secret \ --data client_secret=export-dooris-ssh-keys-secret \
--data grant_type=client_credentials | jq --raw-output .access_token -)" --data grant_type=client_credentials | jq --raw-output .access_token -)"
echo "All attributes collated into a single list"
curl -s --request GET \ curl -s --request GET \
--url http://localhost:8080/realms/testing/attribute-endpoints-provider/export/dooris-ssh-keys \ --url http://localhost:8080/realms/testing/attribute-endpoints-provider/export/dooris-ssh-keys \
--header "authorization: Bearer ${ACCESS_TOKEN}" \ --header "authorization: Bearer ${ACCESS_TOKEN}" \
--header "content-type: application/json" | jq . - --header "content-type: application/json" | jq . -
echo "Results mapped per attribute"
curl -s --request GET \
--url http://localhost:8080/realms/testing/attribute-endpoints-provider/export/dooris-ssh-keys/map \
--header "authorization: Bearer ${ACCESS_TOKEN}" \
--header "content-type: application/json" | jq . -

View file

@ -0,0 +1,27 @@
#!/bin/sh
#
# Use curl to run a test export of Dooris ssh keys
#
set -e
ACCESS_TOKEN="$(curl -s --request POST \
--url http://localhost:8080/realms/testing/protocol/openid-connect/token \
--header 'content-type: application/x-www-form-urlencoded' \
--data scope=openid \
--data client_id=export-mailing-list-addresses \
--data client_secret=export-mailing-list-addresses-secret \
--data grant_type=client_credentials | jq --raw-output .access_token -)"
echo "chaos@"
curl -s --request GET \
--url http://localhost:8080/realms/testing/attribute-endpoints-provider/export/mailing-list-addresses-chaos \
--header "authorization: Bearer ${ACCESS_TOKEN}" \
--header "content-type: application/json" | jq . -
echo "intern@"
curl -s --request GET \
--url http://localhost:8080/realms/testing/attribute-endpoints-provider/export/mailing-list-addresses-intern \
--header "authorization: Bearer ${ACCESS_TOKEN}" \
--header "content-type: application/json" | jq . -

View file

@ -12,6 +12,17 @@ services:
ports: ports:
- "8080:8080" - "8080:8080"
- "8081:8081" - "8081:8081"
networks:
- keycloak
volumes: volumes:
- ./import:/opt/keycloak/data/import/ - ./import:/opt/keycloak/data/import/
- ../attribute-endpoints-provider/target/attribute-endpoints-provider-1.0-SNAPSHOT.jar:/opt/keycloak/providers/attribute-endpoints-provider.jar - ../attribute-endpoints-provider/target/attribute-endpoints-provider-1.0-SNAPSHOT.jar:/opt/keycloak/providers/attribute-endpoints-provider.jar
networks:
# force a "local" IP address that Keycloak accepts HTTP (not HTTPS) requests from
keycloak:
driver: bridge
ipam:
config:
- subnet: 192.168.231.128/29
gateway: 192.168.231.129

View file

@ -1478,7 +1478,7 @@
"subType" : "authenticated", "subType" : "authenticated",
"subComponents" : { }, "subComponents" : { },
"config" : { "config" : {
"allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper" ] "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper" ]
} }
}, { }, {
"id" : "a49de9bf-462b-4c61-bb52-0373732f4b1b", "id" : "a49de9bf-462b-4c61-bb52-0373732f4b1b",
@ -1538,7 +1538,7 @@
"subType" : "anonymous", "subType" : "anonymous",
"subComponents" : { }, "subComponents" : { },
"config" : { "config" : {
"allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper" ] "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "saml-user-attribute-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper" ]
} }
}, { }, {
"id" : "dd4024e9-f080-4319-aeb7-7f9906c345c5", "id" : "dd4024e9-f080-4319-aeb7-7f9906c345c5",
@ -1605,8 +1605,8 @@
"subComponents" : { }, "subComponents" : { },
"config" : { "config" : {
"match-role" : [ "mailing-list-intern-member" ], "match-role" : [ "mailing-list-intern-member" ],
"auth-role" : [ "export-mailing-list-addresses" ],
"attribute-group" : [ "mailing-list-addresses" ], "attribute-group" : [ "mailing-list-addresses" ],
"auth-role" : [ "export-mailing-list-addresses" ],
"slug" : [ "mailing-list-addresses-intern" ] "slug" : [ "mailing-list-addresses-intern" ]
} }
}, { }, {
@ -1615,8 +1615,8 @@
"subComponents" : { }, "subComponents" : { },
"config" : { "config" : {
"match-role" : [ "dooris-authorized" ], "match-role" : [ "dooris-authorized" ],
"auth-role" : [ "export-dooris-ssh-keys" ],
"attribute-group" : [ "dooris-ssh-keys" ], "attribute-group" : [ "dooris-ssh-keys" ],
"auth-role" : [ "export-dooris-ssh-keys" ],
"slug" : [ "dooris-ssh-keys" ] "slug" : [ "dooris-ssh-keys" ]
} }
}, { }, {