Fine grained clients (#2702)

This commit is contained in:
Stan Silvert 2022-05-30 05:23:24 -04:00 committed by GitHub
parent be000ff08c
commit 5b559bcdbd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 153 additions and 75 deletions

View file

@ -56,6 +56,7 @@ export const AdvancedTab = ({
protocol, protocol,
authenticationFlowBindingOverrides, authenticationFlowBindingOverrides,
adminUrl, adminUrl,
access,
}, },
}: AdvancedProps) => { }: AdvancedProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
@ -197,7 +198,11 @@ export const AdvancedTab = ({
tab tab
</Trans> </Trans>
</Text> </Text>
<FormAccess role="manage-clients" isHorizontal> <FormAccess
role="manage-clients"
fineGrainedAccess={access?.configure}
isHorizontal
>
<FormGroup <FormGroup
label={t("notBefore")} label={t("notBefore")}
fieldId="kc-not-before" fieldId="kc-not-before"
@ -258,7 +263,11 @@ export const AdvancedTab = ({
)} )}
{publicClient && ( {publicClient && (
<> <>
<FormAccess role="manage-clients" isHorizontal> <FormAccess
role="manage-clients"
fineGrainedAccess={access?.configure}
isHorizontal
>
<FormGroup <FormGroup
label={t("nodeReRegistrationTimeout")} label={t("nodeReRegistrationTimeout")}
fieldId="kc-node-reregistration-timeout" fieldId="kc-node-reregistration-timeout"
@ -393,6 +402,7 @@ export const AdvancedTab = ({
setValue(`attributes.${key}`, value) setValue(`attributes.${key}`, value)
) )
} }
hasConfigureAccess={access?.configure}
/> />
</> </>
)} )}
@ -424,6 +434,7 @@ export const AdvancedTab = ({
reset={() => reset={() =>
resetFields(["exclude.session.state.from.auth.response"]) resetFields(["exclude.session.state.from.auth.response"])
} }
hasConfigureAccess={access?.configure}
/> />
</> </>
)} )}
@ -434,6 +445,7 @@ export const AdvancedTab = ({
<AdvancedSettings <AdvancedSettings
protocol={protocol} protocol={protocol}
control={control} control={control}
hasConfigureAccess={access?.configure}
save={() => save()} save={() => save()}
reset={() => { reset={() => {
resetFields([ resetFields([
@ -463,6 +475,7 @@ export const AdvancedTab = ({
authenticationFlowBindingOverrides?.direct_grant authenticationFlowBindingOverrides?.direct_grant
); );
}} }}
hasConfigureAccess={access?.configure}
/> />
</> </>
</ScrollForm> </ScrollForm>

View file

@ -11,9 +11,13 @@ import { KeycloakTextArea } from "../components/keycloak-text-area/KeycloakTextA
type ClientDescriptionProps = { type ClientDescriptionProps = {
protocol?: string; protocol?: string;
hasConfigureAccess?: boolean;
}; };
export const ClientDescription = ({ protocol }: ClientDescriptionProps) => { export const ClientDescription = ({
protocol,
hasConfigureAccess: configure,
}: ClientDescriptionProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const { const {
register, register,
@ -21,7 +25,7 @@ export const ClientDescription = ({ protocol }: ClientDescriptionProps) => {
formState: { errors }, formState: { errors },
} = useFormContext<ClientRepresentation>(); } = useFormContext<ClientRepresentation>();
return ( return (
<FormAccess role="manage-clients" unWrap> <FormAccess role="manage-clients" fineGrainedAccess={configure} unWrap>
<FormGroup <FormGroup
labelIcon={ labelIcon={
<HelpItem helpText="clients-help:clientId" fieldLabelId="clientId" /> <HelpItem helpText="clients-help:clientId" fieldLabelId="clientId" />

View file

@ -128,7 +128,7 @@ const ClientDetailHeader = ({
}, [client, t]); }, [client, t]);
const { hasAccess } = useAccess(); const { hasAccess } = useAccess();
const isManager = hasAccess("manage-clients"); const isManager = hasAccess("manage-clients") || client.access?.configure;
const dropdownItems = [ const dropdownItems = [
<DropdownItem key="download" onClick={toggleDownloadDialog}> <DropdownItem key="download" onClick={toggleDownloadDialog}>
@ -189,12 +189,11 @@ export default function ClientDetails() {
const { profileInfo } = useServerInfo(); const { profileInfo } = useServerInfo();
const { hasAccess } = useAccess(); const { hasAccess } = useAccess();
const isManager = hasAccess("manage-clients"); const permissionsEnabled =
const canViewPermissions = hasAccess( !profileInfo?.disabledFeatures?.includes("ADMIN_FINE_GRAINED_AUTHZ") &&
"manage-authorization", hasAccess("manage-authorization");
"manage-clients" const hasManageClients = hasAccess("manage-clients");
); const hasViewUsers = hasAccess("view-users");
const canViewServiceAccountRoles = hasAccess("view-users");
const history = useHistory(); const history = useHistory();
@ -432,27 +431,33 @@ export default function ClientDetails() {
{...route("keys")} {...route("keys")}
> >
{client.protocol === "openid-connect" && ( {client.protocol === "openid-connect" && (
<Keys clientId={clientId} save={save} /> <Keys
clientId={clientId}
save={save}
hasConfigureAccess={client.access?.configure}
/>
)} )}
{client.protocol === "saml" && ( {client.protocol === "saml" && (
<SamlKeys clientId={clientId} save={save} /> <SamlKeys clientId={clientId} save={save} />
)} )}
</Tab> </Tab>
)} )}
{!client.publicClient && !isRealmClient(client) && ( {!client.publicClient &&
<Tab !isRealmClient(client) &&
id="credentials" (hasManageClients || client.access?.configure) && (
title={<TabTitleText>{t("credentials")}</TabTitleText>} <Tab
{...route("credentials")} id="credentials"
> title={<TabTitleText>{t("credentials")}</TabTitleText>}
<Credentials {...route("credentials")}
key={key} >
client={client} <Credentials
save={save} key={key}
refresh={() => setKey(key + 1)} client={client}
/> save={save}
</Tab> refresh={() => setKey(key + 1)}
)} />
</Tab>
)}
<Tab <Tab
id="roles" id="roles"
data-testid="rolesTab" data-testid="rolesTab"
@ -463,7 +468,7 @@ export default function ClientDetails() {
loader={loader} loader={loader}
paginated={false} paginated={false}
messageBundle="clients" messageBundle="clients"
isReadOnly={!isManager} isReadOnly={!(hasManageClients || client.access?.configure)}
/> />
</Tab> </Tab>
{!isRealmClient(client) && !client.bearerOnly && ( {!isRealmClient(client) && !client.bearerOnly && (
@ -496,6 +501,7 @@ export default function ClientDetails() {
clientName={client.clientId!} clientName={client.clientId!}
clientId={clientId} clientId={clientId}
protocol={client!.protocol!} protocol={client!.protocol!}
fineGrainedAccess={client!.access?.manage}
/> />
</Tab> </Tab>
<Tab <Tab
@ -595,7 +601,7 @@ export default function ClientDetails() {
</RoutableTabs> </RoutableTabs>
</Tab> </Tab>
)} )}
{client!.serviceAccountsEnabled && canViewServiceAccountRoles && ( {client!.serviceAccountsEnabled && hasViewUsers && (
<Tab <Tab
id="serviceAccount" id="serviceAccount"
data-testid="serviceAccountTab" data-testid="serviceAccountTab"
@ -605,19 +611,16 @@ export default function ClientDetails() {
<ServiceAccount client={client} /> <ServiceAccount client={client} />
</Tab> </Tab>
)} )}
{!profileInfo?.disabledFeatures?.includes( {permissionsEnabled && (hasManageClients || client.access?.manage) && (
"ADMIN_FINE_GRAINED_AUTHZ" <Tab
) && id="permissions"
canViewPermissions && ( data-testid="permissionsTab"
<Tab title={<TabTitleText>{t("common:permissions")}</TabTitleText>}
id="permissions" {...route("permissions")}
data-testid="permissionsTab" >
title={<TabTitleText>{t("common:permissions")}</TabTitleText>} <PermissionsTab id={client.id!} type="clients" />
{...route("permissions")} </Tab>
> )}
<PermissionsTab id={client.id!} type="clients" />
</Tab>
)}
<Tab <Tab
id="advanced" id="advanced"
data-testid="advancedTab" data-testid="advancedTab"

View file

@ -49,7 +49,7 @@ export const ClientSettings = ({
const { realm } = useRealm(); const { realm } = useRealm();
const { hasAccess } = useAccess(); const { hasAccess } = useAccess();
const isManager = hasAccess("manage-clients"); const isManager = hasAccess("manage-clients") || client.access?.configure;
const [loginThemeOpen, setLoginThemeOpen] = useState(false); const [loginThemeOpen, setLoginThemeOpen] = useState(false);
const loginThemes = useServerInfo().themes!["login"]; const loginThemes = useServerInfo().themes!["login"];
@ -88,7 +88,10 @@ export const ClientSettings = ({
sections={sections.map((section) => t(section))} sections={sections.map((section) => t(section))}
> >
<Form isHorizontal> <Form isHorizontal>
<ClientDescription protocol={client.protocol} /> <ClientDescription
protocol={client.protocol}
hasConfigureAccess={client.access?.configure}
/>
</Form> </Form>
{protocol === "saml" ? ( {protocol === "saml" ? (
<SamlConfig /> <SamlConfig />
@ -96,7 +99,11 @@ export const ClientSettings = ({
!client.bearerOnly && <CapabilityConfig /> !client.bearerOnly && <CapabilityConfig />
)} )}
{protocol === "saml" && <SamlSignature />} {protocol === "saml" && <SamlSignature />}
<FormAccess isHorizontal role="manage-clients"> <FormAccess
isHorizontal
role="manage-clients"
fineGrainedAccess={client.access?.configure}
>
{!client.bearerOnly && ( {!client.bearerOnly && (
<> <>
<FormGroup <FormGroup
@ -259,7 +266,11 @@ export const ClientSettings = ({
/> />
)} )}
</FormAccess> </FormAccess>
<FormAccess isHorizontal role="manage-clients"> <FormAccess
isHorizontal
role="manage-clients"
fineGrainedAccess={client.access?.configure}
>
<FormGroup <FormGroup
label={t("loginTheme")} label={t("loginTheme")}
labelIcon={ labelIcon={
@ -383,7 +394,11 @@ export const ClientSettings = ({
/> />
)} )}
</FormAccess> </FormAccess>
<FormAccess isHorizontal role="manage-clients"> <FormAccess
isHorizontal
role="manage-clients"
fineGrainedAccess={client.access?.configure}
>
{protocol === "openid-connect" && ( {protocol === "openid-connect" && (
<> <>
<FormGroup <FormGroup

View file

@ -161,7 +161,10 @@ export default function ClientsSection() {
}, },
]; ];
if (!isRealmClient(client) && isManager) { if (
!isRealmClient(client) &&
(isManager || client.access?.configure)
) {
actions.push({ actions.push({
title: t("common:delete"), title: t("common:delete"),
onClick() { onClick() {

View file

@ -68,7 +68,7 @@ export const GeneralSettings = () => {
)} )}
/> />
</FormGroup> </FormGroup>
<ClientDescription /> <ClientDescription hasConfigureAccess />
</FormAccess> </FormAccess>
); );
}; };

View file

@ -23,6 +23,7 @@ type AdvancedSettingsProps = {
save: () => void; save: () => void;
reset: () => void; reset: () => void;
protocol?: string; protocol?: string;
hasConfigureAccess?: boolean;
}; };
export const AdvancedSettings = ({ export const AdvancedSettings = ({
@ -30,11 +31,16 @@ export const AdvancedSettings = ({
save, save,
reset, reset,
protocol, protocol,
hasConfigureAccess,
}: AdvancedSettingsProps) => { }: AdvancedSettingsProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
return ( return (
<FormAccess role="manage-realm" isHorizontal> <FormAccess
role="manage-realm"
fineGrainedAccess={hasConfigureAccess}
isHorizontal
>
{protocol !== "openid-connect" && ( {protocol !== "openid-connect" && (
<FormGroup <FormGroup
label={t("assertionLifespan")} label={t("assertionLifespan")}

View file

@ -20,6 +20,7 @@ type AuthenticationOverridesProps = {
save: () => void; save: () => void;
reset: () => void; reset: () => void;
protocol?: string; protocol?: string;
hasConfigureAccess?: boolean;
}; };
export const AuthenticationOverrides = ({ export const AuthenticationOverrides = ({
@ -27,6 +28,7 @@ export const AuthenticationOverrides = ({
control, control,
save, save,
reset, reset,
hasConfigureAccess,
}: AuthenticationOverridesProps) => { }: AuthenticationOverridesProps) => {
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
@ -56,7 +58,11 @@ export const AuthenticationOverrides = ({
); );
return ( return (
<FormAccess role="manage-clients" isHorizontal> <FormAccess
role="manage-clients"
fineGrainedAccess={hasConfigureAccess}
isHorizontal
>
<FormGroup <FormGroup
label={t("browserFlow")} label={t("browserFlow")}
fieldId="browserFlow" fieldId="browserFlow"

View file

@ -20,11 +20,13 @@ import { KeycloakTextInput } from "../../components/keycloak-text-input/Keycloak
type FineGrainOpenIdConnectProps = { type FineGrainOpenIdConnectProps = {
save: () => void; save: () => void;
reset: () => void; reset: () => void;
hasConfigureAccess?: boolean;
}; };
export const FineGrainOpenIdConnect = ({ export const FineGrainOpenIdConnect = ({
save, save,
reset, reset,
hasConfigureAccess,
}: FineGrainOpenIdConnectProps) => { }: FineGrainOpenIdConnectProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const providers = useServerInfo().providers; const providers = useServerInfo().providers;
@ -141,7 +143,11 @@ export const FineGrainOpenIdConnect = ({
)); ));
return ( return (
<FormAccess role="manage-clients" isHorizontal> <FormAccess
role="manage-clients"
fineGrainedAccess={hasConfigureAccess}
isHorizontal
>
<FormGroup <FormGroup
label={t("logoUrl")} label={t("logoUrl")}
fieldId="logoUrl" fieldId="logoUrl"

View file

@ -10,16 +10,22 @@ type OpenIdConnectCompatibilityModesProps = {
control: Control<Record<string, any>>; control: Control<Record<string, any>>;
save: () => void; save: () => void;
reset: () => void; reset: () => void;
hasConfigureAccess?: boolean;
}; };
export const OpenIdConnectCompatibilityModes = ({ export const OpenIdConnectCompatibilityModes = ({
control, control,
save, save,
reset, reset,
hasConfigureAccess,
}: OpenIdConnectCompatibilityModesProps) => { }: OpenIdConnectCompatibilityModesProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
return ( return (
<FormAccess role="manage-realm" isHorizontal> <FormAccess
role="manage-realm"
fineGrainedAccess={hasConfigureAccess}
isHorizontal
>
<FormGroup <FormGroup
label={t("excludeSessionStateFromAuthenticationResponse")} label={t("excludeSessionStateFromAuthenticationResponse")}
fieldId="excludeSessionStateFromAuthenticationResponse" fieldId="excludeSessionStateFromAuthenticationResponse"

View file

@ -347,7 +347,7 @@ export const AuthorizationEvaluate = ({ client }: Props) => {
> >
<FormAccess <FormAccess
isHorizontal isHorizontal
role="manage-clients" role="view-clients"
onSubmit={form.handleSubmit(evaluate)} onSubmit={form.handleSubmit(evaluate)}
> >
<FormGroup <FormGroup
@ -487,7 +487,7 @@ export const AuthorizationEvaluate = ({ client }: Props) => {
</FormAccess> </FormAccess>
</FormPanel> </FormPanel>
<FormPanel className="kc-permissions" title={t("common:permissions")}> <FormPanel className="kc-permissions" title={t("common:permissions")}>
<FormAccess isHorizontal role="manage-clients"> <FormAccess isHorizontal role="view-clients">
<FormGroup <FormGroup
label={t("applyToResourceType")} label={t("applyToResourceType")}
fieldId="applyToResourceType" fieldId="applyToResourceType"

View file

@ -65,7 +65,7 @@ export const AuthorizationExport = () => {
return ( return (
<PageSection> <PageSection>
<FormAccess isHorizontal role="manage-realm" className="pf-u-mt-lg"> <FormAccess isHorizontal role="view-realm" className="pf-u-mt-lg">
<FormGroup <FormGroup
label={t("authDetails")} label={t("authDetails")}
labelIcon={ labelIcon={

View file

@ -194,7 +194,7 @@ export default function PermissionDetails() {
<PageSection variant="light"> <PageSection variant="light">
<FormAccess <FormAccess
isHorizontal isHorizontal
role="manage-clients" role="view-clients"
onSubmit={handleSubmit(save)} onSubmit={handleSubmit(save)}
> >
<FormProvider {...form}> <FormProvider {...form}>

View file

@ -179,7 +179,7 @@ export default function ResourceDetails() {
<FormProvider {...form}> <FormProvider {...form}>
<FormAccess <FormAccess
isHorizontal isHorizontal
role="manage-clients" role="view-clients"
className="keycloak__resource-details__form" className="keycloak__resource-details__form"
onSubmit={handleSubmit(save)} onSubmit={handleSubmit(save)}
> >

View file

@ -128,7 +128,7 @@ export default function ScopeDetails() {
<PageSection variant="light"> <PageSection variant="light">
<FormAccess <FormAccess
isHorizontal isHorizontal
role="manage-clients" role="view-clients"
onSubmit={handleSubmit(save)} onSubmit={handleSubmit(save)}
> >
<FormGroup <FormGroup

View file

@ -192,7 +192,7 @@ export default function PolicyDetails() {
<FormAccess <FormAccess
isHorizontal isHorizontal
onSubmit={handleSubmit(save)} onSubmit={handleSubmit(save)}
role="manage-clients" role="view-clients"
> >
<FormProvider {...form}> <FormProvider {...form}>
<NameDescription prefix="policy" /> <NameDescription prefix="policy" />

View file

@ -75,7 +75,7 @@ export default function ImportForm() {
> >
<FormProvider {...form}> <FormProvider {...form}>
<JsonFileUpload id="realm-file" onChange={handleFileChange} /> <JsonFileUpload id="realm-file" onChange={handleFileChange} />
<ClientDescription /> <ClientDescription hasConfigureAccess />
<FormGroup label={t("common:type")} fieldId="kc-type"> <FormGroup label={t("common:type")} fieldId="kc-type">
<KeycloakTextInput <KeycloakTextInput
type="text" type="text"

View file

@ -33,11 +33,12 @@ import { Certificate } from "./Certificate";
type KeysProps = { type KeysProps = {
save: () => void; save: () => void;
clientId: string; clientId: string;
hasConfigureAccess?: boolean;
}; };
const attr = "jwt.credential"; const attr = "jwt.credential";
export const Keys = ({ clientId, save }: KeysProps) => { export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const { const {
control, control,
@ -125,7 +126,11 @@ export const Keys = ({ clientId, save }: KeysProps) => {
</TextContent> </TextContent>
</CardBody> </CardBody>
<CardBody> <CardBody>
<FormAccess role="manage-clients" isHorizontal> <FormAccess
role="manage-clients"
fineGrainedAccess={hasConfigureAccess}
isHorizontal
>
<FormGroup <FormGroup
hasNoPaddingTop hasNoPaddingTop
label={t("useJwksUrl")} label={t("useJwksUrl")}

View file

@ -15,7 +15,7 @@ export const DedicatedScopeDetailsRoute: RouteDef = {
path: "/:realm/clients/:clientId/clientScopes/dedicated/:tab?", path: "/:realm/clients/:clientId/clientScopes/dedicated/:tab?",
component: lazy(() => import("../scopes/DedicatedScopes")), component: lazy(() => import("../scopes/DedicatedScopes")),
breadcrumb: (t) => t("clients:dedicatedScopes"), breadcrumb: (t) => t("clients:dedicatedScopes"),
access: "manage-clients", access: "view-clients",
}; };
export const toDedicatedScope = ( export const toDedicatedScope = (

View file

@ -16,7 +16,7 @@ export const NewPermissionRoute: RouteDef = {
path: "/:realm/clients/:id/authorization/permission/new/:permissionType/:selectedId?", path: "/:realm/clients/:id/authorization/permission/new/:permissionType/:selectedId?",
component: lazy(() => import("../authorization/PermissionDetails")), component: lazy(() => import("../authorization/PermissionDetails")),
breadcrumb: (t) => t("clients:createPermission"), breadcrumb: (t) => t("clients:createPermission"),
access: "manage-clients", access: "view-clients",
}; };
export const toNewPermission = ( export const toNewPermission = (

View file

@ -9,7 +9,7 @@ export const NewPolicyRoute: RouteDef = {
path: "/:realm/clients/:id/authorization/policy/new/:policyType", path: "/:realm/clients/:id/authorization/policy/new/:policyType",
component: lazy(() => import("../authorization/policy/PolicyDetails")), component: lazy(() => import("../authorization/policy/PolicyDetails")),
breadcrumb: (t) => t("clients:createPolicy"), breadcrumb: (t) => t("clients:createPolicy"),
access: "manage-clients", access: "view-clients",
}; };
export const toCreatePolicy = ( export const toCreatePolicy = (

View file

@ -9,7 +9,7 @@ export const NewResourceRoute: RouteDef = {
path: "/:realm/clients/:id/authorization/resource/new", path: "/:realm/clients/:id/authorization/resource/new",
component: lazy(() => import("../authorization/ResourceDetails")), component: lazy(() => import("../authorization/ResourceDetails")),
breadcrumb: (t) => t("clients:createResource"), breadcrumb: (t) => t("clients:createResource"),
access: "manage-clients", access: "view-clients",
}; };
export const toCreateResource = ( export const toCreateResource = (

View file

@ -9,7 +9,7 @@ export const NewScopeRoute: RouteDef = {
path: "/:realm/clients/:id/authorization/scope/new", path: "/:realm/clients/:id/authorization/scope/new",
component: lazy(() => import("../authorization/ScopeDetails")), component: lazy(() => import("../authorization/ScopeDetails")),
breadcrumb: (t) => t("clients:createAuthorizationScope"), breadcrumb: (t) => t("clients:createAuthorizationScope"),
access: "manage-clients", access: "view-clients",
}; };
export const toNewScope = ( export const toNewScope = (

View file

@ -15,7 +15,7 @@ export const PermissionDetailsRoute: RouteDef = {
path: "/:realm/clients/:id/authorization/permission/:permissionType/:permissionId", path: "/:realm/clients/:id/authorization/permission/:permissionType/:permissionId",
component: lazy(() => import("../authorization/PermissionDetails")), component: lazy(() => import("../authorization/PermissionDetails")),
breadcrumb: (t) => t("clients:permissionDetails"), breadcrumb: (t) => t("clients:permissionDetails"),
access: "manage-clients", access: "view-clients",
}; };
export const toPermissionDetails = ( export const toPermissionDetails = (

View file

@ -14,7 +14,7 @@ export const PolicyDetailsRoute: RouteDef = {
path: "/:realm/clients/:id/authorization/policy/:policyId/:policyType", path: "/:realm/clients/:id/authorization/policy/:policyId/:policyType",
component: lazy(() => import("../authorization/policy/PolicyDetails")), component: lazy(() => import("../authorization/policy/PolicyDetails")),
breadcrumb: (t) => t("clients:createPolicy"), breadcrumb: (t) => t("clients:createPolicy"),
access: "manage-clients", access: "view-clients",
}; };
export const toPolicyDetails = ( export const toPolicyDetails = (

View file

@ -13,7 +13,7 @@ export const ResourceDetailsRoute: RouteDef = {
path: "/:realm/clients/:id/authorization/resource/:resourceId?", path: "/:realm/clients/:id/authorization/resource/:resourceId?",
component: lazy(() => import("../authorization/ResourceDetails")), component: lazy(() => import("../authorization/ResourceDetails")),
breadcrumb: (t) => t("clients:createResource"), breadcrumb: (t) => t("clients:createResource"),
access: "manage-clients", access: "view-clients",
}; };
export const toResourceDetails = ( export const toResourceDetails = (

View file

@ -46,6 +46,7 @@ export type ClientScopesProps = {
clientId: string; clientId: string;
protocol: string; protocol: string;
clientName: string; clientName: string;
fineGrainedAccess?: boolean;
}; };
export type Row = ClientScopeRepresentation & { export type Row = ClientScopeRepresentation & {
@ -59,6 +60,7 @@ export const ClientScopes = ({
clientId, clientId,
protocol, protocol,
clientName, clientName,
fineGrainedAccess,
}: ClientScopesProps) => { }: ClientScopesProps) => {
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
@ -83,7 +85,7 @@ export const ClientScopes = ({
const isDedicatedRow = (value: Row) => value.id === DEDICATED_ROW; const isDedicatedRow = (value: Row) => value.id === DEDICATED_ROW;
const { hasAccess } = useAccess(); const { hasAccess } = useAccess();
const isManager = hasAccess("manage-clients"); const isManager = hasAccess("manage-clients") || fineGrainedAccess;
const loader = async (first?: number, max?: number, search?: string) => { const loader = async (first?: number, max?: number, search?: string) => {
const defaultClientScopes = const defaultClientScopes =
@ -130,7 +132,7 @@ export const ClientScopes = ({
firstNum, firstNum,
firstNum + Number(max) firstNum + Number(max)
); );
if (firstNum === 0) { if (firstNum === 0 && isManager) {
return [ return [
{ {
id: DEDICATED_ROW, id: DEDICATED_ROW,

View file

@ -20,6 +20,7 @@ import {
} from "../../components/role-mapping/RoleMapping"; } from "../../components/role-mapping/RoleMapping";
import type { RoleMappingPayload } from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; import type { RoleMappingPayload } from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import useToggle from "../../utils/useToggle"; import useToggle from "../../utils/useToggle";
import { useAccess } from "../../context/access/Access";
type DedicatedScopeProps = { type DedicatedScopeProps = {
client: ClientRepresentation; client: ClientRepresentation;
@ -35,6 +36,9 @@ export const DedicatedScope = ({
const [client, setClient] = useState<ClientRepresentation>(initialClient); const [client, setClient] = useState<ClientRepresentation>(initialClient);
const [hide, toggle] = useToggle(); const [hide, toggle] = useToggle();
const { hasAccess } = useAccess();
const isManager = hasAccess("manage-clients") || client.access?.manage;
const loader = async () => { const loader = async () => {
const [clients, assignedRoles, effectiveRoles] = await Promise.all([ const [clients, assignedRoles, effectiveRoles] = await Promise.all([
adminClient.clients.find(), adminClient.clients.find(),
@ -118,7 +122,11 @@ export const DedicatedScope = ({
return ( return (
<PageSection> <PageSection>
<FormAccess role="manage-clients" isHorizontal> <FormAccess
role="manage-clients"
fineGrainedAccess={client.access?.manage}
isHorizontal
>
<FormGroup <FormGroup
hasNoPaddingTop hasNoPaddingTop
label={t("fullScopeAllowed")} label={t("fullScopeAllowed")}
@ -149,6 +157,7 @@ export const DedicatedScope = ({
loader={loader} loader={loader}
save={assignRoles} save={assignRoles}
onHideRolesToggle={toggle} onHideRolesToggle={toggle}
isManager={isManager}
/> />
</> </>
)} )}

View file

@ -35,7 +35,7 @@ export const ServiceAccount = ({ client }: ServiceAccountProps) => {
const [serviceAccount, setServiceAccount] = useState<UserRepresentation>(); const [serviceAccount, setServiceAccount] = useState<UserRepresentation>();
const { hasAccess } = useAccess(); const { hasAccess } = useAccess();
const isManager = hasAccess("manage-clients"); const hasManageClients = hasAccess("manage-clients");
useFetch( useFetch(
() => () =>
@ -128,7 +128,7 @@ export const ServiceAccount = ({ client }: ServiceAccountProps) => {
name={client.clientId!} name={client.clientId!}
id={serviceAccount.id!} id={serviceAccount.id!}
type="users" type="users"
isManager={isManager} isManager={hasManageClients || client.access?.configure}
loader={loader} loader={loader}
save={assignRoles} save={assignRoles}
onHideRolesToggle={() => setHide(!hide)} onHideRolesToggle={() => setHide(!hide)}