UNPKG

@openshift-console/dynamic-plugin-sdk

Version:

Provides core APIs, types and utilities used by dynamic plugins at runtime.

120 lines (119 loc) 5.29 kB
import { useEffect } from 'react'; import * as _ from 'lodash'; import { ProjectModel, SelfSubjectAccessReviewModel } from '../../../models'; import { k8sCreate } from '../../../utils/k8s/k8s-resource'; import { getImpersonate } from '../../core/reducers/coreSelectors'; import storeHandler from '../../storeHandler'; import { useSafetyFirst } from '../safety-first'; /** * It provides impersonation key based on data from the redux store. */ const getImpersonateKey = (impersonate) => { const newImpersonate = impersonate || getImpersonate(storeHandler.getStore().getState()); return newImpersonate ? `${newImpersonate.kind}~${newImpersonate.name}` : ''; }; /** * Memoizes the result so it is possible to only make the request once for each access review. * This does mean that the user will have to refresh the page to see updates. * Function takes in the destructured resource attributes so that the cache keys are stable. * `JSON.stringify` is not guaranteed to give the same result for equivalent objects. * Impersonate headers are added automatically by `k8sCreate`. * @param group resource group. * @param resource resource string. * @param subresource subresource string. * @param verb K8s verb. * @param namespace namespace. * @param impersonateKey parameter to include in the cache key even though it's not used in the function body. * @returns Memoized result of the access review. */ const checkAccessInternal = _.memoize((group, resource, subresource, verb, name, namespace, // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars impersonateKey) => { // Projects are a special case. `namespace` must be set to the project name // even though it's a cluster-scoped resource. const reviewNamespace = group === ProjectModel.apiGroup && resource === ProjectModel.plural ? name : namespace; const ssar = { apiVersion: 'authorization.k8s.io/v1', kind: 'SelfSubjectAccessReview', spec: { resourceAttributes: { group, resource, subresource, verb, name, namespace: reviewNamespace, }, }, }; return k8sCreate(SelfSubjectAccessReviewModel, ssar); }, (...args) => args.join('~')); /** * Provides information about user access to a given resource. * @param resourceAttributes resource attributes for access review * @param impersonate impersonation details * @returns Object with resource access information. */ export const checkAccess = (resourceAttributes, impersonate) => { // Destructure the attributes with defaults so we can create a stable cache key. const { group = '', resource = '', subresource = '', verb = '', name = '', namespace = '', } = resourceAttributes || {}; return checkAccessInternal(group, resource, subresource, verb, name, namespace, getImpersonateKey(impersonate)); }; /** * Hook that provides information about user access to a given resource. * @param resourceAttributes resource attributes for access review * @param impersonate impersonation details * @returns Array with `isAllowed` and `loading` values. */ export const useAccessReview = (resourceAttributes, impersonate, noCheckForEmptyGroupAndResource) => { const [loading, setLoading] = useSafetyFirst(true); const [isAllowed, setAllowed] = useSafetyFirst(false); // Destructure the attributes to pass them as dependencies to `useEffect`, // which doesn't do deep comparison of object dependencies. const { group = '', resource = '', subresource = '', verb = '', name = '', namespace = '', } = resourceAttributes; const impersonateKey = getImpersonateKey(impersonate); const skipCheck = noCheckForEmptyGroupAndResource && !group && !resource; useEffect(() => { if (skipCheck) { setAllowed(false); setLoading(false); return; } checkAccessInternal(group, resource, subresource, verb, name, namespace, impersonateKey) .then((result) => { setAllowed(result.status.allowed); setLoading(false); }) .catch((e) => { // eslint-disable-next-line no-console console.warn('SelfSubjectAccessReview failed', e); // Default to enabling the action if the access review fails so that we // don't incorrectly block users from actions they can perform. The server // still enforces access control. setAllowed(true); setLoading(false); }); }, [ setLoading, setAllowed, group, resource, subresource, verb, name, namespace, impersonateKey, skipCheck, ]); return [isAllowed, loading]; }; /** * @deprecated - Use useAccessReview from \@console/dynamic-plugin-sdk instead. * Hook that provides allowed status about user access to a given resource. * @param resourceAttributes resource attributes for access review * @param impersonate impersonation details * @returns The isAllowed boolean value. */ export const useAccessReviewAllowed = (resourceAttributes, impersonate) => { return useAccessReview(resourceAttributes, impersonate)[0]; };