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:
Erik Jan de Wit 2022-09-01 02:06:29 +02:00 committed by GitHub
parent df072f5c15
commit 93b0144ff3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 161 additions and 30 deletions

View file

@ -17,7 +17,6 @@ import useLocaleSort from "../../utils/useLocaleSort";
import { ResourcesKey, Row, ServiceRole } from "./RoleMapping"; import { ResourcesKey, Row, ServiceRole } from "./RoleMapping";
import { getAvailableRoles } from "./queries"; import { getAvailableRoles } from "./queries";
import { getAvailableClientRoles } from "./resource"; import { getAvailableClientRoles } from "./resource";
import { useRealm } from "../../context/realm-context/RealmContext";
type AddRoleMappingModalProps = { type AddRoleMappingModalProps = {
id: string; id: string;
@ -42,7 +41,6 @@ export const AddRoleMappingModal = ({
}: AddRoleMappingModalProps) => { }: AddRoleMappingModalProps) => {
const { t } = useTranslation("common"); const { t } = useTranslation("common");
const { adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const { realm } = useRealm();
const [searchToggle, setSearchToggle] = useState(false); const [searchToggle, setSearchToggle] = useState(false);
@ -80,7 +78,6 @@ export const AddRoleMappingModal = ({
const roles = await getAvailableClientRoles({ const roles = await getAvailableClientRoles({
adminClient, adminClient,
id, id,
realm,
type, type,
first: first || 0, first: first || 0,
max: max || 10, max: max || 10,

View file

@ -22,7 +22,6 @@ import { useAdminClient } from "../../context/auth/AdminClient";
import { ListEmptyState } from "../list-empty-state/ListEmptyState"; import { ListEmptyState } from "../list-empty-state/ListEmptyState";
import { deleteMapping, getEffectiveRoles, getMapping } from "./queries"; import { deleteMapping, getEffectiveRoles, getMapping } from "./queries";
import { getEffectiveClientRoles } from "./resource"; import { getEffectiveClientRoles } from "./resource";
import { useRealm } from "../../context/realm-context/RealmContext";
import "./role-mapping.css"; import "./role-mapping.css";
@ -89,7 +88,6 @@ export const RoleMapping = ({
}: RoleMappingProps) => { }: RoleMappingProps) => {
const { t } = useTranslation(type); const { t } = useTranslation(type);
const { adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const { realm } = useRealm();
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
@ -113,7 +111,6 @@ export const RoleMapping = ({
effectiveClientRoles = ( effectiveClientRoles = (
await getEffectiveClientRoles({ await getEffectiveClientRoles({
adminClient, adminClient,
realm,
type, type,
id, id,
}) })

View file

@ -1,11 +1,9 @@
import KeycloakAdminClient from "@keycloak/keycloak-admin-client"; import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
import { addTrailingSlash } from "../../util"; import { fetchAdminUI } from "../../context/auth/admin-ui-endpoint";
import { getAuthorizationHeaders } from "../../utils/getAuthorizationHeaders";
type BaseClientRolesQuery = { type BaseClientRolesQuery = {
adminClient: KeycloakAdminClient; adminClient: KeycloakAdminClient;
id: string; id: string;
realm: string;
type: string; type: string;
}; };
@ -33,29 +31,17 @@ type ClientRole = {
const fetchRoles = async ({ const fetchRoles = async ({
adminClient, adminClient,
id, id,
realm,
type, type,
first, first,
max, max,
search, search,
endpoint, endpoint,
}: Query): Promise<ClientRole[]> => { }: Query): Promise<ClientRole[]> => {
const accessToken = await adminClient.getAccessToken(); return fetchAdminUI(adminClient, `/admin-ui-${endpoint}/${type}/${id}`, {
const baseUrl = adminClient.baseUrl; first: (first || 0).toString(),
max: (max || 10).toString(),
const response = await fetch( search: search || "",
`${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();
}; };
export const getAvailableClientRoles = async ( export const getAvailableClientRoles = async (

View 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();
}

View file

@ -6,6 +6,7 @@ import { cellWidth } from "@patternfly/react-table";
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation"; import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
import { useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient } from "../context/auth/AdminClient";
import { fetchAdminUI } from "../context/auth/admin-ui-endpoint";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
@ -58,9 +59,7 @@ export const GroupTable = ({ toggleView }: GroupTableProps) => {
groupsData = group.subGroups; groupsData = group.subGroups;
} else { } else {
groupsData = await adminClient.groups.find({ groupsData = await fetchAdminUI(adminClient, "admin-ui-groups");
briefRepresentation: false,
});
} }
if (!groupsData) { if (!groupsData) {

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -17,3 +17,4 @@
org.keycloak.admin.ui.rest.AvailableRoleMappingProvider org.keycloak.admin.ui.rest.AvailableRoleMappingProvider
org.keycloak.admin.ui.rest.EffectiveRoleMappingProvider org.keycloak.admin.ui.rest.EffectiveRoleMappingProvider
org.keycloak.admin.ui.rest.GroupsResourceProvider