Roadmap/#1379 admin ui doc level access (#1624)

* feat: adds document level access endpoints so admin ui can now accurately reflect document level access control
* chore(docs): new doc access callout, updates useDocumentInfo props from change
This commit is contained in:
Jarrod Flesch
2022-12-14 16:14:29 -05:00
committed by GitHub
parent d9c45f62b1
commit eda6f70acb
23 changed files with 702 additions and 154 deletions

View File

@@ -10,6 +10,7 @@ export const restrictedSlug = 'restricted';
export const restrictedVersionsSlug = 'restricted-versions';
export const siblingDataSlug = 'sibling-data';
export const relyOnRequestHeadersSlug = 'rely-on-request-headers';
export const docLevelAccessSlug = 'doc-level-access';
const openAccess = {
create: () => true,
@@ -32,7 +33,7 @@ const UseRequestHeadersAccess: FieldAccess = ({ req: { headers } }) => {
export default buildConfig({
admin: {
user: 'users'
user: 'users',
},
collections: [
{
@@ -45,7 +46,7 @@ export default buildConfig({
setTimeout(resolve, 50, true); // set to 'true' or 'false' here to simulate the response
}),
},
fields: []
fields: [],
},
{
slug,
@@ -195,6 +196,52 @@ export default buildConfig({
},
],
},
{
slug: docLevelAccessSlug,
labels: {
singular: 'Doc Level Access',
plural: 'Doc Level Access',
},
access: {
delete: () => ({
and: [
{
approvedForRemoval: {
equals: true,
},
},
],
}),
},
fields: [
{
name: 'approvedForRemoval',
type: 'checkbox',
defaultValue: false,
admin: {
position: 'sidebar',
},
},
{
name: 'approvedTitle',
type: 'text',
localized: true,
access: {
update: (args) => {
if (args?.doc?.lockTitle) {
return false;
}
return true;
},
},
},
{
name: 'lockTitle',
type: 'checkbox',
defaultValue: false,
},
],
},
],
onInit: async (payload) => {
await payload.create({

View File

@@ -4,7 +4,7 @@ import payload from '../../src';
import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../helpers/configHelpers';
import { login } from '../helpers';
import { restrictedVersionsSlug, readOnlySlug, restrictedSlug, slug } from './config';
import { restrictedVersionsSlug, readOnlySlug, restrictedSlug, slug, docLevelAccessSlug } from './config';
import type { ReadOnlyCollection, RestrictedVersion } from './payload-types';
/**
@@ -17,16 +17,17 @@ import type { ReadOnlyCollection, RestrictedVersion } from './payload-types';
*/
const { beforeAll, describe } = test;
describe('access control', () => {
let page: Page;
let url: AdminUrlUtil;
let restrictedUrl: AdminUrlUtil;
let readOnlyUrl: AdminUrlUtil;
let restrictedVersionsUrl: AdminUrlUtil;
let serverURL: string;
beforeAll(async ({ browser }) => {
const { serverURL } = await initPayloadE2E(__dirname);
const config = await initPayloadE2E(__dirname);
serverURL = config.serverURL;
url = new AdminUrlUtil(serverURL, slug);
restrictedUrl = new AdminUrlUtil(serverURL, restrictedSlug);
@@ -174,11 +175,51 @@ describe('access control', () => {
await expect(page.locator('.versions-count')).not.toBeVisible();
});
});
describe('doc level access', () => {
let existingDoc: ReadOnlyCollection;
let docLevelAccessURL;
beforeAll(async () => {
docLevelAccessURL = new AdminUrlUtil(serverURL, docLevelAccessSlug);
existingDoc = await payload.create<any>({
collection: docLevelAccessSlug,
data: {
approvedTitle: 'Title',
lockTitle: true,
approvedForRemoval: false,
},
});
});
test('disable field based on document data', async () => {
await page.goto(docLevelAccessURL.edit(existingDoc.id));
// validate that the text input is disabled because the field is "locked"
const isDisabled = await page.locator('#field-approvedTitle').isDisabled();
expect(isDisabled).toBe(true);
});
test('disable operation based on document data', async () => {
await page.goto(docLevelAccessURL.edit(existingDoc.id));
// validate that the delete action is not displayed
const duplicateAction = page.locator('.collection-edit__collection-actions >> li').last();
await expect(duplicateAction).toContainText('Duplicate');
await page.locator('#field-approvedForRemoval + button').click();
await page.locator('#action-save').click();
const deleteAction = page.locator('.collection-edit__collection-actions >> li').last();
await expect(deleteAction).toContainText('Delete');
});
});
});
async function createDoc(data: any): Promise<{ id: string }> {
return payload.create({
collection: slug,
collection: docLevelAccessSlug,
data,
});
}