initial version of an endpoint (#3095)
* initial version of an endpoint * added open-api yaml * changed to list available added mapping to own domain model * added search and roles endpoint * use new endpoints * removed `Representation` suffix and made id role and client mandatory fields * fixed test
This commit is contained in:
parent
7729a54ad6
commit
84da777693
11 changed files with 514 additions and 36 deletions
|
@ -555,7 +555,10 @@ describe("Clients test", () => {
|
|||
.checkNotificationMessage("Associated roles have been added", true);
|
||||
|
||||
// Add associated client role
|
||||
associatedRolesPage.addAssociatedRoleFromSearchBar("create-client", true);
|
||||
associatedRolesPage.addAssociatedRoleFromSearchBar(
|
||||
"manage-account",
|
||||
true
|
||||
);
|
||||
commonPage
|
||||
.masthead()
|
||||
.checkNotificationMessage("Associated roles have been added", true);
|
||||
|
@ -609,7 +612,7 @@ describe("Clients test", () => {
|
|||
rolesTab.goToAssociatedRolesTab();
|
||||
|
||||
cy.get('td[data-label="Role name"]')
|
||||
.contains("create-client")
|
||||
.contains("manage-account")
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get("input").click();
|
||||
|
|
|
@ -122,7 +122,10 @@ describe("Realm roles test", () => {
|
|||
rolesTab.goToAssociatedRolesTab();
|
||||
|
||||
// Add associated client role
|
||||
associatedRolesPage.addAssociatedRoleFromSearchBar("manage-clients", true);
|
||||
associatedRolesPage.addAssociatedRoleFromSearchBar(
|
||||
"manage-account-links",
|
||||
true
|
||||
);
|
||||
masthead.checkNotificationMessage("Associated roles have been added", true);
|
||||
});
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the 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 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<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 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.keycloak</groupId>
|
||||
|
@ -34,6 +35,10 @@
|
|||
<nexus.staging.plugin.version>1.6.13</nexus.staging.plugin.version>
|
||||
<frontend.maven.plugin.version>1.12.1</frontend.maven.plugin.version>
|
||||
<frontend.maven.plugin.nodeVersion>v18.7.0</frontend.maven.plugin.nodeVersion>
|
||||
|
||||
<keycloak.version>19.0.1</keycloak.version>
|
||||
<kotlin.version>1.7.10</kotlin.version>
|
||||
<mapstruct.version>1.5.2.Final</mapstruct.version>
|
||||
</properties>
|
||||
|
||||
<licenses>
|
||||
|
@ -72,6 +77,51 @@
|
|||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi</artifactId>
|
||||
<version>${keycloak.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi-private</artifactId>
|
||||
<version>${keycloak.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<version>${keycloak.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.openapi</groupId>
|
||||
<artifactId>microprofile-openapi-api</artifactId>
|
||||
<version>3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<version>6.14.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>gpg</id>
|
||||
|
@ -105,7 +155,7 @@
|
|||
<id>install-tarball</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>installTarball</name>
|
||||
<name>installTarball</name>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
|
@ -122,7 +172,9 @@
|
|||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>install ${project.basedir}/keycloak-nodejs-admin-client.tgz ${project.basedir}/keycloak-js.tgz</arguments>
|
||||
<arguments>install ${project.basedir}/keycloak-nodejs-admin-client.tgz
|
||||
${project.basedir}/keycloak-js.tgz
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
@ -139,6 +191,98 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>kapt</id>
|
||||
<goals>
|
||||
<goal>kapt</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sourceDirs>
|
||||
<sourceDir>src/main/java</sourceDir>
|
||||
</sourceDirs>
|
||||
<annotationProcessorPaths>
|
||||
<annotationProcessorPath>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</annotationProcessorPath>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<jvmTarget>1.8</jvmTarget>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<executions>
|
||||
<!-- Replacing default-compile as it is treated specially by maven -->
|
||||
<execution>
|
||||
<id>default-compile</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
<!-- Replacing default-testCompile as it is treated specially by maven -->
|
||||
<execution>
|
||||
<id>default-testCompile</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>java-compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>java-test-compile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>smallrye-open-api-maven-plugin</artifactId>
|
||||
<groupId>io.smallrye</groupId>
|
||||
<version>2.1.22</version>
|
||||
<configuration>
|
||||
<scanPackages>org.keycloak.admin.ui.rest</scanPackages>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>generate-schema</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
|
@ -149,7 +293,6 @@
|
|||
<nexusUrl>${jboss.repo.nexusUrl}</nexusUrl>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
package org.keycloak.admin.ui.rest
|
||||
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation
|
||||
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse
|
||||
import org.keycloak.admin.ui.rest.model.ClientRole
|
||||
import org.keycloak.admin.ui.rest.model.RoleMapper
|
||||
import org.keycloak.models.KeycloakSession
|
||||
import org.keycloak.models.RealmModel
|
||||
import org.keycloak.models.RoleModel
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator
|
||||
import org.mapstruct.factory.Mappers
|
||||
import java.util.*
|
||||
import java.util.function.Predicate
|
||||
import java.util.stream.Collectors
|
||||
import javax.ws.rs.*
|
||||
import javax.ws.rs.core.Context
|
||||
import javax.ws.rs.core.MediaType
|
||||
|
||||
@Path("/")
|
||||
open class AdminUIExtendedResource(
|
||||
private var realm: RealmModel,
|
||||
private var auth: AdminPermissionEvaluator,
|
||||
) {
|
||||
@Context
|
||||
var session: KeycloakSession? = null
|
||||
|
||||
@GET
|
||||
@Path("/clientScopes/{id}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(
|
||||
summary = "List all composite client roles for this client scope",
|
||||
description = "This endpoint returns all the client role mapping for a specific client scope"
|
||||
)
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "",
|
||||
content = [Content(
|
||||
schema = Schema(
|
||||
type = SchemaType.ARRAY,
|
||||
implementation = ClientRole::class
|
||||
)
|
||||
)]
|
||||
)
|
||||
fun listCompositeClientScopeRoleMappings(
|
||||
@PathParam("id") id: String,
|
||||
@QueryParam("first") @DefaultValue("0") first: Long,
|
||||
@QueryParam("max") @DefaultValue("10") max: Long,
|
||||
@QueryParam("search") @DefaultValue("") search: String
|
||||
): List<ClientRole> {
|
||||
val scopeContainer = realm.getClientScopeById(id) ?: throw NotFoundException("Could not find client scope")
|
||||
auth.clients().requireView(scopeContainer)
|
||||
|
||||
return availableMapping(Predicate<RoleModel?> { r -> scopeContainer.hasDirectScope(r) }.negate(), first, max, search)
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/clients/{id}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(
|
||||
summary = "List all composite client roles for this client",
|
||||
description = "This endpoint returns all the client role mapping for a specific client"
|
||||
)
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "",
|
||||
content = [Content(
|
||||
schema = Schema(
|
||||
type = SchemaType.ARRAY,
|
||||
implementation = ClientRole::class
|
||||
)
|
||||
)]
|
||||
)
|
||||
fun listCompositeClientRoleMappings(
|
||||
@PathParam("id") id: String,
|
||||
@QueryParam("first") @DefaultValue("0") first: Long,
|
||||
@QueryParam("max") @DefaultValue("10") max: Long,
|
||||
@QueryParam("search") @DefaultValue("") search: String
|
||||
): List<ClientRole> {
|
||||
val scopeContainer = realm.getClientById(id) ?: throw NotFoundException("Could not find client")
|
||||
auth.clients().requireView(scopeContainer)
|
||||
|
||||
return availableMapping(Predicate<RoleModel?> { r -> scopeContainer.hasDirectScope(r) }.negate(), first, max, search)
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/groups/{id}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(
|
||||
summary = "List all composite client roles for this group",
|
||||
description = "This endpoint returns all the client role mapping for a specific group"
|
||||
)
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "",
|
||||
content = [Content(
|
||||
schema = Schema(
|
||||
type = SchemaType.ARRAY,
|
||||
implementation = ClientRole::class
|
||||
)
|
||||
)]
|
||||
)
|
||||
fun listCompositeGroupRoleMappings(
|
||||
@PathParam("id") id: String,
|
||||
@QueryParam("first") @DefaultValue("0") first: Long,
|
||||
@QueryParam("max") @DefaultValue("10") max: Long,
|
||||
@QueryParam("search") @DefaultValue("") search: String
|
||||
): List<ClientRole> {
|
||||
val scopeContainer = realm.getGroupById(id) ?: throw NotFoundException("Could not find group")
|
||||
auth.groups().requireView(scopeContainer)
|
||||
|
||||
return availableMapping(Predicate<RoleModel?> { r -> scopeContainer.hasDirectRole(r) }.negate(), first, max, search)
|
||||
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/users/{id}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(
|
||||
summary = "List all composite client roles for this user",
|
||||
description = "This endpoint returns all the client role mapping for a specific user"
|
||||
)
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "",
|
||||
content = [Content(
|
||||
schema = Schema(
|
||||
type = SchemaType.ARRAY,
|
||||
implementation = ClientRole::class
|
||||
)
|
||||
)]
|
||||
)
|
||||
fun listCompositeUserRoleMappings(
|
||||
@PathParam("id") id: String,
|
||||
@QueryParam("first") @DefaultValue("0") first: Long,
|
||||
@QueryParam("max") @DefaultValue("10") max: Long,
|
||||
@QueryParam("search") @DefaultValue("") search: String
|
||||
): List<ClientRole> {
|
||||
val user = session?.users()?.getUserById(realm, id)
|
||||
?: if (auth.users().canQuery()) throw NotFoundException("User not found") else throw ForbiddenException()
|
||||
auth.users().requireView(user)
|
||||
|
||||
return availableMapping(Predicate<RoleModel?> { r -> user.hasDirectRole(r) }.negate(), first, max, search)
|
||||
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/roles/{id}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(
|
||||
summary = "List all composite client roles",
|
||||
description = "This endpoint returns all the client role"
|
||||
)
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "",
|
||||
content = [Content(
|
||||
schema = Schema(
|
||||
type = SchemaType.ARRAY,
|
||||
implementation = ClientRole::class
|
||||
)
|
||||
)]
|
||||
)
|
||||
fun listCompositeRoleMappings(
|
||||
@QueryParam("first") @DefaultValue("0") first: Long,
|
||||
@QueryParam("max") @DefaultValue("10") max: Long,
|
||||
@QueryParam("search") @DefaultValue("") search: String
|
||||
): List<ClientRole> {
|
||||
val clients = realm.clientsStream
|
||||
val mapper = Mappers.getMapper(RoleMapper::class.java)
|
||||
return clients
|
||||
.flatMap { c -> c.rolesStream }
|
||||
.map { r -> mapper.convertToRepresentation(r, realm.clientsStream) }
|
||||
.skip(first)
|
||||
.limit(max)
|
||||
.collect(Collectors.toList()) ?: Collections.emptyList()
|
||||
}
|
||||
|
||||
private fun availableMapping(
|
||||
predicate: Predicate<RoleModel?>,
|
||||
first: Long,
|
||||
max: Long,
|
||||
search: String
|
||||
): List<ClientRole> {
|
||||
val clients = realm.clientsStream
|
||||
val mapper = Mappers.getMapper(RoleMapper::class.java)
|
||||
return clients
|
||||
.flatMap { c -> c.rolesStream }
|
||||
.filter(predicate)
|
||||
.filter(auth.roles()::canMapClientScope)
|
||||
|
||||
.map { r -> mapper.convertToRepresentation(r, realm.clientsStream) }
|
||||
.filter { r -> r.client?.indexOf(search) != -1 || r.role.indexOf(search) != -1 }
|
||||
.skip(first)
|
||||
.limit(max)
|
||||
.collect(Collectors.toList()) ?: Collections.emptyList()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.keycloak.admin.ui.rest
|
||||
|
||||
import org.keycloak.Config
|
||||
import org.keycloak.models.KeycloakSession
|
||||
import org.keycloak.models.KeycloakSessionFactory
|
||||
import org.keycloak.models.RealmModel
|
||||
import org.keycloak.services.resources.admin.AdminEventBuilder
|
||||
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider
|
||||
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator
|
||||
|
||||
class AdminUIRestEndpointProvider : AdminRealmResourceProviderFactory, AdminRealmResourceProvider {
|
||||
override fun create(session: KeycloakSession): AdminRealmResourceProvider {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun init(config: Config.Scope) {}
|
||||
override fun postInit(factory: KeycloakSessionFactory) {}
|
||||
override fun close() {}
|
||||
override fun getId(): String {
|
||||
return "admin-ui"
|
||||
}
|
||||
|
||||
override fun getResource(
|
||||
session: KeycloakSession,
|
||||
realm: RealmModel,
|
||||
auth: AdminPermissionEvaluator,
|
||||
adminEvent: AdminEventBuilder
|
||||
): Any {
|
||||
return AdminUIExtendedResource(realm, auth)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.keycloak.admin.ui.rest.model
|
||||
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema
|
||||
|
||||
data class ClientRole(
|
||||
@field:Schema(required = true) var id: String,
|
||||
@field:Schema(required = true) var role: String,
|
||||
@field:Schema(required = true) var client: String?,
|
||||
var description: String?
|
||||
) {
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.keycloak.admin.ui.rest.model
|
||||
|
||||
import org.keycloak.models.ClientModel
|
||||
import org.keycloak.models.RoleModel
|
||||
import org.mapstruct.*
|
||||
import java.util.stream.Stream
|
||||
|
||||
@Mapper
|
||||
abstract class RoleMapper {
|
||||
|
||||
@Mapping(source = "name", target = "role")
|
||||
@Mapping(target = "client", ignore = true)
|
||||
abstract fun convertToRepresentation(role: RoleModel, @Context clientModel: Stream<ClientModel>): ClientRole
|
||||
|
||||
@AfterMapping
|
||||
fun convert(role: RoleModel, @MappingTarget clientRole: ClientRole, @Context list: Stream<ClientModel>) {
|
||||
clientRole.client = list.filter { c -> role.containerId == c.id }.findFirst().get().clientId
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
org.keycloak.admin.ui.rest.AdminUIRestEndpointProvider
|
|
@ -15,7 +15,9 @@ import { KeycloakDataTable } from "../table-toolbar/KeycloakDataTable";
|
|||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import useLocaleSort from "../../utils/useLocaleSort";
|
||||
import { ResourcesKey, Row, ServiceRole } from "./RoleMapping";
|
||||
import { getAvailableClientRoles, getAvailableRoles } from "./queries";
|
||||
import { getAvailableRoles } from "./queries";
|
||||
import { getAvailableClientRoles } from "./resource";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
|
||||
type AddRoleMappingModalProps = {
|
||||
id: string;
|
||||
|
@ -40,6 +42,7 @@ export const AddRoleMappingModal = ({
|
|||
}: AddRoleMappingModalProps) => {
|
||||
const { t } = useTranslation("common");
|
||||
const { adminClient } = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
|
||||
const [searchToggle, setSearchToggle] = useState(false);
|
||||
|
||||
|
@ -69,19 +72,28 @@ export const AddRoleMappingModal = ({
|
|||
return localeSort(roles, compareRow);
|
||||
};
|
||||
|
||||
/* this is still pretty expensive querying all client and then all roles */
|
||||
const clientRolesLoader = async (): Promise<Row[]> => {
|
||||
const allClients = await adminClient.clients.find();
|
||||
const clientRolesLoader = async (
|
||||
first?: number,
|
||||
max?: number,
|
||||
search?: string
|
||||
): Promise<Row[]> => {
|
||||
const roles = await getAvailableClientRoles({
|
||||
adminClient,
|
||||
id,
|
||||
realm,
|
||||
type,
|
||||
first: first || 0,
|
||||
max: max || 10,
|
||||
search,
|
||||
});
|
||||
|
||||
const roles = (
|
||||
await Promise.all(
|
||||
allClients.map((client) =>
|
||||
getAvailableClientRoles(adminClient, type, id, client)
|
||||
)
|
||||
)
|
||||
).flat();
|
||||
|
||||
return localeSort(roles, compareRow);
|
||||
return localeSort(
|
||||
roles.map((e) => ({
|
||||
client: { clientId: e.client },
|
||||
role: { id: e.id, name: e.role, description: e.description },
|
||||
})),
|
||||
compareRow
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -119,6 +131,7 @@ export const AddRoleMappingModal = ({
|
|||
key={key}
|
||||
onSelect={(rows) => setSelectedRows([...rows])}
|
||||
searchPlaceholderKey="clients:searchByRoleName"
|
||||
isPaginated={filterType === "clients"}
|
||||
searchTypeComponent={
|
||||
<ToolbarItem>
|
||||
<Dropdown
|
||||
|
|
|
@ -181,19 +181,3 @@ export const getAvailableRoles = async (
|
|||
role,
|
||||
}));
|
||||
};
|
||||
|
||||
export const getAvailableClientRoles = async (
|
||||
adminClient: KeycloakAdminClient,
|
||||
type: ResourcesKey,
|
||||
id: string,
|
||||
client: ClientRepresentation
|
||||
) => {
|
||||
const query = mapping[type]!.listAvailable[0];
|
||||
return (
|
||||
await applyQuery(adminClient, type === "roles" ? "clients" : type, query, {
|
||||
id: type === "roles" ? client.id : id,
|
||||
client: client.id,
|
||||
clientUniqueId: client.id,
|
||||
})
|
||||
).map((role) => ({ role, client: { clientId: client.id, ...client } }));
|
||||
};
|
||||
|
|
47
src/components/role-mapping/resource.ts
Normal file
47
src/components/role-mapping/resource.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import { addTrailingSlash } from "../../util";
|
||||
import { getAuthorizationHeaders } from "../../utils/getAuthorizationHeaders";
|
||||
|
||||
type AvailableClientRolesQuery = {
|
||||
adminClient: KeycloakAdminClient;
|
||||
id: string;
|
||||
realm: string;
|
||||
type: string;
|
||||
first: number;
|
||||
max: number;
|
||||
search?: string;
|
||||
};
|
||||
|
||||
type ClientRole = {
|
||||
id: string;
|
||||
role: string;
|
||||
description?: string;
|
||||
client?: string;
|
||||
};
|
||||
|
||||
export const getAvailableClientRoles = async ({
|
||||
adminClient,
|
||||
id,
|
||||
realm,
|
||||
type,
|
||||
first,
|
||||
max,
|
||||
search,
|
||||
}: AvailableClientRolesQuery): Promise<ClientRole[]> => {
|
||||
const accessToken = await adminClient.getAccessToken();
|
||||
const baseUrl = adminClient.baseUrl;
|
||||
|
||||
const response = await fetch(
|
||||
`${addTrailingSlash(
|
||||
baseUrl
|
||||
)}admin/realms/${realm}/admin-ui/${type}/${id}?first=${first}&max=${max}${
|
||||
search ? "&search=" + search : ""
|
||||
}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: getAuthorizationHeaders(accessToken),
|
||||
}
|
||||
);
|
||||
|
||||
return await response.json();
|
||||
};
|
Loading…
Reference in a new issue