Summary: - Placed rule-updating functions in acl.py. - Reset UI when rules update externally, or alert the user to reset if there are pending local changes. - Removed some unused and distracting bits from client-side DocModel. A few improvements related to poor error handling: - In case of missing DocActions (tickled by broken ACL rule handling), don't add to confusion by attempting to process bad actions - In case of missing attributes in ACL formulas, return undefined rather than fail; the latter creates more problems. - In case in invalid rules, fail rather than skip; this feels more correct now that we have error checking and recovery option, and helps avoid invalid rules. - Prevent saving invalid rules with an empty ACL formula. - Fix bug with rule positions. Test Plan: Added a python and browser test for table/column renames. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2698pull/3/head
parent
d6d1eb217f
commit
5deac68315
@ -1,14 +0,0 @@
|
||||
import {ACLPrincipalRec, DocModel, IRowModel, refRecord} from 'app/client/models/DocModel';
|
||||
import * as ko from 'knockout';
|
||||
|
||||
// Table for containment relationships between Principals, e.g. user contains multiple
|
||||
// instances, group contains multiple users, and groups may contain other groups.
|
||||
export interface ACLMembershipRec extends IRowModel<"_grist_ACLMemberships"> {
|
||||
parentRec: ko.Computed<ACLPrincipalRec>;
|
||||
childRec: ko.Computed<ACLPrincipalRec>;
|
||||
}
|
||||
|
||||
export function createACLMembershipRec(this: ACLMembershipRec, docModel: DocModel): void {
|
||||
this.parentRec = refRecord(docModel.aclPrincipals, this.parent);
|
||||
this.childRec = refRecord(docModel.aclPrincipals, this.child);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import {KoArray} from 'app/client/lib/koArray';
|
||||
import {ACLMembershipRec, DocModel, IRowModel, recordSet} from 'app/client/models/DocModel';
|
||||
import {KoSaveableObservable} from 'app/client/models/modelUtil';
|
||||
import * as ko from 'knockout';
|
||||
|
||||
// A principals used by ACL rules, including users, groups, and instances.
|
||||
export interface ACLPrincipalRec extends IRowModel<"_grist_ACLPrincipals"> {
|
||||
// Declare a more specific type for 'type' than what's set automatically from schema.ts.
|
||||
type: KoSaveableObservable<'user'|'instance'|'group'>;
|
||||
|
||||
// KoArray of ACLMembership row models which contain this principal as a child.
|
||||
parentMemberships: ko.Computed<KoArray<ACLMembershipRec>>;
|
||||
|
||||
// Gives an array of ACLPrincipal parents to this row model.
|
||||
parents: ko.Computed<ACLPrincipalRec[]>;
|
||||
|
||||
// KoArray of ACLMembership row models which contain this principal as a parent.
|
||||
childMemberships: ko.Computed<KoArray<ACLMembershipRec>>;
|
||||
|
||||
// Gives an array of ACLPrincipal children of this row model.
|
||||
children: ko.Computed<ACLPrincipalRec[]>;
|
||||
}
|
||||
|
||||
export function createACLPrincipalRec(this: ACLPrincipalRec, docModel: DocModel): void {
|
||||
this.parentMemberships = recordSet(this, docModel.aclMemberships, 'child');
|
||||
this.childMemberships = recordSet(this, docModel.aclMemberships, 'parent');
|
||||
this.parents = ko.pureComputed(() => this.parentMemberships().all().map(m => m.parentRec()));
|
||||
this.children = ko.pureComputed(() => this.childMemberships().all().map(m => m.childRec()));
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
import {DocModel, IRowModel} from 'app/client/models/DocModel';
|
||||
|
||||
export type ACLResourceRec = IRowModel<"_grist_ACLResources">;
|
||||
|
||||
export function createACLResourceRec(this: ACLResourceRec, docModel: DocModel): void {
|
||||
// no extra fields
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
import test_engine
|
||||
import testsamples
|
||||
import useractions
|
||||
|
||||
user_attr1 = {
|
||||
'name': 'School',
|
||||
'charId': 'Email',
|
||||
'tableId': 'Schools',
|
||||
'lookupColId': 'LiasonEmail',
|
||||
}
|
||||
|
||||
class TestACLRenames(test_engine.EngineTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestACLRenames, self).setUp()
|
||||
|
||||
self.load_sample(testsamples.sample_students)
|
||||
|
||||
# Add column to Schools to use with User Attribute.
|
||||
self.engine.apply_user_actions([useractions.from_repr(ua) for ua in (
|
||||
['AddColumn', 'Schools', 'LiasonEmail', {'type': 'Text'}],
|
||||
['AddRecord', '_grist_ACLResources', -1, {'tableId': '*', 'colIds': '*'}],
|
||||
['AddRecord', '_grist_ACLRules', None, {
|
||||
'resource': -1,
|
||||
'userAttributes': json.dumps(user_attr1),
|
||||
}],
|
||||
['AddRecord', '_grist_ACLResources', -2, {
|
||||
'tableId': 'Students', 'colIds': 'firstName,lastName'
|
||||
}],
|
||||
['AddRecord', '_grist_ACLResources', -3, {
|
||||
'tableId': 'Students', 'colIds': '*'
|
||||
}],
|
||||
['AddRecord', '_grist_ACLRules', None, {
|
||||
'resource': -2,
|
||||
# Include comments and unicode to check that renaming respects all that.
|
||||
'aclFormula': '( rec.schoolName != # ünîcødé comment\n user.School.name)',
|
||||
'permissionsText': 'none',
|
||||
}],
|
||||
['AddRecord', '_grist_ACLRules', None, {
|
||||
'resource': -3,
|
||||
'permissionsText': 'all'
|
||||
}],
|
||||
)])
|
||||
|
||||
# Here's what we expect to be in the ACL tables (for reference in tests below).
|
||||
self.assertTableData('_grist_ACLResources', cols="subset", data=[
|
||||
['id', 'tableId', 'colIds'],
|
||||
[1, '*', '*'],
|
||||
[2, 'Students', 'firstName,lastName'],
|
||||
[3, 'Students', '*'],
|
||||
])
|
||||
self.assertTableData('_grist_ACLRules', cols="subset", data=[
|
||||
['id', 'resource', 'aclFormula', 'permissionsText', 'userAttributes'],
|
||||
[1, 1, '', '', json.dumps(user_attr1)],
|
||||
[2, 2, '( rec.schoolName != # ünîcødé comment\n user.School.name)', 'none', ''],
|
||||
[3, 3, '', 'all', ''],
|
||||
])
|
||||
|
||||
def test_acl_table_renames(self):
|
||||
# Rename some tables.
|
||||
self.apply_user_action(['RenameTable', 'Students', 'Estudiantes'])
|
||||
self.apply_user_action(['RenameTable', 'Schools', 'Escuelas'])
|
||||
|
||||
user_attr1_renamed = dict(user_attr1, tableId='Escuelas')
|
||||
|
||||
# Check the result of both renames.
|
||||
self.assertTableData('_grist_ACLResources', cols="subset", data=[
|
||||
['id', 'tableId', 'colIds'],
|
||||
[1, '*', '*'],
|
||||
[2, 'Estudiantes', 'firstName,lastName'],
|
||||
[3, 'Estudiantes', '*'],
|
||||
])
|
||||
self.assertTableData('_grist_ACLRules', cols="subset", data=[
|
||||
['id', 'resource', 'aclFormula', 'permissionsText', 'userAttributes'],
|
||||
[1, 1, '', '', json.dumps(user_attr1_renamed)],
|
||||
[2, 2, '( rec.schoolName != # ünîcødé comment\n user.School.name)', 'none', ''],
|
||||
[3, 3, '', 'all', ''],
|
||||
])
|
||||
|
||||
def test_acl_column_renames(self):
|
||||
# Rename some columns.
|
||||
self.apply_user_action(['RenameColumn', 'Students', 'lastName', 'Family_Name'])
|
||||
self.apply_user_action(['RenameColumn', 'Schools', 'name', 'schoolName'])
|
||||
self.apply_user_action(['RenameColumn', 'Students', 'schoolName', 'escuela'])
|
||||
self.apply_user_action(['RenameColumn', 'Schools', 'LiasonEmail', 'AdminEmail'])
|
||||
|
||||
user_attr1_renamed = dict(user_attr1, lookupColId='AdminEmail')
|
||||
|
||||
# Check the result of both renames.
|
||||
self.assertTableData('_grist_ACLResources', cols="subset", data=[
|
||||
['id', 'tableId', 'colIds'],
|
||||
[1, '*', '*'],
|
||||
[2, 'Students', 'firstName,Family_Name'],
|
||||
[3, 'Students', '*'],
|
||||
])
|
||||
self.assertTableData('_grist_ACLRules', cols="subset", data=[
|
||||
['id', 'resource', 'aclFormula', 'permissionsText', 'userAttributes'],
|
||||
[1, 1, '', '', json.dumps(user_attr1_renamed)],
|
||||
[2, 2, '( rec.escuela != # ünîcødé comment\n user.School.schoolName)', 'none', ''],
|
||||
[3, 3, '', 'all', ''],
|
||||
])
|
||||
|
||||
def test_multiple_renames(self):
|
||||
# Combine several renames into one bundle.
|
||||
self.engine.apply_user_actions([useractions.from_repr(ua) for ua in (
|
||||
['RenameColumn', 'Students', 'firstName', 'Given_Name'],
|
||||
['RenameColumn', 'Students', 'lastName', 'Family_Name'],
|
||||
['RenameTable', 'Students', 'Students2'],
|
||||
['RenameColumn', 'Students2', 'schoolName', 'escuela'],
|
||||
['RenameColumn', 'Schools', 'name', 'schoolName'],
|
||||
)])
|
||||
self.assertTableData('_grist_ACLResources', cols="subset", data=[
|
||||
['id', 'tableId', 'colIds'],
|
||||
[1, '*', '*'],
|
||||
[2, 'Students2', 'Given_Name,Family_Name'],
|
||||
[3, 'Students2', '*'],
|
||||
])
|
||||
self.assertTableData('_grist_ACLRules', cols="subset", data=[
|
||||
['id', 'resource', 'aclFormula', 'permissionsText', 'userAttributes'],
|
||||
[1, 1, '', '', json.dumps(user_attr1)],
|
||||
[2, 2, '( rec.escuela != # ünîcødé comment\n user.School.schoolName)', 'none', ''],
|
||||
[3, 3, '', 'all', ''],
|
||||
])
|
Loading…
Reference in new issue