fix(ui): properly sets hasSavePermission on nested documents (#6394)
This commit is contained in:
@@ -28,15 +28,16 @@ import {
|
||||
} from '../helpers.js'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
import {
|
||||
createNotUpdateSlug,
|
||||
docLevelAccessSlug,
|
||||
fullyRestrictedSlug,
|
||||
noAdminAccessEmail,
|
||||
nonAdminUserEmail,
|
||||
nonAdminUserSlug,
|
||||
readOnlyGlobalSlug,
|
||||
readOnlySlug,
|
||||
restrictedSlug,
|
||||
restrictedVersionsSlug,
|
||||
slug,
|
||||
unrestrictedSlug,
|
||||
@@ -46,7 +47,6 @@ const dirname = path.dirname(filename)
|
||||
|
||||
/**
|
||||
* TODO: Access Control
|
||||
* prevent user from logging in (canAccessAdmin)
|
||||
*
|
||||
* FSK: 'should properly prevent / allow public users from reading a restricted field'
|
||||
*
|
||||
@@ -59,6 +59,7 @@ describe('access control', () => {
|
||||
let page: Page
|
||||
let url: AdminUrlUtil
|
||||
let restrictedUrl: AdminUrlUtil
|
||||
let unrestrictedURL: AdminUrlUtil
|
||||
let readOnlyCollectionUrl: AdminUrlUtil
|
||||
let readOnlyGlobalUrl: AdminUrlUtil
|
||||
let restrictedVersionsUrl: AdminUrlUtil
|
||||
@@ -71,7 +72,8 @@ describe('access control', () => {
|
||||
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
|
||||
|
||||
url = new AdminUrlUtil(serverURL, slug)
|
||||
restrictedUrl = new AdminUrlUtil(serverURL, restrictedSlug)
|
||||
restrictedUrl = new AdminUrlUtil(serverURL, fullyRestrictedSlug)
|
||||
unrestrictedURL = new AdminUrlUtil(serverURL, unrestrictedSlug)
|
||||
readOnlyCollectionUrl = new AdminUrlUtil(serverURL, readOnlySlug)
|
||||
readOnlyGlobalUrl = new AdminUrlUtil(serverURL, readOnlySlug)
|
||||
restrictedVersionsUrl = new AdminUrlUtil(serverURL, restrictedVersionsSlug)
|
||||
@@ -93,44 +95,51 @@ describe('access control', () => {
|
||||
logoutURL = `${serverURL}${adminRoute}${logoutRoute}`
|
||||
})
|
||||
|
||||
test('field without read access should not show', async () => {
|
||||
const { id } = await createDoc({ restrictedField: 'restricted' })
|
||||
describe('fields', () => {
|
||||
test('field without read access should not show', async () => {
|
||||
const { id } = await createDoc({ restrictedField: 'restricted' })
|
||||
|
||||
await page.goto(url.edit(id))
|
||||
await page.goto(url.edit(id))
|
||||
|
||||
await expect(page.locator('#field-restrictedField')).toHaveCount(0)
|
||||
await expect(page.locator('#field-restrictedField')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('field without read access inside a group should not show', async () => {
|
||||
const { id } = await createDoc({ restrictedField: 'restricted' })
|
||||
|
||||
await page.goto(url.edit(id))
|
||||
|
||||
await expect(page.locator('#field-group__restrictedGroupText')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('field without read access inside a collapsible should not show', async () => {
|
||||
const { id } = await createDoc({ restrictedField: 'restricted' })
|
||||
|
||||
await page.goto(url.edit(id))
|
||||
|
||||
await expect(page.locator('#field-restrictedRowText')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('field without read access inside a row should not show', async () => {
|
||||
const { id } = await createDoc({ restrictedField: 'restricted' })
|
||||
|
||||
await page.goto(url.edit(id))
|
||||
|
||||
await expect(page.locator('#field-restrictedCollapsibleText')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('should not show field without permission', async () => {
|
||||
await page.goto(url.account)
|
||||
await expect(page.locator('#field-roles')).toBeHidden()
|
||||
})
|
||||
})
|
||||
|
||||
test('field without read access inside a group should not show', async () => {
|
||||
const { id } = await createDoc({ restrictedField: 'restricted' })
|
||||
|
||||
await page.goto(url.edit(id))
|
||||
|
||||
await expect(page.locator('#field-group__restrictedGroupText')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('field without read access inside a collapsible should not show', async () => {
|
||||
const { id } = await createDoc({ restrictedField: 'restricted' })
|
||||
|
||||
await page.goto(url.edit(id))
|
||||
|
||||
await expect(page.locator('#field-restrictedRowText')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('field without read access inside a row should not show', async () => {
|
||||
const { id } = await createDoc({ restrictedField: 'restricted' })
|
||||
|
||||
await page.goto(url.edit(id))
|
||||
|
||||
await expect(page.locator('#field-restrictedCollapsibleText')).toHaveCount(0)
|
||||
})
|
||||
|
||||
describe('restricted collection', () => {
|
||||
describe('collection - fully restricted', () => {
|
||||
let existingDoc: ReadOnlyCollection
|
||||
|
||||
beforeAll(async () => {
|
||||
existingDoc = await payload.create({
|
||||
collection: restrictedSlug,
|
||||
collection: fullyRestrictedSlug,
|
||||
data: {
|
||||
name: 'name',
|
||||
},
|
||||
@@ -139,7 +148,7 @@ describe('access control', () => {
|
||||
|
||||
test('should not show in card list', async () => {
|
||||
await page.goto(url.admin)
|
||||
await expect(page.locator(`#card-${restrictedSlug}`)).toHaveCount(0)
|
||||
await expect(page.locator(`#card-${fullyRestrictedSlug}`)).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('should not show in nav', async () => {
|
||||
@@ -169,14 +178,7 @@ describe('access control', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('restricted fields', () => {
|
||||
test('should not show field without permission', async () => {
|
||||
await page.goto(url.account)
|
||||
await expect(page.locator('#field-roles')).toBeHidden()
|
||||
})
|
||||
})
|
||||
|
||||
describe('read-only collection', () => {
|
||||
describe('collection - read-only', () => {
|
||||
let existingDoc: ReadOnlyCollection
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -232,7 +234,84 @@ describe('access control', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('readVersions', () => {
|
||||
describe('collection - create but not edit', () => {
|
||||
test('should not show edit button', async () => {
|
||||
const createNotUpdateURL = new AdminUrlUtil(serverURL, createNotUpdateSlug)
|
||||
await page.goto(createNotUpdateURL.create)
|
||||
await page.waitForURL(createNotUpdateURL.create)
|
||||
await expect(page.locator('#field-name')).toBeVisible()
|
||||
await page.locator('#field-name').fill('name')
|
||||
await expect(page.locator('#field-name')).toHaveValue('name')
|
||||
await expect(page.locator('#action-save')).toBeVisible()
|
||||
await page.locator('#action-save').click()
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
await expect(page.locator('#action-save')).toBeHidden()
|
||||
await expect(page.locator('#field-name')).toBeDisabled()
|
||||
})
|
||||
|
||||
test('should maintain access control in document drawer', async () => {
|
||||
const unrestrictedDoc = await payload.create({
|
||||
collection: unrestrictedSlug,
|
||||
data: {
|
||||
name: 'unrestricted-123',
|
||||
},
|
||||
})
|
||||
|
||||
await page.goto(unrestrictedURL.edit(unrestrictedDoc.id.toString()))
|
||||
|
||||
const addDocButton = page.locator(
|
||||
'#createNotUpdateDocs-add-new button.relationship-add-new__add-button.doc-drawer__toggler',
|
||||
)
|
||||
|
||||
await expect(addDocButton).toBeVisible()
|
||||
await addDocButton.click()
|
||||
const documentDrawer = page.locator('[id^=doc-drawer_create-not-update_1_]')
|
||||
await expect(documentDrawer).toBeVisible()
|
||||
await expect(documentDrawer.locator('#action-save')).toBeVisible()
|
||||
await documentDrawer.locator('#field-name').fill('name')
|
||||
await expect(documentDrawer.locator('#field-name')).toHaveValue('name')
|
||||
await documentDrawer.locator('#action-save').click()
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
await expect(documentDrawer.locator('#action-save')).toBeHidden()
|
||||
await expect(documentDrawer.locator('#field-name')).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('collection - dynamic update access', () => {
|
||||
test('maintain access control in document drawer', async () => {
|
||||
const unrestrictedDoc = await payload.create({
|
||||
collection: unrestrictedSlug,
|
||||
data: {
|
||||
name: 'unrestricted-123',
|
||||
},
|
||||
})
|
||||
|
||||
await page.goto(unrestrictedURL.edit(unrestrictedDoc.id.toString()))
|
||||
|
||||
const addDocButton = page.locator(
|
||||
'#userRestrictedDocs-add-new button.relationship-add-new__add-button.doc-drawer__toggler',
|
||||
)
|
||||
|
||||
await addDocButton.click()
|
||||
const documentDrawer = page.locator('[id^=doc-drawer_user-restricted_1_]')
|
||||
await expect(documentDrawer).toBeVisible()
|
||||
await documentDrawer.locator('#field-name').fill('anonymous@email.com')
|
||||
await documentDrawer.locator('#action-save').click()
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
await expect(documentDrawer.locator('#field-name')).toBeDisabled()
|
||||
await documentDrawer.locator('button.doc-drawer__header-close').click()
|
||||
await expect(documentDrawer).toBeHidden()
|
||||
await addDocButton.click()
|
||||
const documentDrawer2 = page.locator('[id^=doc-drawer_user-restricted_1_]')
|
||||
await expect(documentDrawer2).toBeVisible()
|
||||
await documentDrawer2.locator('#field-name').fill('dev@payloadcms.com')
|
||||
await documentDrawer2.locator('#action-save').click()
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
await expect(documentDrawer2.locator('#field-name')).toBeEnabled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('collection - restricted versions', () => {
|
||||
let existingDoc: RestrictedVersion
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -323,111 +402,75 @@ describe('access control', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('maintain access control in document drawer', async () => {
|
||||
const unrestrictedDoc = await payload.create({
|
||||
collection: unrestrictedSlug,
|
||||
data: {
|
||||
name: 'unrestricted-123',
|
||||
},
|
||||
describe('admin access', () => {
|
||||
test('should block admin access to admin user', async () => {
|
||||
const adminURL = `${serverURL}/admin`
|
||||
await page.goto(adminURL)
|
||||
await page.waitForURL(adminURL)
|
||||
|
||||
await expect(page.locator('.dashboard')).toBeVisible()
|
||||
|
||||
await page.goto(logoutURL)
|
||||
await page.waitForURL(logoutURL)
|
||||
|
||||
await login({
|
||||
page,
|
||||
serverURL,
|
||||
data: {
|
||||
email: noAdminAccessEmail,
|
||||
password: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
await expect(page.locator('.next-error-h1')).toBeVisible()
|
||||
|
||||
await page.goto(logoutURL)
|
||||
await page.waitForURL(logoutURL)
|
||||
|
||||
// Log back in for the next test
|
||||
await login({
|
||||
page,
|
||||
serverURL,
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// navigate to the `unrestricted` document and open the drawers to test access
|
||||
const unrestrictedURL = new AdminUrlUtil(serverURL, unrestrictedSlug)
|
||||
await page.goto(unrestrictedURL.edit(unrestrictedDoc.id.toString()))
|
||||
test('should block admin access to non-admin user', async () => {
|
||||
const adminURL = `${serverURL}/admin`
|
||||
await page.goto(adminURL)
|
||||
await page.waitForURL(adminURL)
|
||||
|
||||
const addDocButton = page.locator(
|
||||
'#userRestrictedDocs-add-new button.relationship-add-new__add-button.doc-drawer__toggler',
|
||||
)
|
||||
await addDocButton.click()
|
||||
const documentDrawer = page.locator('[id^=doc-drawer_user-restricted_1_]')
|
||||
await expect(documentDrawer).toBeVisible()
|
||||
await documentDrawer.locator('#field-name').fill('anonymous@email.com')
|
||||
await documentDrawer.locator('#action-save').click()
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
await expect(page.locator('.dashboard')).toBeVisible()
|
||||
|
||||
// ensure user is not allowed to edit this document
|
||||
await expect(documentDrawer.locator('#field-name')).toBeDisabled()
|
||||
await documentDrawer.locator('button.doc-drawer__header-close').click()
|
||||
await expect(documentDrawer).toBeHidden()
|
||||
await page.goto(logoutURL)
|
||||
await page.waitForURL(logoutURL)
|
||||
|
||||
await addDocButton.click()
|
||||
const documentDrawer2 = page.locator('[id^=doc-drawer_user-restricted_1_]')
|
||||
await expect(documentDrawer2).toBeVisible()
|
||||
await documentDrawer2.locator('#field-name').fill('dev@payloadcms.com')
|
||||
await documentDrawer2.locator('#action-save').click()
|
||||
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||
const nonAdminUser: NonAdminUser & {
|
||||
token?: string
|
||||
} = await payload.login({
|
||||
collection: nonAdminUserSlug,
|
||||
data: {
|
||||
email: nonAdminUserEmail,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
|
||||
// ensure user is allowed to edit this document
|
||||
await expect(documentDrawer2.locator('#field-name')).toBeEnabled()
|
||||
})
|
||||
await context.addCookies([
|
||||
{
|
||||
name: 'payload-token',
|
||||
value: nonAdminUser.token,
|
||||
url: serverURL,
|
||||
},
|
||||
])
|
||||
|
||||
test('should block admin access to admin user', async () => {
|
||||
const adminURL = `${serverURL}/admin`
|
||||
await page.goto(adminURL)
|
||||
await page.waitForURL(adminURL)
|
||||
await page.goto(adminURL)
|
||||
await page.waitForURL(adminURL)
|
||||
|
||||
await expect(page.locator('.dashboard')).toBeVisible()
|
||||
|
||||
await page.goto(logoutURL)
|
||||
await page.waitForURL(logoutURL)
|
||||
|
||||
await login({
|
||||
page,
|
||||
serverURL,
|
||||
data: {
|
||||
email: noAdminAccessEmail,
|
||||
password: 'test',
|
||||
},
|
||||
await expect(page.locator('.next-error-h1')).toBeVisible()
|
||||
})
|
||||
|
||||
await expect(page.locator('.next-error-h1')).toBeVisible()
|
||||
|
||||
await page.goto(logoutURL)
|
||||
await page.waitForURL(logoutURL)
|
||||
|
||||
// Log back in for the next test
|
||||
await login({
|
||||
page,
|
||||
serverURL,
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('should block admin access to non-admin user', async () => {
|
||||
const adminURL = `${serverURL}/admin`
|
||||
await page.goto(adminURL)
|
||||
await page.waitForURL(adminURL)
|
||||
|
||||
await expect(page.locator('.dashboard')).toBeVisible()
|
||||
|
||||
await page.goto(logoutURL)
|
||||
await page.waitForURL(logoutURL)
|
||||
|
||||
const nonAdminUser: NonAdminUser & {
|
||||
token?: string
|
||||
} = await payload.login({
|
||||
collection: nonAdminUserSlug,
|
||||
data: {
|
||||
email: nonAdminUserEmail,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
|
||||
await context.addCookies([
|
||||
{
|
||||
name: 'payload-token',
|
||||
value: nonAdminUser.token,
|
||||
url: serverURL,
|
||||
},
|
||||
])
|
||||
|
||||
await page.goto(adminURL)
|
||||
await page.waitForURL(adminURL)
|
||||
|
||||
await expect(page.locator('.next-error-h1')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user