Compare commits
1 Commits
feat/auth-
...
fix/#2623
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5e84c1534 |
@@ -215,7 +215,9 @@ export const collapsible = baseField.keys({
|
||||
});
|
||||
|
||||
const tab = baseField.keys({
|
||||
name: joi.string().when('localized', { is: joi.exist(), then: joi.required() }),
|
||||
name: joi.string()
|
||||
.when('localized', { is: joi.exist(), then: joi.required() })
|
||||
.when('access', { is: joi.exist(), then: joi.required() }),
|
||||
localized: joi.boolean(),
|
||||
label: joi.alternatives().try(
|
||||
joi.string(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { CSSProperties } from 'react';
|
||||
import { Editor } from 'slate';
|
||||
import type { TFunction, i18n as Ii18n } from 'i18next';
|
||||
import type { i18n as Ii18n, TFunction } from 'i18next';
|
||||
import type { EditorProps } from '@monaco-editor/react';
|
||||
import { Operation, Where } from '../../types';
|
||||
import { SanitizedConfig } from '../../config/types';
|
||||
@@ -214,6 +214,7 @@ export type NamedTab = TabBase
|
||||
export type UnnamedTab = Omit<TabBase, 'name'> & {
|
||||
label: Record<string, string> | string
|
||||
localized?: never
|
||||
access?: never
|
||||
}
|
||||
|
||||
export type Tab = NamedTab | UnnamedTab
|
||||
|
||||
@@ -155,6 +155,15 @@ export function getEntityPolicies<T extends Args>(args: T): ReturnType<T> {
|
||||
field.tabs.forEach((tab) => {
|
||||
if (tabHasName(tab)) {
|
||||
if (!mutablePolicies[tab.name]) mutablePolicies[tab.name] = { fields: {} };
|
||||
if (tab.access && typeof tab.access[operation] === 'function') {
|
||||
promises.push(createAccessPromise({
|
||||
policiesObj: mutablePolicies[tab.name],
|
||||
access: tab.access[operation],
|
||||
operation,
|
||||
disableWhere: true,
|
||||
accessLevel: 'field',
|
||||
}));
|
||||
}
|
||||
executeFieldPolicies({
|
||||
policiesObj: mutablePolicies[tab.name],
|
||||
fields: tab.fields,
|
||||
|
||||
@@ -112,6 +112,44 @@ export default buildConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
// this tab should not be visible in the admin ui
|
||||
{
|
||||
name: 'restrictedTab',
|
||||
label: 'Restricted Tab',
|
||||
// access should work the same as a group field
|
||||
access: {
|
||||
create: () => false,
|
||||
read: () => false,
|
||||
update: () => false,
|
||||
},
|
||||
fields: [
|
||||
// the field will be visible even though the parent tab should restrict it
|
||||
{
|
||||
name: 'test',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// this tab is visible and can be clicked, even though there isn't a field available
|
||||
label: 'Restricted Fields',
|
||||
fields: [
|
||||
{
|
||||
name: 'tabText',
|
||||
type: 'text',
|
||||
access: {
|
||||
create: () => false,
|
||||
read: () => false,
|
||||
update: () => false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,14 @@ import payload from '../../src';
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil';
|
||||
import { initPayloadE2E } from '../helpers/configHelpers';
|
||||
import { login } from '../helpers';
|
||||
import { restrictedVersionsSlug, readOnlySlug, restrictedSlug, slug, docLevelAccessSlug, unrestrictedSlug } from './config';
|
||||
import {
|
||||
docLevelAccessSlug,
|
||||
readOnlySlug,
|
||||
restrictedSlug,
|
||||
restrictedVersionsSlug,
|
||||
slug,
|
||||
unrestrictedSlug,
|
||||
} from './config';
|
||||
import type { ReadOnlyCollection, RestrictedVersion } from './payload-types';
|
||||
import wait from '../../src/utilities/wait';
|
||||
|
||||
@@ -73,6 +80,24 @@ describe('access control', () => {
|
||||
await expect(page.locator('#field-restrictedCollapsibleText')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('field without read access inside a tab should not show', async () => {
|
||||
const { id } = await createDoc({});
|
||||
|
||||
await page.goto(url.edit(id));
|
||||
|
||||
// TODO: this shouldn't be passing right now, the tab exists
|
||||
await expect(page.getByText('Restricted Tab')).toBeHidden();
|
||||
});
|
||||
|
||||
test('tab without read access should not show', async () => {
|
||||
const { id } = await createDoc({ });
|
||||
|
||||
await page.goto(url.edit(id));
|
||||
|
||||
// TODO: this shouldn't be passing right now, the tab exists
|
||||
await expect(page.getByText('Restricted Fields')).toBeHidden();
|
||||
});
|
||||
|
||||
describe('restricted collection', () => {
|
||||
let existingDoc: ReadOnlyCollection;
|
||||
|
||||
|
||||
@@ -157,6 +157,34 @@ describe('Access Control', () => {
|
||||
expect(retrievedDoc.restrictedField).toBeUndefined();
|
||||
});
|
||||
|
||||
it('field within restricted tab should not show', async () => {
|
||||
const { id } = await payload.create({
|
||||
collection: slug,
|
||||
overrideAccess: true,
|
||||
data: {
|
||||
restrictedTab: { test: 'restricted' },
|
||||
},
|
||||
});
|
||||
|
||||
const retrievedDoc = await payload.findByID({ collection: slug, id, overrideAccess: false });
|
||||
|
||||
expect(retrievedDoc.restrictedTab).toBeUndefined();
|
||||
});
|
||||
|
||||
it('field within tab should not show', async () => {
|
||||
const { id } = await payload.create({
|
||||
collection: slug,
|
||||
overrideAccess: true,
|
||||
data: {
|
||||
tabText: 'restricted',
|
||||
},
|
||||
});
|
||||
|
||||
const retrievedDoc = await payload.findByID({ collection: slug, id, overrideAccess: false });
|
||||
|
||||
expect(retrievedDoc.tabText).toBeUndefined();
|
||||
});
|
||||
|
||||
it('field without read access should not show when overrideAccess: true', async () => {
|
||||
const { id, restrictedField } = await createDoc<Post>({ restrictedField: 'restricted' });
|
||||
|
||||
|
||||
@@ -9,25 +9,28 @@ export interface Config {
|
||||
collections: {
|
||||
users: User;
|
||||
posts: Post;
|
||||
unrestricted: Unrestricted;
|
||||
restricted: Restricted;
|
||||
'read-only-collection': ReadOnlyCollection;
|
||||
'user-restricted': UserRestricted;
|
||||
'restricted-versions': RestrictedVersion;
|
||||
'sibling-data': SiblingDatum;
|
||||
'rely-on-request-headers': RelyOnRequestHeader;
|
||||
'doc-level-access': DocLevelAccess;
|
||||
'hidden-fields': HiddenField;
|
||||
'hidden-access': HiddenAccess;
|
||||
};
|
||||
globals: {};
|
||||
}
|
||||
export interface User {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email?: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
password?: string;
|
||||
}
|
||||
export interface Post {
|
||||
@@ -38,26 +41,44 @@ export interface Post {
|
||||
};
|
||||
restrictedRowText?: string;
|
||||
restrictedCollapsibleText?: string;
|
||||
createdAt: string;
|
||||
restrictedTab: {
|
||||
test?: string;
|
||||
};
|
||||
test?: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface Unrestricted {
|
||||
id: string;
|
||||
name?: string;
|
||||
userRestrictedDocs?: string[] | UserRestricted[];
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface UserRestricted {
|
||||
id: string;
|
||||
name?: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface Restricted {
|
||||
id: string;
|
||||
name?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface ReadOnlyCollection {
|
||||
id: string;
|
||||
name?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface RestrictedVersion {
|
||||
id: string;
|
||||
name?: string;
|
||||
createdAt: string;
|
||||
hidden?: boolean;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface SiblingDatum {
|
||||
id: string;
|
||||
@@ -66,22 +87,22 @@ export interface SiblingDatum {
|
||||
text?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface RelyOnRequestHeader {
|
||||
id: string;
|
||||
name?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface DocLevelAccess {
|
||||
id: string;
|
||||
approvedForRemoval?: boolean;
|
||||
approvedTitle?: string;
|
||||
lockTitle?: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface HiddenField {
|
||||
id: string;
|
||||
@@ -95,6 +116,14 @@ export interface HiddenField {
|
||||
value?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
createdAt: string;
|
||||
hidden?: boolean;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface HiddenAccess {
|
||||
id: string;
|
||||
title: string;
|
||||
hidden?: boolean;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user