aboutsummaryrefslogtreecommitdiffstats
path: root/aai-traversal
diff options
context:
space:
mode:
Diffstat (limited to 'aai-traversal')
-rw-r--r--aai-traversal/pom.xml41
-rw-r--r--aai-traversal/src/test/java/org/onap/aai/it/multitenancy/KeycloakTestConfiguration.java73
-rw-r--r--aai-traversal/src/test/java/org/onap/aai/it/multitenancy/KeycloakTestProperties.java44
-rw-r--r--aai-traversal/src/test/java/org/onap/aai/it/multitenancy/MultiTenancyIT.java189
-rw-r--r--aai-traversal/src/test/java/org/onap/aai/it/multitenancy/RoleHandler.java56
-rw-r--r--aai-traversal/src/test/resources/it/application-keycloak-test.properties16
-rw-r--r--aai-traversal/src/test/resources/it/multi-tenancy-realm.json173
7 files changed, 560 insertions, 32 deletions
diff --git a/aai-traversal/pom.xml b/aai-traversal/pom.xml
index 2cdfd32..13402a0 100644
--- a/aai-traversal/pom.xml
+++ b/aai-traversal/pom.xml
@@ -102,7 +102,9 @@
<micrometer-jersey2>1.6.6</micrometer-jersey2>
<testcontainers.version>1.6.1</testcontainers.version>
<!-- End of Default ONAP Schema Properties -->
- <build.helper-maven-plugin.version>3.2.0</build.helper-maven-plugin.version>
+
+ <!-- Integration tests will be skipped by default. Could be enabled here or by -DskipITs=false-->
+ <skipITs>true</skipITs>
</properties>
<profiles>
<!-- Docker profile to be used for building docker image and pushing to
@@ -1087,37 +1089,12 @@
</configuration>
</plugin>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>build-helper-maven-plugin</artifactId>
- <version>${build.helper-maven-plugin.version}</version>
- <executions>
- <execution>
- <id>add-integration-test-source</id>
- <phase>generate-test-sources</phase>
- <goals>
- <goal>add-test-source</goal>
- </goals>
- <configuration>
- <sources>
- <source>src/it/java</source>
- </sources>
- </configuration>
- </execution>
- <execution>
- <id>add-integration-test-resource</id>
- <phase>generate-test-resources</phase>
- <goals>
- <goal>add-test-resource</goal>
- </goals>
- <configuration>
- <resources>
- <resource>
- <directory>src/it/resources</directory>
- </resource>
- </resources>
- </configuration>
- </execution>
- </executions>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>3.0.0-M4</version>
+ <configuration>
+ <classesDirectory>${project.build.outputDirectory}</classesDirectory>
+ </configuration>
</plugin>
</plugins>
</build>
diff --git a/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/KeycloakTestConfiguration.java b/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/KeycloakTestConfiguration.java
new file mode 100644
index 0000000..4b87cde
--- /dev/null
+++ b/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/KeycloakTestConfiguration.java
@@ -0,0 +1,73 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.it.multitenancy;
+
+import com.github.dockerjava.api.model.ExposedPort;
+import com.github.dockerjava.api.model.HostConfig;
+import com.github.dockerjava.api.model.PortBinding;
+import com.github.dockerjava.api.model.Ports;
+import dasniko.testcontainers.keycloak.KeycloakContainer;
+import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.KeycloakBuilder;
+import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+
+@TestConfiguration
+class KeycloakTestConfiguration {
+
+ @Bean
+ public AdapterConfig adapterConfig() {
+ return new KeycloakSpringBootProperties();
+ }
+
+ @Bean
+ KeycloakContainer keycloakContainer(KeycloakTestProperties properties) {
+ KeycloakContainer keycloak = new KeycloakContainer("jboss/keycloak:12.0.4")
+ .withRealmImportFile(properties.realmJson)
+ .withCreateContainerCmdModifier(cmd -> cmd.withHostConfig(
+ new HostConfig().withPortBindings(new PortBinding(Ports.Binding.bindPort(Integer.parseInt(properties.port)), new ExposedPort(8080)))
+ ));
+ keycloak.start();
+ return keycloak;
+ }
+
+ @Bean
+ Keycloak keycloakAdminClient(KeycloakContainer keycloak, KeycloakTestProperties properties) {
+ return KeycloakBuilder.builder()
+ .serverUrl(keycloak.getAuthServerUrl())
+ .realm(properties.realm)
+ .clientId(properties.adminCli)
+ .username(keycloak.getAdminUsername())
+ .password(keycloak.getAdminPassword())
+ .build();
+ }
+
+ @Bean
+ RoleHandler roleHandler(Keycloak adminClient, KeycloakTestProperties properties) {
+ return new RoleHandler(adminClient, properties);
+ }
+
+ @Bean
+ KeycloakTestProperties properties() {
+ return new KeycloakTestProperties();
+ }
+}
diff --git a/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/KeycloakTestProperties.java b/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/KeycloakTestProperties.java
new file mode 100644
index 0000000..df9db84
--- /dev/null
+++ b/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/KeycloakTestProperties.java
@@ -0,0 +1,44 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.it.multitenancy;
+
+import org.springframework.beans.factory.annotation.Value;
+
+class KeycloakTestProperties {
+
+ @Value("${test.keycloak.realm.json}")
+ public String realmJson;
+
+ @Value("${keycloak.realm}")
+ public String realm;
+
+ @Value("${keycloak.resource}")
+ public String clientId;
+
+ @Value("${test.keycloak.client.secret}")
+ public String clientSecret;
+
+ @Value("${test.keycloak.admin.cli}")
+ public String adminCli;
+
+ @Value("${test.keycloak.auth-server-port}")
+ public String port;
+
+}
diff --git a/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/MultiTenancyIT.java b/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/MultiTenancyIT.java
new file mode 100644
index 0000000..b0fddb4
--- /dev/null
+++ b/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/MultiTenancyIT.java
@@ -0,0 +1,189 @@
+/**
+ * ============LICENSE_START==================================================
+ * org.onap.aai
+ * ===========================================================================
+ * Copyright © 2017-2020 AT&T Intellectual Property. All rights reserved.
+ * ===========================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END====================================================
+ */
+package org.onap.aai.it.multitenancy;
+
+import com.jayway.jsonpath.JsonPath;
+import dasniko.testcontainers.keycloak.KeycloakContainer;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.janusgraph.core.JanusGraphTransaction;
+import org.junit.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.KeycloakBuilder;
+import org.keycloak.representations.AccessTokenResponse;
+import org.onap.aai.PayloadUtil;
+import org.onap.aai.dbmap.AAIGraph;
+import org.onap.aai.rest.AbstractSpringRestTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.*;
+import org.springframework.test.context.TestPropertySource;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+@Import(KeycloakTestConfiguration.class)
+@TestPropertySource(locations = "classpath:it/application-keycloak-test.properties")
+public class MultiTenancyIT extends AbstractSpringRestTest {
+
+ @Autowired
+ private KeycloakContainer keycloakContainer;
+ @Autowired
+ private RoleHandler roleHandler;
+ @Autowired
+ private KeycloakTestProperties properties;
+
+ @Override
+ public void createTestGraph() {
+ JanusGraphTransaction transaction = AAIGraph.getInstance().getGraph().newTransaction();
+ boolean success = true;
+
+ try {
+ GraphTraversalSource g = transaction.traversal();
+
+ g.addV().property("aai-node-type", "pnf")
+ .property("pnf-name", "test-pnf-name-01")
+ .property("prov-status", "in_service")
+ .property("data-owner", "operator")
+ .property("in-maint", false)
+ .property("source-of-truth", "JUNIT")
+ .property("aai-uri", "/network/pnfs/pnf/test-pnf-name-01").next();
+
+ g.addV().property("aai-node-type", "pnf")
+ .property("pnf-name", "test-pnf-name-02")
+ .property("prov-status", "in_service")
+ .property("in-maint", false)
+ .property("source-of-truth", "JUNIT")
+ .property("aai-uri", "/network/pnfs/pnf/test-pnf-name-02").next();
+
+ g.addV().property("aai-node-type", "pnf")
+ .property("pnf-name", "test-pnf-name-03")
+ .property("prov-status", "in_service")
+ .property("data-owner", "selector")
+ .property("in-maint", false)
+ .property("source-of-truth", "JUNIT")
+ .property("aai-uri", "/network/pnfs/pnf/test-pnf-name-03").next();
+
+ g.addV().property("aai-node-type", "pnf")
+ .property("pnf-name", "test-pnf-name-04")
+ .property("prov-status", "in_service")
+ .property("data-owner", "selector")
+ .property("in-maint", false)
+ .property("source-of-truth", "JUNIT")
+ .property("aai-uri", "/network/pnfs/pnf/test-pnf-name-04").next();
+
+ g.addV().property("aai-node-type", "pnf")
+ .property("pnf-name", "test-pnf-name-05")
+ .property("prov-status", "in_service")
+ .property("data-owner", "selector")
+ .property("in-maint", false)
+ .property("source-of-truth", "JUNIT")
+ .property("aai-uri", "/network/pnfs/pnf/test-pnf-name-05").next();
+ } catch (Exception ex) {
+ success = false;
+ } finally {
+ if (success) {
+ transaction.commit();
+ } else {
+ transaction.rollback();
+ fail("Unable to setup the graph");
+ }
+ }
+ }
+
+ @Test
+ public void testDslQueryWithDataOwner() throws Exception {
+ baseUrl = "http://localhost:" + randomPort;
+ String endpoint = baseUrl + "/aai/v23/dsl?format=console";
+ List<Object> queryResults = null;
+ ResponseEntity responseEntity = null;
+
+ Map<String, String> dslQueryMap = new HashMap<>();
+ dslQueryMap.put("dsl-query", "pnf*('prov-status','in_service') ");
+ String payload = PayloadUtil.getTemplatePayload("dsl-query.json", dslQueryMap);
+
+ // get pnf with ran (operator)
+ String username = "ran", password = "ran";
+ headers = this.getHeaders(username, password);
+ httpEntity = new HttpEntity(payload, headers);
+ responseEntity = restTemplate.exchange(endpoint, HttpMethod.PUT, httpEntity, String.class);
+ queryResults = JsonPath.read(responseEntity.getBody().toString(), "$.results");
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertEquals(queryResults.size(), 2);
+
+ // get pnf with bob (operator_readOnly)
+ username = "bob"; password = "bob";
+ headers = this.getHeaders(username, password);
+ httpEntity = new HttpEntity(payload, headers);
+ responseEntity = restTemplate.exchange(endpoint, HttpMethod.PUT, httpEntity, String.class);
+ queryResults = JsonPath.read(responseEntity.getBody().toString(), "$.results");
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertEquals(queryResults.size(), 2);
+
+ // get pnf with ted (selector)
+ username = "ted"; password = "ted";
+ headers = this.getHeaders(username, password);
+ httpEntity = new HttpEntity(payload, headers);
+ responseEntity = restTemplate.exchange(endpoint, HttpMethod.PUT, httpEntity, String.class);
+ queryResults = JsonPath.read(responseEntity.getBody().toString(), "$.results");
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertEquals(queryResults.size(), 4);
+
+ // add role to ted and try to get pnf again
+ roleHandler.addToUser(RoleHandler.OPERATOR, username);
+ headers = this.getHeaders(username, password);
+ httpEntity = new HttpEntity(payload, headers);
+ responseEntity = restTemplate.exchange(endpoint, HttpMethod.PUT, httpEntity, String.class);
+ queryResults = JsonPath.read(responseEntity.getBody().toString(), "$.results");
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertEquals(queryResults.size(), 5);
+ }
+
+ private HttpHeaders getHeaders(String username, String password) {
+ HttpHeaders headers = new HttpHeaders();
+
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+ headers.add("Real-Time", "true");
+ headers.add("X-FromAppId", "JUNIT");
+ headers.add("X-TransactionId", "JUNIT");
+ headers.add("Authorization", "Bearer " + getStringToken(username, password));
+
+ return headers;
+ }
+
+ private String getStringToken(String username, String password) {
+ Keycloak keycloakClient = KeycloakBuilder.builder()
+ .serverUrl(keycloakContainer.getAuthServerUrl())
+ .realm(properties.realm)
+ .clientId(properties.clientId)
+ .clientSecret(properties.clientSecret)
+ .username(username)
+ .password(password)
+ .build();
+
+ AccessTokenResponse tokenResponse = keycloakClient.tokenManager().getAccessToken();
+ assertNotNull(tokenResponse);
+ return tokenResponse.getToken();
+ }
+}
diff --git a/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/RoleHandler.java b/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/RoleHandler.java
new file mode 100644
index 0000000..763c071
--- /dev/null
+++ b/aai-traversal/src/test/java/org/onap/aai/it/multitenancy/RoleHandler.java
@@ -0,0 +1,56 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.it.multitenancy;
+
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.RealmResource;
+
+import java.util.Collections;
+
+class RoleHandler {
+
+ /**
+ Following roles should be the same as given roles in multi-tenancy-realm json file
+ */
+ final static String OPERATOR = "operator";
+ private final Keycloak adminClient;
+ private final KeycloakTestProperties properties;
+
+ RoleHandler(Keycloak adminClient, KeycloakTestProperties properties) {
+ this.adminClient = adminClient;
+ this.properties = properties;
+ }
+
+ void addToUser(String role, String username) {
+ RealmResource realm = adminClient.realm(properties.realm);
+ realm.users().get(username)
+ .roles()
+ .realmLevel()
+ .add(Collections.singletonList(realm.roles().get(role).toRepresentation()));
+ }
+
+ void removeFromUser(String role, String username) {
+ RealmResource realm = adminClient.realm(properties.realm);
+ realm.users().get(username)
+ .roles()
+ .realmLevel()
+ .remove(Collections.singletonList(realm.roles().get(role).toRepresentation()));
+ }
+}
diff --git a/aai-traversal/src/test/resources/it/application-keycloak-test.properties b/aai-traversal/src/test/resources/it/application-keycloak-test.properties
new file mode 100644
index 0000000..632ec81
--- /dev/null
+++ b/aai-traversal/src/test/resources/it/application-keycloak-test.properties
@@ -0,0 +1,16 @@
+test.keycloak.realm.json=it/multi-tenancy-realm.json
+test.keycloak.client.secret=secret
+test.keycloak.admin.cli=admin-cli
+test.keycloak.auth-server-port=58181
+
+keycloak.auth-server-url=http://localhost:58181/auth
+keycloak.realm=aai-resources
+keycloak.resource=aai-resources-app
+keycloak.public-client=true
+keycloak.principal-attribute=preferred_username
+
+keycloak.ssl-required=external
+keycloak.bearer-only=true
+
+multi.tenancy.enabled=true
+spring.profiles.active=production,keycloak
diff --git a/aai-traversal/src/test/resources/it/multi-tenancy-realm.json b/aai-traversal/src/test/resources/it/multi-tenancy-realm.json
new file mode 100644
index 0000000..401187b
--- /dev/null
+++ b/aai-traversal/src/test/resources/it/multi-tenancy-realm.json
@@ -0,0 +1,173 @@
+{
+ "id": "aai-resources",
+ "realm": "aai-resources",
+ "notBefore": 0,
+ "revokeRefreshToken": false,
+ "refreshTokenMaxReuse": 0,
+ "accessTokenLifespan": 300,
+ "accessTokenLifespanForImplicitFlow": 900,
+ "ssoSessionIdleTimeout": 1800,
+ "ssoSessionMaxLifespan": 36000,
+ "ssoSessionIdleTimeoutRememberMe": 0,
+ "ssoSessionMaxLifespanRememberMe": 0,
+ "offlineSessionIdleTimeout": 2592000,
+ "offlineSessionMaxLifespanEnabled": false,
+ "offlineSessionMaxLifespan": 5184000,
+ "clientSessionIdleTimeout": 0,
+ "clientSessionMaxLifespan": 0,
+ "clientOfflineSessionIdleTimeout": 0,
+ "clientOfflineSessionMaxLifespan": 0,
+ "accessCodeLifespan": 60,
+ "accessCodeLifespanUserAction": 300,
+ "accessCodeLifespanLogin": 1800,
+ "actionTokenGeneratedByAdminLifespan": 43200,
+ "actionTokenGeneratedByUserLifespan": 300,
+ "enabled": true,
+ "sslRequired": "external",
+ "registrationAllowed": false,
+ "registrationEmailAsUsername": false,
+ "rememberMe": false,
+ "verifyEmail": false,
+ "loginWithEmailAllowed": true,
+ "duplicateEmailsAllowed": false,
+ "resetPasswordAllowed": false,
+ "editUsernameAllowed": false,
+ "bruteForceProtected": false,
+ "permanentLockout": false,
+ "maxFailureWaitSeconds": 900,
+ "minimumQuickLoginWaitSeconds": 60,
+ "waitIncrementSeconds": 60,
+ "quickLoginCheckMilliSeconds": 1000,
+ "maxDeltaTimeSeconds": 43200,
+ "failureFactor": 30,
+ "users": [
+ {
+ "username": "admin",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "admin"
+ }
+ ],
+ "clientRoles": {
+ "realm-management": ["manage-users", "view-clients", "view-realm", "view-users"]
+ }
+ },
+ {
+ "id": "ran",
+ "username": "ran",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "ran"
+ }
+ ],
+ "realmRoles": [
+ "operator"
+ ]
+ },
+ {
+ "id": "bob",
+ "username": "bob",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "bob"
+ }
+ ],
+ "realmRoles": [
+ "operator_readOnly"
+ ]
+ },
+ {
+ "id": "ted",
+ "username": "ted",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "ted"
+ }
+ ],
+ "realmRoles": [
+ "selector"
+ ]
+ }
+ ],
+ "roles": {
+ "realm": [
+ {
+ "name": "operator",
+ "description": "Operator privileges"
+ },
+ {
+ "name": "operator_readOnly",
+ "description": "Operator's read only privileges"
+ },
+ {
+ "name": "selector",
+ "description": "Selector privileges"
+ },
+ {
+ "name": "selector_readOnly",
+ "description": "Selector's read only privileges"
+ },
+ {
+ "name": "admin",
+ "description": "Administrator privileges"
+ }
+ ]
+ },
+ "clients": [
+ {
+ "clientId": "aai-resources-app",
+ "enabled": true,
+ "secret": "secret",
+ "directAccessGrantsEnabled": true,
+ "authorizationServicesEnabled": true,
+ "authorizationSettings": {
+ "allowRemoteResourceManagement": true,
+ "policyEnforcementMode": "ENFORCING"
+ }
+ }
+ ],
+ "defaultDefaultClientScopes": [
+ "roles",
+ "email",
+ "web-origins",
+ "profile",
+ "role_list"
+ ],
+ "clientScopes": [
+ {
+ "id": "0f7dfd8b-c230-4664-8d77-da85bcc4fe2a",
+ "name": "roles",
+ "description": "OpenID Connect scope for add user roles to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${rolesScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "4b9f8798-8990-4c0d-87d3-034e72655e3b",
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "multivalued": "true",
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "realm_access.roles",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file