mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
233 lines
10 KiB
TypeScript
233 lines
10 KiB
TypeScript
|
/**
|
||
|
* Test of the UI for the SchemaEdit permission in Granular Access Control.
|
||
|
*/
|
||
|
import { UserAPI } from 'app/common/UserAPI';
|
||
|
import { TableRecordValue } from 'app/common/DocActions';
|
||
|
import { assert, driver } from 'mocha-webdriver';
|
||
|
import { assertChanged, assertSaved, enterRulePart,
|
||
|
findDefaultRuleSet, findTable, getRules } from 'test/nbrowser/aclTestUtils';
|
||
|
import * as gu from 'test/nbrowser/gristUtils';
|
||
|
import { setupTestSuite } from 'test/nbrowser/testUtils';
|
||
|
import pick = require('lodash/pick');
|
||
|
|
||
|
describe("AccessRulesSchemaEdit", function() {
|
||
|
this.timeout(40000);
|
||
|
const cleanup = setupTestSuite();
|
||
|
let docId: string;
|
||
|
let mainSession: gu.Session;
|
||
|
let api: UserAPI;
|
||
|
let editorApi: UserAPI;
|
||
|
|
||
|
before(async function() {
|
||
|
const editorSession = await gu.session().teamSite.user('user2').login();
|
||
|
editorApi = editorSession.createHomeApi();
|
||
|
|
||
|
// Import a test document we've set up for this.
|
||
|
mainSession = await gu.session().teamSite.user('user1').login();
|
||
|
docId = await mainSession.tempNewDoc(cleanup, 'ACL-SchemaEdit', {load: false});
|
||
|
|
||
|
// Share it with a few users.
|
||
|
api = mainSession.createHomeApi();
|
||
|
await api.updateDocPermissions(docId, { users: {
|
||
|
[gu.translateUser("user2").email]: 'editors',
|
||
|
} });
|
||
|
return docId;
|
||
|
});
|
||
|
|
||
|
afterEach(() => gu.checkForErrors());
|
||
|
|
||
|
it('should allow disabling non-owner schemaEdit via checkbox', async function() {
|
||
|
const mainDocApi = api.getDocAPI(docId);
|
||
|
|
||
|
// Open Access Rules page.
|
||
|
await loadAccessRulesPage(mainSession, docId);
|
||
|
|
||
|
// Check the schemaEdit checkbox is checked (editors allowed)
|
||
|
assert.equal(await getSchemaEditCheckbox().isSelected(), true);
|
||
|
|
||
|
// Check that a warning is present.
|
||
|
assert.equal(await driver.find('.test-rule-schema-edit-warning').isDisplayed(), true);
|
||
|
|
||
|
// Check that default rules don't show the schemaEdit bit.
|
||
|
assert.deepEqual((await getRules(findTable('*')))[0],
|
||
|
{res: 'All', formula: 'user.Access in [EDITOR, OWNER]', perm: '+R+U+C+D'});
|
||
|
|
||
|
// Check that an editor CAN make structure changes (default behavior is unchanged).
|
||
|
await assert.isFulfilled(editorApi.applyUserActions(docId, [["RenameTable", 'Table1', 'Renamed1']]));
|
||
|
|
||
|
// Uncheck the box.
|
||
|
await getSchemaEditCheckbox().click();
|
||
|
assert.equal(await getSchemaEditCheckbox().isSelected(), false);
|
||
|
|
||
|
// Check the warning is now absent.
|
||
|
assert.equal(await driver.find('.test-rule-schema-edit-warning').isPresent(), false);
|
||
|
await assertChanged();
|
||
|
|
||
|
// Save the changes.
|
||
|
await saveRules();
|
||
|
|
||
|
// Check that it works: an editor cannot make structure changes.
|
||
|
await assert.isRejected(editorApi.applyUserActions(docId, [["RenameTable", 'Renamed1', 'Table1']]),
|
||
|
/Blocked by table structure access rules/);
|
||
|
|
||
|
// Check that after reload, the box is unchecked and the warning is gone.
|
||
|
await reloadAccessRulesPage();
|
||
|
assert.equal(await getSchemaEditCheckbox().isSelected(), false);
|
||
|
assert.equal(await driver.find('.test-rule-schema-edit-warning').isPresent(), false);
|
||
|
assert.equal(await getSchemaEditRuleSet().isPresent(), false);
|
||
|
|
||
|
// Check what the rules are on the default resource.
|
||
|
const rules = await mainDocApi.getRecords('_grist_ACLRules');
|
||
|
const defaultResourceRef = (await getDefaultResourceRec(api, docId))!.id;
|
||
|
assert.deepEqual(rules.map(r => pick(r.fields, "resource", "aclFormula", "permissionsText")),
|
||
|
[{resource: defaultResourceRef, aclFormula: 'user.Access != OWNER', permissionsText: '-S'}]);
|
||
|
|
||
|
// Revert by checking the box again.
|
||
|
await getSchemaEditCheckbox().click();
|
||
|
assert.equal(await getSchemaEditCheckbox().isSelected(), true);
|
||
|
|
||
|
// Check the warning is now present.
|
||
|
assert.equal(await driver.find('.test-rule-schema-edit-warning').isDisplayed(), true);
|
||
|
|
||
|
// Save the changes.
|
||
|
await saveRules();
|
||
|
|
||
|
// Check that it works: an editor can make structure changes again.
|
||
|
await assert.isFulfilled(editorApi.applyUserActions(docId, [["RenameTable", 'Renamed1', 'Table1']]));
|
||
|
|
||
|
// Check there are no rules left.
|
||
|
assert.lengthOf(await mainDocApi.getRecords('_grist_ACLRules'), 0);
|
||
|
});
|
||
|
|
||
|
it('should allow dismissing the warning', async function() {
|
||
|
const mainDocApi = api.getDocAPI(docId);
|
||
|
await loadAccessRulesPage(mainSession, docId);
|
||
|
|
||
|
// Check we are in the expected default state: checkbox is checked and a warning is present.
|
||
|
assert.equal(await getSchemaEditCheckbox().isSelected(), true);
|
||
|
assert.equal(await driver.find('.test-rule-schema-edit-warning').isPresent(), true);
|
||
|
|
||
|
// Click "Dismiss", and save.
|
||
|
await driver.findContent('.test-rule-schema-edit-warning a', /Dismiss/).click();
|
||
|
await assertChanged();
|
||
|
await saveRules();
|
||
|
|
||
|
// Check that an editor can still make structure changes.
|
||
|
await assert.isFulfilled(editorApi.applyUserActions(docId, [["RenameTable", 'Table1', 'Renamed2']]));
|
||
|
|
||
|
// Check that after reload, the box is checked and warning is gone.
|
||
|
await reloadAccessRulesPage();
|
||
|
assert.equal(await getSchemaEditCheckbox().isSelected(), true);
|
||
|
assert.equal(await driver.find('.test-rule-schema-edit-warning').isPresent(), false);
|
||
|
assert.equal(await getSchemaEditRuleSet().isPresent(), false);
|
||
|
|
||
|
// Check what the rules are on the default resource.
|
||
|
const rules = await mainDocApi.getRecords('_grist_ACLRules');
|
||
|
const defaultResourceRef = (await getDefaultResourceRec(api, docId))!.id;
|
||
|
assert.deepEqual(rules.map(r => pick(r.fields, "resource", "aclFormula", "permissionsText")),
|
||
|
[{resource: defaultResourceRef, aclFormula: 'user.Access == EDITOR', permissionsText: '+S'}]);
|
||
|
|
||
|
// Revert the rule change; wait for page to reload.
|
||
|
await gu.undo();
|
||
|
await driver.findWait('.test-rule-set', 2000);
|
||
|
|
||
|
assert.equal(await getSchemaEditCheckbox().isSelected(), true);
|
||
|
assert.equal(await driver.find('.test-rule-schema-edit-warning').isPresent(), true);
|
||
|
assert.lengthOf(await mainDocApi.getRecords('_grist_ACLRules'), 0);
|
||
|
|
||
|
// Let's revert also the table rename, to keep test cases independent.
|
||
|
await assert.isFulfilled(editorApi.applyUserActions(docId, [["RenameTable", 'Renamed2', 'Table1']]));
|
||
|
});
|
||
|
|
||
|
it('should handle existing rules that mix schemaEdit and other permissions', async function() {
|
||
|
const editorEmailAddr = gu.translateUser("user2").email;
|
||
|
const customAclFormula = `user.Email == "${editorEmailAddr}"`;
|
||
|
|
||
|
// Use the API to add a mixed rule, with both a 'schemaEdit' and a 'delete' permission.
|
||
|
const defaultResourceRef = (await getDefaultResourceRec(api, docId))!.id;
|
||
|
await api.applyUserActions(docId, [
|
||
|
['AddRecord', '_grist_ACLRules', null, {
|
||
|
resource: defaultResourceRef,
|
||
|
aclFormula: customAclFormula,
|
||
|
permissionsText: '-DS',
|
||
|
memo: 'Memo MIXED',
|
||
|
}],
|
||
|
]);
|
||
|
|
||
|
// Load the Access Rules page.
|
||
|
await loadAccessRulesPage(mainSession, docId);
|
||
|
|
||
|
// The new rule should be visible in both the default section, and in SchemaEdit section
|
||
|
// (which should be expanded).
|
||
|
assert.deepEqual((await getRules(findTable('*')))[0],
|
||
|
{formula: customAclFormula, perm: '-D', memo: 'Memo MIXED', res: 'All'});
|
||
|
|
||
|
assert.equal(await getSchemaEditRuleSet().isDisplayed(), true);
|
||
|
assert.deepEqual((await getRules(driver.find('.test-rule-special-SchemaEdit')))[0],
|
||
|
{formula: customAclFormula, perm: '-S', memo: 'Memo MIXED'});
|
||
|
|
||
|
// Check that the checkbox is disabled (since non-standard state).
|
||
|
assert.equal(await getSchemaEditCheckbox().getAttribute('disabled'), 'true');
|
||
|
|
||
|
// Check that the rule works.
|
||
|
let error = await editorApi.applyUserActions(docId, [["RenameTable", 'Table1', 'Renamed3']])
|
||
|
.then(() => null).catch(err => err);
|
||
|
assert.match(error?.message, /Blocked by table structure access rules/);
|
||
|
assert.deepInclude(error?.details, {memos: ['Memo MIXED']});
|
||
|
|
||
|
// Change the memos on both copies of the rule.
|
||
|
await enterRulePart(findDefaultRuleSet('*'), 1, null, {}, 'Memo DDD');
|
||
|
await enterRulePart(getSchemaEditRuleSet(), 1, null, {}, 'Memo SSS');
|
||
|
|
||
|
// Save.
|
||
|
await saveRules();
|
||
|
|
||
|
// The rules should look as before, only the memo is different.
|
||
|
assert.deepEqual((await getRules(findTable('*')))[0],
|
||
|
{formula: customAclFormula, perm: '-D', memo: 'Memo DDD', res: 'All'});
|
||
|
assert.deepEqual((await getRules(driver.find('.test-rule-special-SchemaEdit')))[0],
|
||
|
{formula: customAclFormula, perm: '-S', memo: 'Memo SSS'});
|
||
|
|
||
|
// Check that the changed rule works.
|
||
|
error = await editorApi.applyUserActions(docId, [["RenameTable", 'Table1', 'Renamed3']])
|
||
|
.then(() => null).catch(err => err);
|
||
|
assert.match(error?.message, /Blocked by table structure access rules/);
|
||
|
assert.deepInclude(error?.details, {memos: ['Memo SSS']});
|
||
|
|
||
|
// Check what the rules are on the default resource.
|
||
|
const mainDocApi = api.getDocAPI(docId);
|
||
|
const rules = await mainDocApi.getRecords('_grist_ACLRules');
|
||
|
assert.sameDeepMembers(rules.map(r => pick(r.fields, "resource", "aclFormula", "permissionsText")), [
|
||
|
{resource: defaultResourceRef, aclFormula: customAclFormula, permissionsText: '-S'},
|
||
|
{resource: defaultResourceRef, aclFormula: customAclFormula, permissionsText: '-D'}
|
||
|
]);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
function getSchemaEditCheckbox() {
|
||
|
return driver.find('.test-rule-special-SchemaEdit input[type=checkbox]');
|
||
|
}
|
||
|
function getSchemaEditRuleSet() {
|
||
|
return driver.find('.test-rule-special-SchemaEdit .test-rule-set');
|
||
|
}
|
||
|
function getSaveButton() {
|
||
|
return driver.find('.test-rules-save');
|
||
|
}
|
||
|
async function saveRules() {
|
||
|
await getSaveButton().click();
|
||
|
await gu.waitForServer();
|
||
|
await assertSaved();
|
||
|
}
|
||
|
async function loadAccessRulesPage(session: gu.Session, docId: string) {
|
||
|
await session.loadRelPath(`/doc/${docId}/p/acl`);
|
||
|
await driver.findWait('.test-rule-set', 5000);
|
||
|
}
|
||
|
async function reloadAccessRulesPage() {
|
||
|
await driver.navigate().refresh();
|
||
|
await driver.findWait('.test-rule-set', 5000);
|
||
|
}
|
||
|
async function getDefaultResourceRec(api: UserAPI, docId: string): Promise<TableRecordValue|undefined> {
|
||
|
const records = await api.getDocAPI(docId).getRecords('_grist_ACLResources', {filters: {tableId: ['*']}});
|
||
|
return records[0];
|
||
|
}
|