apostrophe 2.222.0 → 2.223.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/lib/modules/apostrophe-doc-type-manager/index.js +15 -0
- package/lib/modules/apostrophe-docs/lib/api.js +2 -1
- package/lib/modules/apostrophe-pages/lib/api.js +2 -1
- package/lib/modules/apostrophe-permissions/lib/strategiesApi.js +13 -1
- package/lib/modules/apostrophe-schemas/index.js +4 -2
- package/lib/modules/apostrophe-users/index.js +1 -0
- package/package.json +1 -1
- package/test/concurrent-array-joins.js +98 -0
- package/test/permissions.js +9 -0
- package/test/schemas.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.223.0 (2022-11-28)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Allows to exclude certain groups of users from seeing a piece or a page in `Permissions` tab.
|
|
8
|
+
|
|
9
|
+
### Fixes
|
|
10
|
+
|
|
11
|
+
* Handles joins in array fields correctly under high concurrent load. Previously it was possible for joins in array fields to fail to load under certain conditions.
|
|
12
|
+
|
|
3
13
|
## 2.222.0 (2022-07-20)
|
|
4
14
|
|
|
5
15
|
## Adds
|
|
@@ -59,6 +59,12 @@ module.exports = {
|
|
|
59
59
|
value: 'certainUsers',
|
|
60
60
|
label: 'Certain People',
|
|
61
61
|
showFields: [ '_viewGroups', '_viewUsers' ]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
value: 'excludeCertainUsers',
|
|
65
|
+
label: 'Exclude Certain People',
|
|
66
|
+
help: 'Selected group members will not be allowed to view the document. Please note that it will still require a logged-in user to view it, logged-out users will be excluded.',
|
|
67
|
+
showFields: [ '_excludeViewGroups' ]
|
|
62
68
|
}
|
|
63
69
|
]
|
|
64
70
|
},
|
|
@@ -80,6 +86,15 @@ module.exports = {
|
|
|
80
86
|
sortable: false,
|
|
81
87
|
editDocs: false
|
|
82
88
|
},
|
|
89
|
+
{
|
|
90
|
+
name: '_excludeViewGroups',
|
|
91
|
+
type: 'joinByArray',
|
|
92
|
+
withType: 'apostrophe-group',
|
|
93
|
+
label: 'These Groups cannot View',
|
|
94
|
+
idsField: 'excludeViewGroupsIds',
|
|
95
|
+
sortable: false,
|
|
96
|
+
editDocs: false
|
|
97
|
+
},
|
|
83
98
|
{
|
|
84
99
|
name: '_editUsers',
|
|
85
100
|
type: 'joinByArray',
|
|
@@ -336,6 +336,7 @@ module.exports = function(self, options) {
|
|
|
336
336
|
var fields = {
|
|
337
337
|
viewGroupsIds: 'view',
|
|
338
338
|
viewUsersIds: 'view',
|
|
339
|
+
excludeViewGroupsIds: 'exclude-view',
|
|
339
340
|
editGroupsIds: 'edit',
|
|
340
341
|
editUsersIds: 'edit'
|
|
341
342
|
};
|
|
@@ -947,7 +948,7 @@ module.exports = function(self, options) {
|
|
|
947
948
|
// etc.
|
|
948
949
|
|
|
949
950
|
self.docUnversionedFields = function(req, doc, fields) {
|
|
950
|
-
fields.push('slug', 'docPermissions', 'viewUserIds', 'viewGroupIds', 'editUserIds', 'editGroupIds', 'loginRequired');
|
|
951
|
+
fields.push('slug', 'docPermissions', 'viewUserIds', 'viewGroupIds', 'excludeViewGroupIds', 'editUserIds', 'editGroupIds', 'loginRequired');
|
|
951
952
|
};
|
|
952
953
|
|
|
953
954
|
// Lock the given doc id to a given `contextId`, such
|
|
@@ -227,7 +227,7 @@ module.exports = function(self, options) {
|
|
|
227
227
|
|
|
228
228
|
var admin = req.user && req.user._permissions.admin;
|
|
229
229
|
|
|
230
|
-
var allowed = [ 'view' ];
|
|
230
|
+
var allowed = [ 'view', 'exclude-view' ];
|
|
231
231
|
if (admin) {
|
|
232
232
|
allowed.push('edit');
|
|
233
233
|
}
|
|
@@ -313,6 +313,7 @@ module.exports = function(self, options) {
|
|
|
313
313
|
'loginRequired',
|
|
314
314
|
'viewUsersIds',
|
|
315
315
|
'viewGroupsIds',
|
|
316
|
+
'excludeViewGroupsIds',
|
|
316
317
|
'editUsersIds',
|
|
317
318
|
'editGroupsIds',
|
|
318
319
|
'docPermissions'
|
|
@@ -58,6 +58,12 @@ module.exports = function(self, options) {
|
|
|
58
58
|
if (req.user && _.intersection(self.userPermissionNames(req.user, 'edit'), object.docPermissions).length) {
|
|
59
59
|
return true;
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
// Case #5: object is restricted to exclude certain people
|
|
63
|
+
if (req.user && object.published && (object.loginRequired === 'excludeCertainUsers') && _.intersection(self.userPermissionNames(req.user, 'exclude-view'), object.docPermissions).length === 0) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
} else {
|
|
62
68
|
// Not view permissions
|
|
63
69
|
|
|
@@ -95,7 +101,6 @@ module.exports = function(self, options) {
|
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
// Case #3: doc is restricted to certain people
|
|
98
|
-
|
|
99
104
|
clauses.push({
|
|
100
105
|
published: true,
|
|
101
106
|
loginRequired: 'certainUsers',
|
|
@@ -121,6 +126,13 @@ module.exports = function(self, options) {
|
|
|
121
126
|
}
|
|
122
127
|
]
|
|
123
128
|
});
|
|
129
|
+
|
|
130
|
+
// Case #5: doc is restricted to exclude certain people
|
|
131
|
+
clauses.push({
|
|
132
|
+
published: true,
|
|
133
|
+
loginRequired: 'excludeCertainUsers',
|
|
134
|
+
docPermissions: { $nin: self.userPermissionNames(req.user, 'exclude-view') }
|
|
135
|
+
});
|
|
124
136
|
}
|
|
125
137
|
} else {
|
|
126
138
|
// Not view permissions
|
|
@@ -698,9 +698,11 @@ module.exports = {
|
|
|
698
698
|
var joins = [];
|
|
699
699
|
|
|
700
700
|
function findJoins(schema, arrays) {
|
|
701
|
+
// Shallow clone at the end to ensure our _dotPath and _arrays
|
|
702
|
+
// properties are unique to this request
|
|
701
703
|
var _joins = _.filter(schema, function(field) {
|
|
702
704
|
return !!self.fieldTypes[field.type].join;
|
|
703
|
-
});
|
|
705
|
+
}).map(join => _.clone(join));
|
|
704
706
|
_.each(_joins, function(join) {
|
|
705
707
|
if (!arrays.length) {
|
|
706
708
|
join._dotPath = join.name;
|
|
@@ -2736,7 +2738,7 @@ module.exports = {
|
|
|
2736
2738
|
// Return all standard field names currently associated with permissions editing,
|
|
2737
2739
|
// for consistency in arrangeFields, batch permissions schemas, etc.
|
|
2738
2740
|
self.getPermissionsFieldNames = function() {
|
|
2739
|
-
return [ 'loginRequired', '_viewUsers', '_viewGroups', '_editUsers', '_editGroups', 'applyToSubpages' ];
|
|
2741
|
+
return [ 'loginRequired', '_viewUsers', '_viewGroups', '_excludeViewGroups', '_editUsers', '_editGroups', 'applyToSubpages' ];
|
|
2740
2742
|
};
|
|
2741
2743
|
|
|
2742
2744
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
let apos;
|
|
4
|
+
|
|
5
|
+
describe('Concurrent Array Joins', function() {
|
|
6
|
+
|
|
7
|
+
this.timeout(t.timeout);
|
|
8
|
+
|
|
9
|
+
after(function(done) {
|
|
10
|
+
return t.destroy(apos, done);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// EXISTENCE
|
|
14
|
+
|
|
15
|
+
it('should be a property of the apos object', function(done) {
|
|
16
|
+
apos = require('../index.js')({
|
|
17
|
+
root: module,
|
|
18
|
+
shortName: 'test',
|
|
19
|
+
|
|
20
|
+
modules: {
|
|
21
|
+
'apostrophe-express': {
|
|
22
|
+
secret: 'xxx',
|
|
23
|
+
port: 7900
|
|
24
|
+
},
|
|
25
|
+
'test-people': {
|
|
26
|
+
extend: 'apostrophe-pieces',
|
|
27
|
+
name: 'test-person',
|
|
28
|
+
alias: 'persons',
|
|
29
|
+
addFields: [
|
|
30
|
+
{
|
|
31
|
+
name: 'hobbies',
|
|
32
|
+
type: 'array',
|
|
33
|
+
schema: [
|
|
34
|
+
{
|
|
35
|
+
type: 'string',
|
|
36
|
+
name: 'name'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'joinByOne',
|
|
40
|
+
name: '_friend',
|
|
41
|
+
withType: 'test-person'
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
afterInit: function(callback) {
|
|
49
|
+
assert(apos.docs);
|
|
50
|
+
apos.argv._ = [];
|
|
51
|
+
return callback(null);
|
|
52
|
+
},
|
|
53
|
+
afterListen: function(err) {
|
|
54
|
+
assert(!err);
|
|
55
|
+
done();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should be able to retrieve hobbies in parallel with all joins', async function() {
|
|
61
|
+
const req = apos.tasks.getReq();
|
|
62
|
+
const hobbyists = [];
|
|
63
|
+
for (let i = 0; (i < 10); i++) {
|
|
64
|
+
hobbyists.push(await apos.persons.insert(req, {
|
|
65
|
+
title: `Hobbyist ${i}`,
|
|
66
|
+
published: true
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
for (let i = 0; (i < 10); i++) {
|
|
70
|
+
hobbyists[i].hobbies = [
|
|
71
|
+
{
|
|
72
|
+
name: `Hobby ${i}`,
|
|
73
|
+
friendId: hobbyists[9 - i]._id
|
|
74
|
+
}
|
|
75
|
+
];
|
|
76
|
+
await apos.persons.update(req, hobbyists[i]);
|
|
77
|
+
}
|
|
78
|
+
const promises = [];
|
|
79
|
+
for (let i = 0; (i < 100); i++) {
|
|
80
|
+
promises.push(apos.persons.find(req).toArray());
|
|
81
|
+
}
|
|
82
|
+
const results = await Promise.all(promises);
|
|
83
|
+
assert.strictEqual(results.length, 100);
|
|
84
|
+
for (const result of results) {
|
|
85
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
|
|
86
|
+
result.sort((a, b) => a.title.localeCompare(b.title));
|
|
87
|
+
assert.strictEqual(result.length, 10);
|
|
88
|
+
for (let i = 0; (i < 10); i++) {
|
|
89
|
+
const person = result[i];
|
|
90
|
+
assert.strictEqual(person.title, `Hobbyist ${i}`);
|
|
91
|
+
assert.strictEqual(person.hobbies.length, 1);
|
|
92
|
+
assert.strictEqual(person.hobbies[0].name, `Hobby ${i}`);
|
|
93
|
+
assert(person.hobbies[0]._friend);
|
|
94
|
+
assert.strictEqual(person.hobbies[0]._friend.title, `Hobbyist ${9 - i}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
package/test/permissions.js
CHANGED
|
@@ -86,6 +86,15 @@ describe('Permissions', function() {
|
|
|
86
86
|
it('certainUsers will not let you slide past to an unpublished doc', function() {
|
|
87
87
|
assert(!apos.permissions.can(req({ user: { _id: 1 } }), 'view-doc', { loginRequired: 'certainUsers', docPermissions: [ 'view-1' ] }));
|
|
88
88
|
});
|
|
89
|
+
it('forbids view-doc for individual with group id', function() {
|
|
90
|
+
assert(!apos.permissions.can(req({ user: { _id: 1, groupIds: [ 1001, 1002 ] } }), 'view-doc', { published: true, loginRequired: 'excludeCertainUsers', docPermissions: [ 'exclude-view-1002' ] }));
|
|
91
|
+
});
|
|
92
|
+
it('permits view-doc for individual with wrong group id', function() {
|
|
93
|
+
assert(apos.permissions.can(req({ user: { _id: 2, groupIds: [ 1001, 1002 ] } }), 'view-doc', { published: true, loginRequired: 'excludeCertainUsers', docPermissions: [ 'exclude-view-1003' ] }));
|
|
94
|
+
});
|
|
95
|
+
it('excludeCertainUsers will not let you slide past to an unpublished doc', function() {
|
|
96
|
+
assert(!apos.permissions.can(req({ user: { _id: 1 } }), 'exclude-view-doc', { loginRequired: 'excludeCertainUsers', docPermissions: [ 'exclude-view-1' ] }));
|
|
97
|
+
});
|
|
89
98
|
it('permits view-doc for unpublished doc for individual with group id for editing', function() {
|
|
90
99
|
assert(apos.permissions.can(req({ user: { _id: 1, groupIds: [ 1001, 1002 ] } }), 'view-doc', { docPermissions: [ 'edit-1002' ] }));
|
|
91
100
|
});
|