directus 9.2.1 → 9.2.2
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/dist/auth/drivers/ldap.js +6 -0
- package/dist/exceptions/index.d.ts +1 -0
- package/dist/exceptions/index.js +1 -0
- package/dist/exceptions/unexpected-response.d.ts +4 -0
- package/dist/exceptions/unexpected-response.js +10 -0
- package/dist/services/users.js +8 -6
- package/dist/tests/database/migrations/run.test.d.ts +1 -0
- package/dist/tests/database/migrations/run.test.js +29 -0
- package/dist/utils/get-permissions.d.ts +2 -2
- package/dist/utils/get-permissions.js +103 -66
- package/package.json +13 -12
|
@@ -87,6 +87,12 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
|
+
res.on('end', (result) => {
|
|
91
|
+
if ((result === null || result === void 0 ? void 0 : result.status) === 0) {
|
|
92
|
+
// Handle edge case with IBM systems where authenticated bind user could not fetch their DN
|
|
93
|
+
reject(new exceptions_1.UnexpectedResponseException('Failed to find bind user record'));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
90
96
|
});
|
|
91
97
|
});
|
|
92
98
|
}
|
package/dist/exceptions/index.js
CHANGED
|
@@ -27,3 +27,4 @@ __exportStar(require("./route-not-found"), exports);
|
|
|
27
27
|
__exportStar(require("./service-unavailable"), exports);
|
|
28
28
|
__exportStar(require("./unprocessable-entity"), exports);
|
|
29
29
|
__exportStar(require("./user-suspended"), exports);
|
|
30
|
+
__exportStar(require("./unexpected-response"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UnexpectedResponseException = void 0;
|
|
4
|
+
const exceptions_1 = require("@directus/shared/exceptions");
|
|
5
|
+
class UnexpectedResponseException extends exceptions_1.BaseException {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message, 503, 'UNEXPECTED_RESPONSE');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.UnexpectedResponseException = UnexpectedResponseException;
|
package/dist/services/users.js
CHANGED
|
@@ -19,6 +19,7 @@ const mail_1 = require("./mail");
|
|
|
19
19
|
const settings_1 = require("./settings");
|
|
20
20
|
const stall_1 = require("../utils/stall");
|
|
21
21
|
const perf_hooks_1 = require("perf_hooks");
|
|
22
|
+
const utils_2 = require("@directus/shared/utils");
|
|
22
23
|
class UsersService extends items_1.ItemsService {
|
|
23
24
|
constructor(options) {
|
|
24
25
|
super('directus_users', options);
|
|
@@ -251,7 +252,7 @@ class UsersService extends items_1.ItemsService {
|
|
|
251
252
|
}
|
|
252
253
|
const STALL_TIME = 500;
|
|
253
254
|
const timeStart = perf_hooks_1.performance.now();
|
|
254
|
-
const user = await this.knex.select('status').from('directus_users').where({ email }).first();
|
|
255
|
+
const user = await this.knex.select('status', 'password').from('directus_users').where({ email }).first();
|
|
255
256
|
if ((user === null || user === void 0 ? void 0 : user.status) !== 'active') {
|
|
256
257
|
await (0, stall_1.stall)(STALL_TIME, timeStart);
|
|
257
258
|
throw new exceptions_2.ForbiddenException();
|
|
@@ -261,7 +262,7 @@ class UsersService extends items_1.ItemsService {
|
|
|
261
262
|
knex: this.knex,
|
|
262
263
|
accountability: this.accountability,
|
|
263
264
|
});
|
|
264
|
-
const payload = { email, scope: 'password-reset' };
|
|
265
|
+
const payload = { email, scope: 'password-reset', hash: (0, utils_2.getSimpleHash)('' + user.password) };
|
|
265
266
|
const token = jsonwebtoken_1.default.sign(payload, env_1.default.SECRET, { expiresIn: '1d', issuer: 'directus' });
|
|
266
267
|
const acceptURL = url ? `${url}?token=${token}` : `${env_1.default.PUBLIC_URL}/admin/reset-password?token=${token}`;
|
|
267
268
|
const subjectLine = subject ? subject : 'Password Reset Request';
|
|
@@ -279,11 +280,12 @@ class UsersService extends items_1.ItemsService {
|
|
|
279
280
|
await (0, stall_1.stall)(STALL_TIME, timeStart);
|
|
280
281
|
}
|
|
281
282
|
async resetPassword(token, password) {
|
|
282
|
-
const { email, scope } = jsonwebtoken_1.default.verify(token, env_1.default.SECRET, { issuer: 'directus' });
|
|
283
|
-
if (scope !== 'password-reset')
|
|
283
|
+
const { email, scope, hash } = jsonwebtoken_1.default.verify(token, env_1.default.SECRET, { issuer: 'directus' });
|
|
284
|
+
if (scope !== 'password-reset' || !hash)
|
|
284
285
|
throw new exceptions_2.ForbiddenException();
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
await this.checkPasswordPolicy([password]);
|
|
287
|
+
const user = await this.knex.select('id', 'status', 'password').from('directus_users').where({ email }).first();
|
|
288
|
+
if ((user === null || user === void 0 ? void 0 : user.status) !== 'active' || hash !== (0, utils_2.getSimpleHash)('' + user.password)) {
|
|
287
289
|
throw new exceptions_2.ForbiddenException();
|
|
288
290
|
}
|
|
289
291
|
// Allow unauthenticated update
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const knex_1 = __importDefault(require("knex"));
|
|
7
|
+
const knex_mock_client_1 = require("knex-mock-client");
|
|
8
|
+
const run_1 = __importDefault(require("../../../database/migrations/run"));
|
|
9
|
+
describe('run', () => {
|
|
10
|
+
let db;
|
|
11
|
+
let tracker;
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
db = (0, knex_1.default)({ client: knex_mock_client_1.MockClient });
|
|
14
|
+
tracker = (0, knex_mock_client_1.getTracker)();
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
tracker.reset();
|
|
18
|
+
});
|
|
19
|
+
describe('when passed the argument up', () => {
|
|
20
|
+
it('returns "Nothing To Updage" if no directus_migrations', async () => {
|
|
21
|
+
// note the difference between an empty array and ['Empty']
|
|
22
|
+
tracker.on.select('directus_migrations').response(['Empty']);
|
|
23
|
+
await (0, run_1.default)(db, 'up').catch((e) => {
|
|
24
|
+
expect(e).toBeInstanceOf(Error);
|
|
25
|
+
expect(e.message).toBe('Nothing to upgrade');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { Accountability } from '@directus/shared/types';
|
|
1
|
+
import { Permission, Accountability } from '@directus/shared/types';
|
|
2
2
|
import { SchemaOverview } from '../types';
|
|
3
|
-
export declare function getPermissions(accountability: Accountability, schema: SchemaOverview): Promise<
|
|
3
|
+
export declare function getPermissions(accountability: Accountability, schema: SchemaOverview): Promise<Permission[]>;
|
|
@@ -16,14 +16,31 @@ const object_hash_1 = __importDefault(require("object-hash"));
|
|
|
16
16
|
const env_1 = __importDefault(require("../env"));
|
|
17
17
|
async function getPermissions(accountability, schema) {
|
|
18
18
|
const database = (0, database_1.default)();
|
|
19
|
-
const { systemCache } = (0, cache_1.getCache)();
|
|
19
|
+
const { systemCache, cache } = (0, cache_1.getCache)();
|
|
20
20
|
let permissions = [];
|
|
21
21
|
const { user, role, app, admin } = accountability;
|
|
22
22
|
const cacheKey = `permissions-${(0, object_hash_1.default)({ user, role, app, admin })}`;
|
|
23
23
|
if (env_1.default.CACHE_PERMISSIONS !== false) {
|
|
24
24
|
const cachedPermissions = await systemCache.get(cacheKey);
|
|
25
25
|
if (cachedPermissions) {
|
|
26
|
-
|
|
26
|
+
if (!cachedPermissions.containDynamicData) {
|
|
27
|
+
return processPermissions(accountability, cachedPermissions.permissions, {});
|
|
28
|
+
}
|
|
29
|
+
const cachedFilterContext = await (cache === null || cache === void 0 ? void 0 : cache.get(`filterContext-${(0, object_hash_1.default)({ user, role, permissions: cachedPermissions.permissions })}`));
|
|
30
|
+
if (cachedFilterContext) {
|
|
31
|
+
return processPermissions(accountability, cachedPermissions.permissions, cachedFilterContext);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const { permissions: parsedPermissions, requiredPermissionData, containDynamicData, } = parsePermissions(cachedPermissions.permissions);
|
|
35
|
+
permissions = parsedPermissions;
|
|
36
|
+
const filterContext = containDynamicData
|
|
37
|
+
? await getFilterContext(schema, accountability, requiredPermissionData)
|
|
38
|
+
: {};
|
|
39
|
+
if (containDynamicData && env_1.default.CACHE_ENABLED !== false) {
|
|
40
|
+
await (cache === null || cache === void 0 ? void 0 : cache.set(`filterContext-${(0, object_hash_1.default)({ user, role, permissions })}`, filterContext));
|
|
41
|
+
}
|
|
42
|
+
return processPermissions(accountability, permissions, filterContext);
|
|
43
|
+
}
|
|
27
44
|
}
|
|
28
45
|
}
|
|
29
46
|
if (accountability.admin !== true) {
|
|
@@ -31,76 +48,96 @@ async function getPermissions(accountability, schema) {
|
|
|
31
48
|
.select('*')
|
|
32
49
|
.from('directus_permissions')
|
|
33
50
|
.where({ role: accountability.role });
|
|
34
|
-
const requiredPermissionData =
|
|
35
|
-
|
|
36
|
-
$CURRENT_ROLE: [],
|
|
37
|
-
};
|
|
38
|
-
permissions = permissionsForRole.map((permissionRaw) => {
|
|
39
|
-
const permission = (0, lodash_1.cloneDeep)(permissionRaw);
|
|
40
|
-
if (permission.permissions && typeof permission.permissions === 'string') {
|
|
41
|
-
permission.permissions = JSON.parse(permission.permissions);
|
|
42
|
-
}
|
|
43
|
-
else if (permission.permissions === null) {
|
|
44
|
-
permission.permissions = {};
|
|
45
|
-
}
|
|
46
|
-
if (permission.validation && typeof permission.validation === 'string') {
|
|
47
|
-
permission.validation = JSON.parse(permission.validation);
|
|
48
|
-
}
|
|
49
|
-
else if (permission.validation === null) {
|
|
50
|
-
permission.validation = {};
|
|
51
|
-
}
|
|
52
|
-
if (permission.presets && typeof permission.presets === 'string') {
|
|
53
|
-
permission.presets = JSON.parse(permission.presets);
|
|
54
|
-
}
|
|
55
|
-
else if (permission.presets === null) {
|
|
56
|
-
permission.presets = {};
|
|
57
|
-
}
|
|
58
|
-
if (permission.fields && typeof permission.fields === 'string') {
|
|
59
|
-
permission.fields = permission.fields.split(',');
|
|
60
|
-
}
|
|
61
|
-
else if (permission.fields === null) {
|
|
62
|
-
permission.fields = [];
|
|
63
|
-
}
|
|
64
|
-
const extractPermissionData = (val) => {
|
|
65
|
-
if (typeof val === 'string' && val.startsWith('$CURRENT_USER.')) {
|
|
66
|
-
requiredPermissionData.$CURRENT_USER.push(val.replace('$CURRENT_USER.', ''));
|
|
67
|
-
}
|
|
68
|
-
if (typeof val === 'string' && val.startsWith('$CURRENT_ROLE.')) {
|
|
69
|
-
requiredPermissionData.$CURRENT_ROLE.push(val.replace('$CURRENT_ROLE.', ''));
|
|
70
|
-
}
|
|
71
|
-
return val;
|
|
72
|
-
};
|
|
73
|
-
(0, utils_1.deepMap)(permission.permissions, extractPermissionData);
|
|
74
|
-
(0, utils_1.deepMap)(permission.validation, extractPermissionData);
|
|
75
|
-
(0, utils_1.deepMap)(permission.presets, extractPermissionData);
|
|
76
|
-
return permission;
|
|
77
|
-
});
|
|
51
|
+
const { permissions: parsedPermissions, requiredPermissionData, containDynamicData, } = parsePermissions(permissionsForRole);
|
|
52
|
+
permissions = parsedPermissions;
|
|
78
53
|
if (accountability.app === true) {
|
|
79
54
|
permissions = (0, merge_permissions_1.mergePermissions)(permissions, app_access_permissions_1.appAccessMinimalPermissions.map((perm) => ({ ...perm, role: accountability.role })));
|
|
80
55
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) {
|
|
85
|
-
filterContext.$CURRENT_USER = await usersService.readOne(accountability.user, {
|
|
86
|
-
fields: requiredPermissionData.$CURRENT_USER,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
if (accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) {
|
|
90
|
-
filterContext.$CURRENT_ROLE = await rolesService.readOne(accountability.role, {
|
|
91
|
-
fields: requiredPermissionData.$CURRENT_ROLE,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
permissions = permissions.map((permission) => {
|
|
95
|
-
permission.permissions = (0, utils_1.parseFilter)(permission.permissions, accountability, filterContext);
|
|
96
|
-
permission.validation = (0, utils_1.parseFilter)(permission.validation, accountability, filterContext);
|
|
97
|
-
permission.presets = (0, utils_1.parseFilter)(permission.presets, accountability, filterContext);
|
|
98
|
-
return permission;
|
|
99
|
-
});
|
|
56
|
+
const filterContext = containDynamicData
|
|
57
|
+
? await getFilterContext(schema, accountability, requiredPermissionData)
|
|
58
|
+
: {};
|
|
100
59
|
if (env_1.default.CACHE_PERMISSIONS !== false) {
|
|
101
|
-
await systemCache.set(cacheKey, permissions);
|
|
60
|
+
await systemCache.set(cacheKey, { permissions, containDynamicData });
|
|
61
|
+
if (containDynamicData && env_1.default.CACHE_ENABLED !== false) {
|
|
62
|
+
await (cache === null || cache === void 0 ? void 0 : cache.set(`filterContext-${(0, object_hash_1.default)({ user, role, permissions })}`, filterContext));
|
|
63
|
+
}
|
|
102
64
|
}
|
|
65
|
+
return processPermissions(accountability, permissions, filterContext);
|
|
103
66
|
}
|
|
104
67
|
return permissions;
|
|
105
68
|
}
|
|
106
69
|
exports.getPermissions = getPermissions;
|
|
70
|
+
function parsePermissions(permissions) {
|
|
71
|
+
const requiredPermissionData = {
|
|
72
|
+
$CURRENT_USER: [],
|
|
73
|
+
$CURRENT_ROLE: [],
|
|
74
|
+
};
|
|
75
|
+
let containDynamicData = false;
|
|
76
|
+
permissions = permissions.map((permissionRaw) => {
|
|
77
|
+
const permission = (0, lodash_1.cloneDeep)(permissionRaw);
|
|
78
|
+
if (permission.permissions && typeof permission.permissions === 'string') {
|
|
79
|
+
permission.permissions = JSON.parse(permission.permissions);
|
|
80
|
+
}
|
|
81
|
+
else if (permission.permissions === null) {
|
|
82
|
+
permission.permissions = {};
|
|
83
|
+
}
|
|
84
|
+
if (permission.validation && typeof permission.validation === 'string') {
|
|
85
|
+
permission.validation = JSON.parse(permission.validation);
|
|
86
|
+
}
|
|
87
|
+
else if (permission.validation === null) {
|
|
88
|
+
permission.validation = {};
|
|
89
|
+
}
|
|
90
|
+
if (permission.presets && typeof permission.presets === 'string') {
|
|
91
|
+
permission.presets = JSON.parse(permission.presets);
|
|
92
|
+
}
|
|
93
|
+
else if (permission.presets === null) {
|
|
94
|
+
permission.presets = {};
|
|
95
|
+
}
|
|
96
|
+
if (permission.fields && typeof permission.fields === 'string') {
|
|
97
|
+
permission.fields = permission.fields.split(',');
|
|
98
|
+
}
|
|
99
|
+
else if (permission.fields === null) {
|
|
100
|
+
permission.fields = [];
|
|
101
|
+
}
|
|
102
|
+
const extractPermissionData = (val) => {
|
|
103
|
+
if (typeof val === 'string' && val.startsWith('$CURRENT_USER.')) {
|
|
104
|
+
requiredPermissionData.$CURRENT_USER.push(val.replace('$CURRENT_USER.', ''));
|
|
105
|
+
containDynamicData = true;
|
|
106
|
+
}
|
|
107
|
+
if (typeof val === 'string' && val.startsWith('$CURRENT_ROLE.')) {
|
|
108
|
+
requiredPermissionData.$CURRENT_ROLE.push(val.replace('$CURRENT_ROLE.', ''));
|
|
109
|
+
containDynamicData = true;
|
|
110
|
+
}
|
|
111
|
+
return val;
|
|
112
|
+
};
|
|
113
|
+
(0, utils_1.deepMap)(permission.permissions, extractPermissionData);
|
|
114
|
+
(0, utils_1.deepMap)(permission.validation, extractPermissionData);
|
|
115
|
+
(0, utils_1.deepMap)(permission.presets, extractPermissionData);
|
|
116
|
+
return permission;
|
|
117
|
+
});
|
|
118
|
+
return { permissions, requiredPermissionData, containDynamicData };
|
|
119
|
+
}
|
|
120
|
+
async function getFilterContext(schema, accountability, requiredPermissionData) {
|
|
121
|
+
const usersService = new users_1.UsersService({ schema });
|
|
122
|
+
const rolesService = new roles_1.RolesService({ schema });
|
|
123
|
+
const filterContext = {};
|
|
124
|
+
if (accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) {
|
|
125
|
+
filterContext.$CURRENT_USER = await usersService.readOne(accountability.user, {
|
|
126
|
+
fields: requiredPermissionData.$CURRENT_USER,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) {
|
|
130
|
+
filterContext.$CURRENT_ROLE = await rolesService.readOne(accountability.role, {
|
|
131
|
+
fields: requiredPermissionData.$CURRENT_ROLE,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return filterContext;
|
|
135
|
+
}
|
|
136
|
+
function processPermissions(accountability, permissions, filterContext) {
|
|
137
|
+
return permissions.map((permission) => {
|
|
138
|
+
permission.permissions = (0, utils_1.parseFilter)(permission.permissions, accountability, filterContext);
|
|
139
|
+
permission.validation = (0, utils_1.parseFilter)(permission.validation, accountability, filterContext);
|
|
140
|
+
permission.presets = (0, utils_1.parseFilter)(permission.presets, accountability, filterContext);
|
|
141
|
+
return permission;
|
|
142
|
+
});
|
|
143
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "directus",
|
|
3
|
-
"version": "9.2.
|
|
3
|
+
"version": "9.2.2",
|
|
4
4
|
"license": "GPL-3.0-only",
|
|
5
5
|
"homepage": "https://github.com/directus/directus#readme",
|
|
6
6
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content.",
|
|
@@ -76,16 +76,16 @@
|
|
|
76
76
|
],
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@aws-sdk/client-ses": "^3.40.0",
|
|
79
|
-
"@directus/app": "9.2.
|
|
80
|
-
"@directus/drive": "9.2.
|
|
81
|
-
"@directus/drive-azure": "9.2.
|
|
82
|
-
"@directus/drive-gcs": "9.2.
|
|
83
|
-
"@directus/drive-s3": "9.2.
|
|
84
|
-
"@directus/extensions-sdk": "9.2.
|
|
85
|
-
"@directus/format-title": "9.2.
|
|
86
|
-
"@directus/schema": "9.2.
|
|
87
|
-
"@directus/shared": "9.2.
|
|
88
|
-
"@directus/specs": "9.2.
|
|
79
|
+
"@directus/app": "9.2.2",
|
|
80
|
+
"@directus/drive": "9.2.2",
|
|
81
|
+
"@directus/drive-azure": "9.2.2",
|
|
82
|
+
"@directus/drive-gcs": "9.2.2",
|
|
83
|
+
"@directus/drive-s3": "9.2.2",
|
|
84
|
+
"@directus/extensions-sdk": "9.2.2",
|
|
85
|
+
"@directus/format-title": "9.2.2",
|
|
86
|
+
"@directus/schema": "9.2.2",
|
|
87
|
+
"@directus/shared": "9.2.2",
|
|
88
|
+
"@directus/specs": "9.2.2",
|
|
89
89
|
"@godaddy/terminus": "^4.9.0",
|
|
90
90
|
"@rollup/plugin-alias": "^3.1.2",
|
|
91
91
|
"@rollup/plugin-virtual": "^2.0.3",
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
"sqlite3": "^5.0.2",
|
|
170
170
|
"tedious": "^13.0.0"
|
|
171
171
|
},
|
|
172
|
-
"gitHead": "
|
|
172
|
+
"gitHead": "546d525175c7ecc6ac9c235b4ebf77a1ae209e10",
|
|
173
173
|
"devDependencies": {
|
|
174
174
|
"@types/async": "3.2.10",
|
|
175
175
|
"@types/atob": "2.1.2",
|
|
@@ -209,6 +209,7 @@
|
|
|
209
209
|
"copyfiles": "2.4.1",
|
|
210
210
|
"cross-env": "7.0.3",
|
|
211
211
|
"jest": "27.3.1",
|
|
212
|
+
"knex-mock-client": "^1.6.1",
|
|
212
213
|
"ts-jest": "27.0.7",
|
|
213
214
|
"ts-node-dev": "1.1.8",
|
|
214
215
|
"typescript": "4.5.2"
|