@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
JavaScript
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];
};