apostrophe 2.221.2 → 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 +20 -0
- package/index.js +7 -9
- 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 +2 -2
- 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,25 @@
|
|
|
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
|
+
|
|
13
|
+
## 2.222.0 (2022-07-20)
|
|
14
|
+
|
|
15
|
+
## Adds
|
|
16
|
+
|
|
17
|
+
* `testModule: true` is now compatible with `mocha` 10.x as well as previously supported versions of `mocha`. Thanks to [Amin Shazrin](https://github.com/ammein) for this contribution.
|
|
18
|
+
|
|
19
|
+
## Fixes
|
|
20
|
+
|
|
21
|
+
* `sanitize-html` dependency bumped to ensure a potential denial-of-service vector is closed.
|
|
22
|
+
|
|
3
23
|
## 2.221.2 (2022-07-06)
|
|
4
24
|
|
|
5
25
|
## Fixes
|
package/index.js
CHANGED
|
@@ -256,7 +256,7 @@ module.exports = function(options) {
|
|
|
256
256
|
while (m.parent) {
|
|
257
257
|
// The test file is the root as far as we are concerned,
|
|
258
258
|
// not mocha itself
|
|
259
|
-
if (m.parent.filename.match(
|
|
259
|
+
if (m.parent.filename.match(new RegExp(`${path.sep}node_modules${path.sep}mocha${path.sep}`))) {
|
|
260
260
|
return m;
|
|
261
261
|
}
|
|
262
262
|
m = m.parent;
|
|
@@ -415,20 +415,18 @@ module.exports = function(options) {
|
|
|
415
415
|
// and throws an exception if we don't
|
|
416
416
|
function findTestModule() {
|
|
417
417
|
var m = module;
|
|
418
|
-
var nodeModuleRegex;
|
|
419
|
-
if (
|
|
420
|
-
|
|
421
|
-
} else {
|
|
422
|
-
nodeModuleRegex = /node_modules\/mocha/;
|
|
418
|
+
var nodeModuleRegex = new RegExp("node_modules" + path.sep + "mocha");
|
|
419
|
+
if (!require.main.filename.match(nodeModuleRegex)) {
|
|
420
|
+
throw new Error('mocha does not seem to be running, is this really a test?');
|
|
423
421
|
}
|
|
424
422
|
while (m) {
|
|
425
423
|
if (m.parent && m.parent.filename.match(nodeModuleRegex)) {
|
|
426
424
|
return m;
|
|
425
|
+
} else if (!m.parent) {
|
|
426
|
+
// Mocha v10 doesn't inject mocha paths inside `module`, therefore, we only detect the parent until the last parent. But we can get Mocha running using `require.main` - Amin
|
|
427
|
+
return m;
|
|
427
428
|
}
|
|
428
429
|
m = m.parent;
|
|
429
|
-
if (!m) {
|
|
430
|
-
throw new Error('mocha does not seem to be running, is this really a test?');
|
|
431
|
-
}
|
|
432
430
|
}
|
|
433
431
|
}
|
|
434
432
|
}
|
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.223.0",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"request-promise": "^4.2.4",
|
|
73
73
|
"resolve": "^1.20.0",
|
|
74
74
|
"rimraf": "^2.7.1",
|
|
75
|
-
"sanitize-html": "^2.
|
|
75
|
+
"sanitize-html": "^2.7.1",
|
|
76
76
|
"server-destroy": "^1.0.1",
|
|
77
77
|
"sluggo": "^0.2.0",
|
|
78
78
|
"syntax-error": "^1.3.0",
|
|
@@ -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
|
});
|