added group endpoint that also returns access (#3170)
* added group endpoint that also returns access fixes: #3163 * convert to java * fixed merge errors
This commit is contained in:
parent
df072f5c15
commit
93b0144ff3
8 changed files with 161 additions and 30 deletions
|
@ -17,7 +17,6 @@ import useLocaleSort from "../../utils/useLocaleSort";
|
|||
import { ResourcesKey, Row, ServiceRole } from "./RoleMapping";
|
||||
import { getAvailableRoles } from "./queries";
|
||||
import { getAvailableClientRoles } from "./resource";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
|
||||
type AddRoleMappingModalProps = {
|
||||
id: string;
|
||||
|
@ -42,7 +41,6 @@ export const AddRoleMappingModal = ({
|
|||
}: AddRoleMappingModalProps) => {
|
||||
const { t } = useTranslation("common");
|
||||
const { adminClient } = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
|
||||
const [searchToggle, setSearchToggle] = useState(false);
|
||||
|
||||
|
@ -80,7 +78,6 @@ export const AddRoleMappingModal = ({
|
|||
const roles = await getAvailableClientRoles({
|
||||
adminClient,
|
||||
id,
|
||||
realm,
|
||||
type,
|
||||
first: first || 0,
|
||||
max: max || 10,
|
||||
|
|
|
@ -22,7 +22,6 @@ import { useAdminClient } from "../../context/auth/AdminClient";
|
|||
import { ListEmptyState } from "../list-empty-state/ListEmptyState";
|
||||
import { deleteMapping, getEffectiveRoles, getMapping } from "./queries";
|
||||
import { getEffectiveClientRoles } from "./resource";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
|
||||
import "./role-mapping.css";
|
||||
|
||||
|
@ -89,7 +88,6 @@ export const RoleMapping = ({
|
|||
}: RoleMappingProps) => {
|
||||
const { t } = useTranslation(type);
|
||||
const { adminClient } = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
|
@ -113,7 +111,6 @@ export const RoleMapping = ({
|
|||
effectiveClientRoles = (
|
||||
await getEffectiveClientRoles({
|
||||
adminClient,
|
||||
realm,
|
||||
type,
|
||||
id,
|
||||
})
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import { addTrailingSlash } from "../../util";
|
||||
import { getAuthorizationHeaders } from "../../utils/getAuthorizationHeaders";
|
||||
import { fetchAdminUI } from "../../context/auth/admin-ui-endpoint";
|
||||
|
||||
type BaseClientRolesQuery = {
|
||||
adminClient: KeycloakAdminClient;
|
||||
id: string;
|
||||
realm: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
|
@ -33,29 +31,17 @@ type ClientRole = {
|
|||
const fetchRoles = async ({
|
||||
adminClient,
|
||||
id,
|
||||
realm,
|
||||
type,
|
||||
first,
|
||||
max,
|
||||
search,
|
||||
endpoint,
|
||||
}: Query): Promise<ClientRole[]> => {
|
||||
const accessToken = await adminClient.getAccessToken();
|
||||
const baseUrl = adminClient.baseUrl;
|
||||
|
||||
const response = await fetch(
|
||||
`${addTrailingSlash(
|
||||
baseUrl
|
||||
)}admin/realms/${realm}/admin-ui-${endpoint}/${type}/${id}?first=${
|
||||
first || 0
|
||||
}&max=${max || 10}${search ? "&search=" + search : ""}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: getAuthorizationHeaders(accessToken),
|
||||
}
|
||||
);
|
||||
|
||||
return await response.json();
|
||||
return fetchAdminUI(adminClient, `/admin-ui-${endpoint}/${type}/${id}`, {
|
||||
first: (first || 0).toString(),
|
||||
max: (max || 10).toString(),
|
||||
search: search || "",
|
||||
});
|
||||
};
|
||||
|
||||
export const getAvailableClientRoles = async (
|
||||
|
|
24
apps/admin-ui/src/context/auth/admin-ui-endpoint.ts
Normal file
24
apps/admin-ui/src/context/auth/admin-ui-endpoint.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
||||
|
||||
import { getAuthorizationHeaders } from "../../utils/getAuthorizationHeaders";
|
||||
import { joinPath } from "../../utils/joinPath";
|
||||
|
||||
export async function fetchAdminUI(
|
||||
adminClient: KeycloakAdminClient,
|
||||
endpoint: string,
|
||||
query?: Record<string, string>
|
||||
) {
|
||||
const accessToken = await adminClient.getAccessToken();
|
||||
const baseUrl = adminClient.baseUrl;
|
||||
|
||||
const response = await fetch(
|
||||
joinPath(baseUrl, "admin/realms", adminClient.realmName, endpoint) +
|
||||
(query ? "?" + new URLSearchParams(query) : ""),
|
||||
{
|
||||
method: "GET",
|
||||
headers: getAuthorizationHeaders(accessToken),
|
||||
}
|
||||
);
|
||||
|
||||
return await response.json();
|
||||
}
|
|
@ -6,6 +6,7 @@ import { cellWidth } from "@patternfly/react-table";
|
|||
|
||||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { fetchAdminUI } from "../context/auth/admin-ui-endpoint";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||
|
@ -58,9 +59,7 @@ export const GroupTable = ({ toggleView }: GroupTableProps) => {
|
|||
|
||||
groupsData = group.subGroups;
|
||||
} else {
|
||||
groupsData = await adminClient.groups.find({
|
||||
briefRepresentation: false,
|
||||
});
|
||||
groupsData = await fetchAdminUI(adminClient, "admin-ui-groups");
|
||||
}
|
||||
|
||||
if (!groupsData) {
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package org.keycloak.admin.ui.rest;
|
||||
|
||||
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
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.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
public class GroupsResource {
|
||||
@Context
|
||||
private KeycloakSession session;
|
||||
private RealmModel realm;
|
||||
private AdminPermissionEvaluator auth;
|
||||
|
||||
public GroupsResource(RealmModel realm, AdminPermissionEvaluator auth) {
|
||||
super();
|
||||
this.realm = realm;
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Consumes({"application/json"})
|
||||
@Produces({"application/json"})
|
||||
@Operation(
|
||||
summary = "List all groups with fine grained authorisation",
|
||||
description = "This endpoint returns a list of groups with fine grained authorisation"
|
||||
)
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "",
|
||||
content = {@Content(
|
||||
schema = @Schema(
|
||||
implementation = GroupRepresentation.class,
|
||||
type = SchemaType.ARRAY
|
||||
)
|
||||
)}
|
||||
)
|
||||
public final Stream<GroupRepresentation> listGroups(@QueryParam("search") @DefaultValue("") final String search, @QueryParam("first")
|
||||
@DefaultValue("0") int first, @QueryParam("max") @DefaultValue("10") int max) {
|
||||
this.auth.groups().requireList();
|
||||
final Stream<GroupModel> stream;
|
||||
if ("".equals(search)) {
|
||||
stream = this.realm.searchForGroupByNameStream(search, first, max);
|
||||
} else {
|
||||
stream = this.realm.getTopLevelGroupsStream(first, max);
|
||||
}
|
||||
return stream.map(g -> toGroupHierarchy(g, search));
|
||||
}
|
||||
|
||||
private GroupRepresentation toGroupHierarchy(GroupModel group, final String search) {
|
||||
GroupRepresentation rep = toRepresentation(group, true);
|
||||
rep.setAccess(auth.groups().getAccess(group));
|
||||
rep.setSubGroups(group.getSubGroupsStream().filter(g ->
|
||||
groupMatchesSearchOrIsPathElement(
|
||||
g, search
|
||||
)
|
||||
).map(subGroup ->
|
||||
ModelToRepresentation.toGroupHierarchy(
|
||||
subGroup, true, search
|
||||
)
|
||||
).collect(Collectors.toList()));
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
private static boolean groupMatchesSearchOrIsPathElement(GroupModel group, String search) {
|
||||
if (StringUtil.isBlank(search)) {
|
||||
return true;
|
||||
}
|
||||
if (group.getName().contains(search)) {
|
||||
return true;
|
||||
}
|
||||
return group.getSubGroupsStream().findAny().isPresent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
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;
|
||||
|
||||
public final class GroupsResourceProvider implements AdminRealmResourceProviderFactory, AdminRealmResourceProvider {
|
||||
public AdminRealmResourceProvider create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return "admin-ui-groups";
|
||||
}
|
||||
|
||||
public Object getResource(KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
|
||||
return new GroupsResource(realm, auth);
|
||||
}
|
||||
}
|
|
@ -16,4 +16,5 @@
|
|||
#
|
||||
|
||||
org.keycloak.admin.ui.rest.AvailableRoleMappingProvider
|
||||
org.keycloak.admin.ui.rest.EffectiveRoleMappingProvider
|
||||
org.keycloak.admin.ui.rest.EffectiveRoleMappingProvider
|
||||
org.keycloak.admin.ui.rest.GroupsResourceProvider
|
Loading…
Reference in a new issue