Skip to content

Commit

Permalink
feat: restricted permissions for permission management [WD-18906] (#1113
Browse files Browse the repository at this point in the history
)
  • Loading branch information
mas-who authored Feb 24, 2025
2 parents e16926b + 01995eb commit 8bc7e81
Show file tree
Hide file tree
Showing 43 changed files with 763 additions and 354 deletions.
10 changes: 8 additions & 2 deletions src/api/auth-groups.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { handleResponse, handleSettledResult } from "util/helpers";
import type { LxdApiResponse } from "types/apiResponse";
import type { LxdGroup } from "types/permissions";
import { withEntitlementsQuery } from "util/entitlements/api";

export const fetchGroups = (): Promise<LxdGroup[]> => {
export const groupEntitlements = ["can_delete", "can_edit"];

export const fetchGroups = (
isFineGrained: boolean | null,
): Promise<LxdGroup[]> => {
const entitlements = withEntitlementsQuery(isFineGrained, groupEntitlements);
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/groups?recursion=1`)
fetch(`/1.0/auth/groups?recursion=1${entitlements}`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdGroup[]>) => resolve(data.metadata))
.catch(reject);
Expand Down
20 changes: 17 additions & 3 deletions src/api/auth-identities.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { handleResponse, handleSettledResult } from "util/helpers";
import type { LxdApiResponse } from "types/apiResponse";
import type { LxdIdentity, TlsIdentityTokenDetail } from "types/permissions";
import { withEntitlementsQuery } from "util/entitlements/api";

export const fetchIdentities = (): Promise<LxdIdentity[]> => {
export const identitiesEntitlements = ["can_delete", "can_edit"];

export const fetchIdentities = (
isFineGrained: boolean | null,
): Promise<LxdIdentity[]> => {
const entitlements = withEntitlementsQuery(
isFineGrained,
identitiesEntitlements,
);
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identities?recursion=1`)
fetch(`/1.0/auth/identities?recursion=1${entitlements}`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdIdentity[]>) => resolve(data.metadata))
.catch(reject);
Expand All @@ -23,9 +32,14 @@ export const fetchCurrentIdentity = (): Promise<LxdIdentity> => {
export const fetchIdentity = (
id: string,
authMethod: string,
isFineGrained: boolean | null,
): Promise<LxdIdentity> => {
const entitlements = withEntitlementsQuery(
isFineGrained,
identitiesEntitlements,
);
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identities/${authMethod}/${id}`)
fetch(`/1.0/auth/identities/${authMethod}/${id}?recursion=1${entitlements}`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdIdentity>) => resolve(data.metadata))
.catch(reject);
Expand Down
21 changes: 12 additions & 9 deletions src/api/auth-idp-groups.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { handleResponse, handleSettledResult } from "util/helpers";
import type { LxdApiResponse } from "types/apiResponse";
import type { IdpGroup } from "types/permissions";
import { withEntitlementsQuery } from "util/entitlements/api";

export const fetchIdpGroups = (): Promise<IdpGroup[]> => {
const idpGroupEntitlements = ["can_delete", "can_edit"];

export const fetchIdpGroups = (
isFineGrained: boolean | null,
): Promise<IdpGroup[]> => {
const entitlements = withEntitlementsQuery(
isFineGrained,
idpGroupEntitlements,
);
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identity-provider-groups?recursion=1`)
fetch(`/1.0/auth/identity-provider-groups?recursion=1${entitlements}`)
.then(handleResponse)
.then((data: LxdApiResponse<IdpGroup[]>) => resolve(data.metadata))
.catch(reject);
Expand All @@ -15,14 +24,8 @@ export const createIdpGroup = (group: Partial<IdpGroup>): Promise<void> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/auth/identity-provider-groups`, {
method: "POST",
body: JSON.stringify({ name: group.name }),
body: JSON.stringify({ name: group.name, groups: group.groups }),
})
.then(() =>
fetch(`/1.0/auth/identity-provider-groups/${group.name}`, {
method: "PUT",
body: JSON.stringify(group),
}),
)
.then(handleResponse)
.then(resolve)
.catch(reject);
Expand Down
16 changes: 9 additions & 7 deletions src/components/SelectableMainTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ interface SelectableMainTableProps {
parentName: string;
selectedNames: string[];
setSelectedNames: (val: string[], isUnselectAll?: boolean) => void;
processingNames: string[];
disabledNames: string[];
rows: MainTableRow[];
indeterminateNames?: string[];
disableSelect?: boolean;
onToggleRow?: (rowName: string) => void;
hideContextualMenu?: boolean;
defaultSortKey?: string;
disableSelectAll?: boolean;
}

type Props = SelectableMainTableProps & MainTableProps;
Expand All @@ -36,14 +37,15 @@ const SelectableMainTable: FC<Props> = ({
parentName,
selectedNames,
setSelectedNames,
processingNames,
disabledNames,
rows,
headers,
indeterminateNames = [],
disableSelect = false,
onToggleRow,
hideContextualMenu,
defaultSortKey,
disableSelectAll,
...props
}: Props) => {
const [currentSelectedIndex, setCurrentSelectedIndex] = useState<number>();
Expand Down Expand Up @@ -91,7 +93,7 @@ const SelectableMainTable: FC<Props> = ({
checked={isAllSelected}
indeterminate={isSomeSelected && !isAllSelected}
onChange={isSomeSelected ? selectNone : selectPage}
disabled={disableSelect}
disabled={disableSelect || disableSelectAll}
/>
{!hideContextualMenu && (
<ContextualMenu
Expand Down Expand Up @@ -128,11 +130,11 @@ const SelectableMainTable: FC<Props> = ({
];

const selectedNamesLookup = new Set(selectedNames);
const processingNamesLookup = new Set(processingNames);
const disabledNamesLookup = new Set(disabledNames);
const indeterminateNamesLookup = new Set(indeterminateNames);
const rowsWithCheckbox = rows.map((row, rowIndex) => {
const isRowSelected = selectedNamesLookup.has(row.name ?? "");
const isRowProcessing = processingNamesLookup.has(row.name ?? "");
const isRowDisabled = disabledNamesLookup.has(row.name ?? "");
const isRowIndeterminate = indeterminateNamesLookup.has(row.name ?? "");

const toggleRow = (event: PointerEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -183,7 +185,7 @@ const SelectableMainTable: FC<Props> = ({
labelClassName="u-no-margin--bottom"
checked={isRowSelected}
onChange={toggleRow}
disabled={isRowProcessing || !row.name || disableSelect}
disabled={isRowDisabled || !row.name || disableSelect}
indeterminate={isRowIndeterminate && !isRowSelected}
/>
),
Expand All @@ -195,7 +197,7 @@ const SelectableMainTable: FC<Props> = ({

const className = classnames(row.className, {
"selected-row": isRowSelected,
"processing-row": isRowProcessing,
"disabled-row": isRowDisabled,
});

const key = row.key ?? row.name;
Expand Down
15 changes: 15 additions & 0 deletions src/context/useGroups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import { UseQueryResult } from "@tanstack/react-query";
import { useAuth } from "./auth";
import { LxdGroup } from "types/permissions";
import { fetchGroups } from "api/auth-groups";

export const useGroups = (): UseQueryResult<LxdGroup[]> => {
const { isFineGrained } = useAuth();
return useQuery({
queryKey: [queryKeys.authGroups],
queryFn: () => fetchGroups(isFineGrained),
enabled: isFineGrained !== null,
});
};
28 changes: 28 additions & 0 deletions src/context/useIdentities.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import { UseQueryResult } from "@tanstack/react-query";
import { useAuth } from "./auth";
import { LxdIdentity } from "types/permissions";
import { fetchIdentities, fetchIdentity } from "api/auth-identities";

export const useIdentities = (): UseQueryResult<LxdIdentity[]> => {
const { isFineGrained } = useAuth();
return useQuery({
queryKey: [queryKeys.identities],
queryFn: () => fetchIdentities(isFineGrained),
enabled: isFineGrained !== null,
});
};

export const useIdentity = (
id: string,
authMethod: string,
enabled?: boolean,
): UseQueryResult<LxdIdentity> => {
const { isFineGrained } = useAuth();
return useQuery({
queryKey: [queryKeys.identities, authMethod, id],
queryFn: () => fetchIdentity(id, authMethod, isFineGrained),
enabled: (enabled ?? true) && isFineGrained !== null,
});
};
15 changes: 15 additions & 0 deletions src/context/useIdpGroups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import { UseQueryResult } from "@tanstack/react-query";
import { useAuth } from "./auth";
import { IdpGroup } from "types/permissions";
import { fetchIdpGroups } from "api/auth-idp-groups";

export const useIdpGroups = (): UseQueryResult<IdpGroup[]> => {
const { isFineGrained } = useAuth();
return useQuery({
queryKey: [queryKeys.idpGroups],
queryFn: () => fetchIdpGroups(isFineGrained),
enabled: isFineGrained !== null,
});
};
11 changes: 3 additions & 8 deletions src/context/useLoggedInUser.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { useQuery } from "@tanstack/react-query";
import { useSettings } from "./useSettings";
import { fetchIdentity } from "api/auth-identities";
import { queryKeys } from "util/queryKeys";
import { useIdentity } from "./useIdentities";

export const useLoggedInUser = () => {
const { data: settings } = useSettings();

const id = settings?.auth_user_name || "";
const authMethod = settings?.auth_user_method || "";

const { data: identity } = useQuery({
queryKey: [queryKeys.identities, id],
queryFn: () => fetchIdentity(id, authMethod),
enabled: !!id && !!authMethod,
});
const identityQueryEnabled = !!id && !!authMethod;
const { data: identity } = useIdentity(id, authMethod, identityQueryEnabled);

return {
loggedInUserName: identity?.name,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/cluster/ClusterGroupForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ const ClusterGroupForm: FC<Props> = ({ group }) => {
setSelectedNames={(newMembers: string[]) =>
void formik.setFieldValue("members", newMembers)
}
processingNames={[]}
disabledNames={[]}
/>
</Col>
</Row>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/images/ImageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ const ImageList: FC = () => {
itemName="image"
parentName="project"
filteredNames={filteredImages.map((item) => item.fingerprint)}
processingNames={processingNames}
disabledNames={processingNames}
rows={[]}
disableSelect={!deletableImages.length}
/>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/instances/InstanceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ const InstanceList: FC = () => {
parentName="project"
selectedNames={selectedNames}
setSelectedNames={setSelectedNames}
processingNames={processingNames}
disabledNames={processingNames}
filteredNames={filteredInstances.map(
(instance) => instance.name,
)}
Expand All @@ -716,7 +716,7 @@ const InstanceList: FC = () => {
parentName="project"
selectedNames={selectedNames}
setSelectedNames={setSelectedNames}
processingNames={processingNames}
disabledNames={processingNames}
filteredNames={filteredInstances.map(
(instance) => instance.name,
)}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/instances/InstanceSnapshots.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ const InstanceSnapshots = (props: Props) => {
parentName="instance"
selectedNames={selectedNames}
setSelectedNames={setSelectedNames}
processingNames={processingNames}
disabledNames={processingNames}
filteredNames={filteredSnapshots.map(
(snapshot) => snapshot.name,
)}
Expand Down
8 changes: 8 additions & 0 deletions src/pages/permissions/CreateTlsIdentityBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { useSmallScreen } from "context/useSmallScreen";
import { FC } from "react";
import { usePortal } from "@canonical/react-components";
import CreateIdentityModal from "./CreateIdentityModal";
import { useServerEntitlements } from "util/entitlements/server";

const CreateTlsIdentityBtn: FC = () => {
const isSmallScreen = useSmallScreen();
const { openPortal, closePortal, isOpen, Portal } = usePortal();
const { canCreateIdentities } = useServerEntitlements();

return (
<>
Expand All @@ -20,6 +22,12 @@ const CreateTlsIdentityBtn: FC = () => {
className="u-float-right u-no-margin--bottom"
onClick={openPortal}
hasIcon={!isSmallScreen}
title={
canCreateIdentities()
? ""
: "You do not have permission to create identities"
}
disabled={!canCreateIdentities()}
>
{!isSmallScreen && <Icon name="plus" light />}
<span>Create TLS Identity</span>
Expand Down
Loading

0 comments on commit 8bc7e81

Please sign in to comment.