@nest-boot/row-level-security 7.3.0 → 8.0.0-beta.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/dist/decorators/policy.decorator.d.ts +4 -4
- package/dist/decorators/policy.decorator.js +25 -31
- package/dist/decorators/policy.decorator.js.map +1 -1
- package/dist/enums/policy-command.enum.js +2 -5
- package/dist/enums/policy-command.enum.js.map +1 -1
- package/dist/enums/policy-mode.enum.js +2 -5
- package/dist/enums/policy-mode.enum.js.map +1 -1
- package/dist/enums/row-level-security-role.enum.js +2 -5
- package/dist/enums/row-level-security-role.enum.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +11 -27
- package/dist/index.js.map +1 -1
- package/dist/interfaces/index.d.ts +4 -4
- package/dist/interfaces/index.js +3 -19
- package/dist/interfaces/index.js.map +1 -1
- package/dist/interfaces/policy-metadata.interface.d.ts +2 -2
- package/dist/interfaces/policy-metadata.interface.js +1 -2
- package/dist/interfaces/policy-options.interface.d.ts +2 -2
- package/dist/interfaces/policy-options.interface.js +1 -2
- package/dist/interfaces/policy-sql-options.interface.d.ts +2 -2
- package/dist/interfaces/policy-sql-options.interface.js +1 -2
- package/dist/interfaces/row-level-security-migration-generator.interface.d.ts +1 -1
- package/dist/interfaces/row-level-security-migration-generator.interface.js +1 -2
- package/dist/row-level-security-driver.d.ts +4 -3
- package/dist/row-level-security-driver.js +8 -19
- package/dist/row-level-security-driver.js.map +1 -1
- package/dist/row-level-security-migration-generator.d.ts +1 -1
- package/dist/row-level-security-migration-generator.js +40 -41
- package/dist/row-level-security-migration-generator.js.map +1 -1
- package/dist/row-level-security-migration.d.ts +1 -1
- package/dist/row-level-security-migration.js +9 -13
- package/dist/row-level-security-migration.js.map +1 -1
- package/dist/row-level-security-migrator.d.ts +2 -2
- package/dist/row-level-security-migrator.js +9 -13
- package/dist/row-level-security-migrator.js.map +1 -1
- package/dist/row-level-security.d.ts +1 -1
- package/dist/row-level-security.js +19 -23
- package/dist/row-level-security.js.map +1 -1
- package/dist/utils/assert-identifier.js +1 -4
- package/dist/utils/assert-identifier.js.map +1 -1
- package/dist/utils/assert-snake-case.js +1 -4
- package/dist/utils/assert-snake-case.js.map +1 -1
- package/dist/utils/create-policy-bootstrap-sql-statements.js +1 -4
- package/dist/utils/create-policy-bootstrap-sql-statements.js.map +1 -1
- package/dist/utils/create-policy-down-sql.d.ts +1 -1
- package/dist/utils/create-policy-down-sql.js +5 -8
- package/dist/utils/create-policy-down-sql.js.map +1 -1
- package/dist/utils/create-policy-privilege-down-sql-statements.d.ts +1 -1
- package/dist/utils/create-policy-privilege-down-sql-statements.js +19 -22
- package/dist/utils/create-policy-privilege-down-sql-statements.js.map +1 -1
- package/dist/utils/create-policy-role-sql-statements.js +8 -13
- package/dist/utils/create-policy-role-sql-statements.js.map +1 -1
- package/dist/utils/create-policy-up-sql-statements.d.ts +1 -1
- package/dist/utils/create-policy-up-sql-statements.js +26 -29
- package/dist/utils/create-policy-up-sql-statements.js.map +1 -1
- package/dist/utils/create-row-level-security-transaction-setup.js +15 -18
- package/dist/utils/create-row-level-security-transaction-setup.js.map +1 -1
- package/dist/utils/escape-sql-literal.js +1 -4
- package/dist/utils/escape-sql-literal.js.map +1 -1
- package/dist/utils/index.d.ts +12 -12
- package/dist/utils/index.js +11 -27
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/is-postgres-keyword-requiring-quote.js +1 -4
- package/dist/utils/is-postgres-keyword-requiring-quote.js.map +1 -1
- package/dist/utils/normalize-postgres-type-alias.util.js +1 -4
- package/dist/utils/normalize-postgres-type-alias.util.js.map +1 -1
- package/dist/utils/quote-identifier.js +3 -6
- package/dist/utils/quote-identifier.js.map +1 -1
- package/dist/utils/quote-qualified-identifier.js +3 -6
- package/dist/utils/quote-qualified-identifier.js.map +1 -1
- package/dist/utils/row-level-security-context-builder.d.ts +1 -1
- package/dist/utils/row-level-security-context-builder.js +7 -13
- package/dist/utils/row-level-security-context-builder.js.map +1 -1
- package/dist/utils/row-level-security-context-builder.types.js +1 -2
- package/package.json +31 -31
- package/dist/decorators/policy.decorator.spec.d.ts +0 -1
- package/dist/decorators/policy.decorator.spec.js +0 -550
- package/dist/decorators/policy.decorator.spec.js.map +0 -1
- package/dist/index.spec.d.ts +0 -1
- package/dist/index.spec.js +0 -64
- package/dist/index.spec.js.map +0 -1
- package/dist/row-level-security-driver.spec.d.ts +0 -1
- package/dist/row-level-security-driver.spec.js +0 -267
- package/dist/row-level-security-driver.spec.js.map +0 -1
- package/dist/row-level-security-migration-generator.spec.d.ts +0 -1
- package/dist/row-level-security-migration-generator.spec.js +0 -1135
- package/dist/row-level-security-migration-generator.spec.js.map +0 -1
- package/dist/row-level-security-migration.spec.d.ts +0 -1
- package/dist/row-level-security-migration.spec.js +0 -32
- package/dist/row-level-security-migration.spec.js.map +0 -1
- package/dist/row-level-security.spec.d.ts +0 -1
- package/dist/row-level-security.spec.js +0 -52
- package/dist/row-level-security.spec.js.map +0 -1
- package/dist/tsconfig.build.tsbuildinfo +0 -1
- package/dist/utils/normalize-postgres-type-alias.spec.d.ts +0 -1
- package/dist/utils/normalize-postgres-type-alias.spec.js +0 -28
- package/dist/utils/normalize-postgres-type-alias.spec.js.map +0 -1
- package/dist/utils/policy-migration-sql.spec.d.ts +0 -1
- package/dist/utils/policy-migration-sql.spec.js +0 -273
- package/dist/utils/policy-migration-sql.spec.js.map +0 -1
- package/dist/utils/row-level-security-context-builder.spec.d.ts +0 -1
- package/dist/utils/row-level-security-context-builder.spec.js +0 -40
- package/dist/utils/row-level-security-context-builder.spec.js.map +0 -1
|
@@ -1,1135 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
const node_fs_1 = require("node:fs");
|
|
10
|
-
const node_os_1 = require("node:os");
|
|
11
|
-
const node_path_1 = require("node:path");
|
|
12
|
-
const migrations_1 = require("@mikro-orm/migrations");
|
|
13
|
-
const policy_decorator_1 = require("./decorators/policy.decorator");
|
|
14
|
-
const policy_command_enum_1 = require("./enums/policy-command.enum");
|
|
15
|
-
const row_level_security_migration_generator_1 = require("./row-level-security-migration-generator");
|
|
16
|
-
const row_level_security_migrator_1 = require("./row-level-security-migrator");
|
|
17
|
-
let WorkspaceMember = class WorkspaceMember {
|
|
18
|
-
};
|
|
19
|
-
WorkspaceMember = __decorate([
|
|
20
|
-
(0, policy_decorator_1.Policy)({
|
|
21
|
-
name: "workspace_member_user_select_policy",
|
|
22
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
23
|
-
using: `((select current_setting('app.user_id', true)::bigint) = "user_id")`,
|
|
24
|
-
})
|
|
25
|
-
], WorkspaceMember);
|
|
26
|
-
let WorkspaceMemberWithMultiplePolicies = class WorkspaceMemberWithMultiplePolicies {
|
|
27
|
-
};
|
|
28
|
-
WorkspaceMemberWithMultiplePolicies = __decorate([
|
|
29
|
-
(0, policy_decorator_1.Policy)({
|
|
30
|
-
name: "workspace_member_user_select_policy",
|
|
31
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
32
|
-
using: `((select current_setting('app.user_id', true)::bigint) = "user_id")`,
|
|
33
|
-
}),
|
|
34
|
-
(0, policy_decorator_1.Policy)({
|
|
35
|
-
name: "workspace_member_write_policy",
|
|
36
|
-
using: `((select current_setting('app.workspace_id', true)::bigint) = "workspace_id")`,
|
|
37
|
-
withCheck: `((select current_setting('app.workspace_id', true)::bigint) = "workspace_id")`,
|
|
38
|
-
})
|
|
39
|
-
], WorkspaceMemberWithMultiplePolicies);
|
|
40
|
-
let WorkspaceMemberWithGeneratedPolicy = class WorkspaceMemberWithGeneratedPolicy {
|
|
41
|
-
};
|
|
42
|
-
WorkspaceMemberWithGeneratedPolicy = __decorate([
|
|
43
|
-
(0, policy_decorator_1.Policy)({
|
|
44
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
45
|
-
property: "user",
|
|
46
|
-
context: "user_id",
|
|
47
|
-
})
|
|
48
|
-
], WorkspaceMemberWithGeneratedPolicy);
|
|
49
|
-
let WorkspaceMemberWithWorkspaceContextPolicy = class WorkspaceMemberWithWorkspaceContextPolicy {
|
|
50
|
-
};
|
|
51
|
-
WorkspaceMemberWithWorkspaceContextPolicy = __decorate([
|
|
52
|
-
(0, policy_decorator_1.Policy)({
|
|
53
|
-
name: "workspace_member_workspace_select_policy",
|
|
54
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
55
|
-
property: "workspace",
|
|
56
|
-
context: "workspace_id",
|
|
57
|
-
roles: ["authenticated"],
|
|
58
|
-
})
|
|
59
|
-
], WorkspaceMemberWithWorkspaceContextPolicy);
|
|
60
|
-
let WorkspaceMemberGroupMemberWithLongGeneratedPolicy = class WorkspaceMemberGroupMemberWithLongGeneratedPolicy {
|
|
61
|
-
};
|
|
62
|
-
WorkspaceMemberGroupMemberWithLongGeneratedPolicy = __decorate([
|
|
63
|
-
(0, policy_decorator_1.Policy)({
|
|
64
|
-
command: policy_command_enum_1.PolicyCommand.ALL,
|
|
65
|
-
property: "workspace",
|
|
66
|
-
context: "workspace_id",
|
|
67
|
-
roles: ["authenticated"],
|
|
68
|
-
})
|
|
69
|
-
], WorkspaceMemberGroupMemberWithLongGeneratedPolicy);
|
|
70
|
-
let AuditLog = class AuditLog {
|
|
71
|
-
};
|
|
72
|
-
AuditLog = __decorate([
|
|
73
|
-
(0, policy_decorator_1.Policy)({
|
|
74
|
-
name: "audit_log_select_policy",
|
|
75
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
76
|
-
using: "true",
|
|
77
|
-
})
|
|
78
|
-
], AuditLog);
|
|
79
|
-
let WorkspaceMemberWithCustomRole = class WorkspaceMemberWithCustomRole {
|
|
80
|
-
};
|
|
81
|
-
WorkspaceMemberWithCustomRole = __decorate([
|
|
82
|
-
(0, policy_decorator_1.Policy)({
|
|
83
|
-
name: "workspace_member_admin_select_policy",
|
|
84
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
85
|
-
using: "true",
|
|
86
|
-
roles: ["workspace_admin"],
|
|
87
|
-
})
|
|
88
|
-
], WorkspaceMemberWithCustomRole);
|
|
89
|
-
let WorkspaceMemberWithSharedRolePolicies = class WorkspaceMemberWithSharedRolePolicies {
|
|
90
|
-
};
|
|
91
|
-
WorkspaceMemberWithSharedRolePolicies = __decorate([
|
|
92
|
-
(0, policy_decorator_1.Policy)({
|
|
93
|
-
name: "workspace_member_admin_select_policy",
|
|
94
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
95
|
-
using: "true",
|
|
96
|
-
roles: ["workspace_admin"],
|
|
97
|
-
}),
|
|
98
|
-
(0, policy_decorator_1.Policy)({
|
|
99
|
-
name: "workspace_member_admin_update_policy",
|
|
100
|
-
command: policy_command_enum_1.PolicyCommand.UPDATE,
|
|
101
|
-
using: "true",
|
|
102
|
-
withCheck: "true",
|
|
103
|
-
roles: ["workspace_admin"],
|
|
104
|
-
})
|
|
105
|
-
], WorkspaceMemberWithSharedRolePolicies);
|
|
106
|
-
class UnmanagedEntity {
|
|
107
|
-
}
|
|
108
|
-
const WORKSPACE_MEMBER_FIELD_CHANGE_DIFF = {
|
|
109
|
-
up: ['alter table "workspace_member" add column "display_name" text null;'],
|
|
110
|
-
down: ['alter table "workspace_member" drop column "display_name";'],
|
|
111
|
-
};
|
|
112
|
-
const WORKSPACE_MEMBER_GROUP_MEMBER_FIELD_CHANGE_DIFF = {
|
|
113
|
-
up: [
|
|
114
|
-
'alter table "workspace_member_group_member" add column "display_name" text null;',
|
|
115
|
-
],
|
|
116
|
-
down: [
|
|
117
|
-
'alter table "workspace_member_group_member" drop column "display_name";',
|
|
118
|
-
],
|
|
119
|
-
};
|
|
120
|
-
const LONG_GENERATED_POLICY_NAME = "workspace_member_group_member_workspace_all_authenticated_policy";
|
|
121
|
-
const LONG_GENERATED_POLICY_NAME_POSTGRES_TRUNCATED = "workspace_member_group_member_workspace_all_authenticated_polic";
|
|
122
|
-
const LONG_GENERATED_POLICY_NAME_SHORTENED = "workspace_member_group_member_workspace_all_authe_fb4ec_policy";
|
|
123
|
-
describe("RowLevelSecurityMigrationGenerator", () => {
|
|
124
|
-
afterEach(() => {
|
|
125
|
-
jest.restoreAllMocks();
|
|
126
|
-
});
|
|
127
|
-
it("loads existing policies from the database while generating migrations", async () => {
|
|
128
|
-
const execute = jest.fn((_sql) => Promise.resolve([
|
|
129
|
-
{
|
|
130
|
-
policy_name: "workspace_member_user_select_policy",
|
|
131
|
-
schema_name: "public",
|
|
132
|
-
table_name: "workspace_member",
|
|
133
|
-
permissive: true,
|
|
134
|
-
command: "r",
|
|
135
|
-
qual: "true",
|
|
136
|
-
with_check: null,
|
|
137
|
-
roles: ["authenticated"],
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
policy_name: "workspace_member_insert_policy",
|
|
141
|
-
schema_name: "public",
|
|
142
|
-
table_name: "workspace_member",
|
|
143
|
-
permissive: false,
|
|
144
|
-
command: "a",
|
|
145
|
-
qual: null,
|
|
146
|
-
with_check: "true",
|
|
147
|
-
roles: ["authenticated"],
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
policy_name: "workspace_member_update_policy",
|
|
151
|
-
schema_name: "public",
|
|
152
|
-
table_name: "workspace_member",
|
|
153
|
-
permissive: true,
|
|
154
|
-
command: "w",
|
|
155
|
-
qual: "true",
|
|
156
|
-
with_check: "true",
|
|
157
|
-
roles: ["authenticated"],
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
policy_name: "workspace_member_delete_policy",
|
|
161
|
-
schema_name: "public",
|
|
162
|
-
table_name: "workspace_member",
|
|
163
|
-
permissive: true,
|
|
164
|
-
command: "d",
|
|
165
|
-
qual: "true",
|
|
166
|
-
with_check: null,
|
|
167
|
-
roles: ["authenticated"],
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
policy_name: "workspace_member_all_policy",
|
|
171
|
-
schema_name: "public",
|
|
172
|
-
table_name: "workspace_member",
|
|
173
|
-
permissive: true,
|
|
174
|
-
command: "*",
|
|
175
|
-
qual: "true",
|
|
176
|
-
with_check: "true",
|
|
177
|
-
roles: ["authenticated"],
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
policy_name: "workspace_member_public_select_policy",
|
|
181
|
-
schema_name: "public",
|
|
182
|
-
table_name: "workspace_member",
|
|
183
|
-
permissive: true,
|
|
184
|
-
command: "r",
|
|
185
|
-
qual: "true",
|
|
186
|
-
with_check: null,
|
|
187
|
-
roles: null,
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
policy_name: "workspace_member_string_roles_select_policy",
|
|
191
|
-
schema_name: "public",
|
|
192
|
-
table_name: "workspace_member",
|
|
193
|
-
permissive: true,
|
|
194
|
-
command: "r",
|
|
195
|
-
qual: "true",
|
|
196
|
-
with_check: null,
|
|
197
|
-
roles: "{authenticated,anonymous}",
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
policy_name: "external_workspace_policy",
|
|
201
|
-
schema_name: "external",
|
|
202
|
-
table_name: "workspace",
|
|
203
|
-
permissive: true,
|
|
204
|
-
command: "r",
|
|
205
|
-
qual: "true",
|
|
206
|
-
with_check: null,
|
|
207
|
-
roles: ["workspace_admin"],
|
|
208
|
-
},
|
|
209
|
-
]));
|
|
210
|
-
const superGenerate = jest
|
|
211
|
-
.spyOn(migrations_1.TSMigrationGenerator.prototype, "generate")
|
|
212
|
-
.mockImplementation(function () {
|
|
213
|
-
expect(this.existingPolicyDefinitions).toEqual(expect.arrayContaining([
|
|
214
|
-
expect.objectContaining({
|
|
215
|
-
policyName: "workspace_member_insert_policy",
|
|
216
|
-
mode: "restrictive",
|
|
217
|
-
command: "insert",
|
|
218
|
-
withCheck: "true",
|
|
219
|
-
roles: ["authenticated"],
|
|
220
|
-
}),
|
|
221
|
-
expect.objectContaining({
|
|
222
|
-
policyName: "workspace_member_delete_policy",
|
|
223
|
-
command: "delete",
|
|
224
|
-
using: "true",
|
|
225
|
-
}),
|
|
226
|
-
expect.objectContaining({
|
|
227
|
-
policyName: "workspace_member_all_policy",
|
|
228
|
-
command: "all",
|
|
229
|
-
}),
|
|
230
|
-
expect.objectContaining({
|
|
231
|
-
policyName: "workspace_member_public_select_policy",
|
|
232
|
-
roles: [],
|
|
233
|
-
}),
|
|
234
|
-
expect.objectContaining({
|
|
235
|
-
policyName: "workspace_member_string_roles_select_policy",
|
|
236
|
-
roles: ["anonymous", "authenticated"],
|
|
237
|
-
}),
|
|
238
|
-
]));
|
|
239
|
-
expect(this.existingPolicyDefinitions).not.toEqual(expect.arrayContaining([
|
|
240
|
-
expect.objectContaining({
|
|
241
|
-
policyName: "external_workspace_policy",
|
|
242
|
-
}),
|
|
243
|
-
]));
|
|
244
|
-
expect(this.existingPolicyRoleDefinitions).toEqual(expect.arrayContaining([
|
|
245
|
-
expect.objectContaining({
|
|
246
|
-
policyName: "external_workspace_policy",
|
|
247
|
-
roles: ["workspace_admin"],
|
|
248
|
-
}),
|
|
249
|
-
]));
|
|
250
|
-
expect(this.currentPolicyDefinitions).toEqual([
|
|
251
|
-
expect.objectContaining({
|
|
252
|
-
policyName: "workspace_member_user_select_policy",
|
|
253
|
-
tableName: "workspace_member",
|
|
254
|
-
}),
|
|
255
|
-
]);
|
|
256
|
-
return Promise.resolve(["migration-file", "/tmp/Migration.ts"]);
|
|
257
|
-
});
|
|
258
|
-
const generator = createGenerator([
|
|
259
|
-
{
|
|
260
|
-
class: WorkspaceMember,
|
|
261
|
-
tableName: "workspace_member",
|
|
262
|
-
},
|
|
263
|
-
], {
|
|
264
|
-
getConnection: () => ({
|
|
265
|
-
execute,
|
|
266
|
-
}),
|
|
267
|
-
});
|
|
268
|
-
await expect(generator.generate({ up: [], down: [] }, "/tmp", "Migration")).resolves.toEqual(["migration-file", "/tmp/Migration.ts"]);
|
|
269
|
-
expect(superGenerate).toHaveBeenCalledWith({ up: [], down: [] }, "/tmp", "Migration");
|
|
270
|
-
expect(execute.mock.calls[0]?.[0]).toContain("FROM pg_policy");
|
|
271
|
-
expect(execute.mock.calls[0]?.[0]).toContain("unnest(p.polroles)");
|
|
272
|
-
expect(generator.existingPolicyDefinitions).toBeUndefined();
|
|
273
|
-
expect(generator.existingPolicyRoleDefinitions).toBeUndefined();
|
|
274
|
-
expect(generator.currentPolicyDefinitions).toBeUndefined();
|
|
275
|
-
});
|
|
276
|
-
it("continues generation when the driver has no database connection", async () => {
|
|
277
|
-
const superGenerate = jest
|
|
278
|
-
.spyOn(migrations_1.TSMigrationGenerator.prototype, "generate")
|
|
279
|
-
.mockImplementation(function () {
|
|
280
|
-
expect(this.existingPolicyDefinitions).toBeUndefined();
|
|
281
|
-
return Promise.resolve(["migration-file", "/tmp/Migration.ts"]);
|
|
282
|
-
});
|
|
283
|
-
const generator = createGenerator([
|
|
284
|
-
{
|
|
285
|
-
class: WorkspaceMember,
|
|
286
|
-
tableName: "workspace_member",
|
|
287
|
-
},
|
|
288
|
-
]);
|
|
289
|
-
await expect(generator.generate({ up: [], down: [] })).resolves.toEqual([
|
|
290
|
-
"migration-file",
|
|
291
|
-
"/tmp/Migration.ts",
|
|
292
|
-
]);
|
|
293
|
-
expect(superGenerate).toHaveBeenCalledTimes(1);
|
|
294
|
-
});
|
|
295
|
-
it("detects pending policy-only changes from existing database policies", async () => {
|
|
296
|
-
const execute = jest.fn((_sql) => Promise.resolve([
|
|
297
|
-
{
|
|
298
|
-
policy_name: "workspace_member_workspace_select_policy",
|
|
299
|
-
schema_name: "public",
|
|
300
|
-
table_name: "workspace_member",
|
|
301
|
-
permissive: true,
|
|
302
|
-
command: "r",
|
|
303
|
-
qual: "(( SELECT current_setting('app.tenant_id'::text, true)::bigint) = workspace_id)",
|
|
304
|
-
with_check: null,
|
|
305
|
-
roles: ["authenticated"],
|
|
306
|
-
},
|
|
307
|
-
]));
|
|
308
|
-
const generator = createGenerator([
|
|
309
|
-
{
|
|
310
|
-
class: WorkspaceMemberWithWorkspaceContextPolicy,
|
|
311
|
-
tableName: "workspace_member",
|
|
312
|
-
properties: {
|
|
313
|
-
workspace: {
|
|
314
|
-
fieldNames: ["workspace_id"],
|
|
315
|
-
columnTypes: ["bigint"],
|
|
316
|
-
},
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
], {
|
|
320
|
-
getConnection: () => ({
|
|
321
|
-
execute,
|
|
322
|
-
}),
|
|
323
|
-
});
|
|
324
|
-
await expect(generator.hasPendingPolicyChanges({ up: [], down: [] })).resolves.toBe(true);
|
|
325
|
-
expect(generator.existingPolicyDefinitions).toBeUndefined();
|
|
326
|
-
expect(generator.currentPolicyDefinitions).toBeUndefined();
|
|
327
|
-
});
|
|
328
|
-
it("does not recreate policies for unrelated schema changes without a database connection", async () => {
|
|
329
|
-
const superGenerate = mockBaseMigrationGenerate();
|
|
330
|
-
const generator = createGenerator([
|
|
331
|
-
{
|
|
332
|
-
class: WorkspaceMember,
|
|
333
|
-
tableName: "workspace_member",
|
|
334
|
-
},
|
|
335
|
-
]);
|
|
336
|
-
const [file] = await generator.generate(WORKSPACE_MEMBER_FIELD_CHANGE_DIFF);
|
|
337
|
-
expect(superGenerate).toHaveBeenCalledWith(WORKSPACE_MEMBER_FIELD_CHANGE_DIFF, undefined, undefined);
|
|
338
|
-
expect(file).toContain("import { Migration } from '@mikro-orm/migrations';");
|
|
339
|
-
expect(file).toContain("extends Migration");
|
|
340
|
-
expect(file).not.toContain("workspace_member_user_select_policy");
|
|
341
|
-
});
|
|
342
|
-
it("does not recreate unchanged policies when database expressions are deparsed", async () => {
|
|
343
|
-
const execute = createExistingPolicyLookup("(( SELECT (current_setting('app.user_id'::text, true))::bigint AS current_setting) = user_id)");
|
|
344
|
-
const superGenerate = mockBaseMigrationGenerate();
|
|
345
|
-
const generator = createGenerator([
|
|
346
|
-
{
|
|
347
|
-
class: WorkspaceMember,
|
|
348
|
-
tableName: "workspace_member",
|
|
349
|
-
},
|
|
350
|
-
], {
|
|
351
|
-
getConnection: () => ({
|
|
352
|
-
execute,
|
|
353
|
-
}),
|
|
354
|
-
});
|
|
355
|
-
const [file] = await generator.generate(WORKSPACE_MEMBER_FIELD_CHANGE_DIFF);
|
|
356
|
-
expect(superGenerate).toHaveBeenCalledWith(WORKSPACE_MEMBER_FIELD_CHANGE_DIFF, undefined, undefined);
|
|
357
|
-
expect(file).toContain("import { Migration } from '@mikro-orm/migrations';");
|
|
358
|
-
expect(file).toContain("extends Migration");
|
|
359
|
-
expect(file).not.toContain("workspace_member_user_select_policy");
|
|
360
|
-
});
|
|
361
|
-
it("does not recreate unchanged generated policies with legacy PostgreSQL-truncated names", async () => {
|
|
362
|
-
const execute = jest.fn((_sql) => Promise.resolve([
|
|
363
|
-
{
|
|
364
|
-
policy_name: LONG_GENERATED_POLICY_NAME_POSTGRES_TRUNCATED,
|
|
365
|
-
schema_name: "public",
|
|
366
|
-
table_name: "workspace_member_group_member",
|
|
367
|
-
permissive: true,
|
|
368
|
-
command: "*",
|
|
369
|
-
qual: "(( SELECT (current_setting('app.workspace_id'::text, true))::bigint AS current_setting) = workspace_id)",
|
|
370
|
-
with_check: "(( SELECT (current_setting('app.workspace_id'::text, true))::bigint AS current_setting) = workspace_id)",
|
|
371
|
-
roles: ["authenticated"],
|
|
372
|
-
},
|
|
373
|
-
]));
|
|
374
|
-
const generator = createGenerator([
|
|
375
|
-
{
|
|
376
|
-
class: WorkspaceMemberGroupMemberWithLongGeneratedPolicy,
|
|
377
|
-
tableName: "workspace_member_group_member",
|
|
378
|
-
properties: {
|
|
379
|
-
workspace: {
|
|
380
|
-
fieldNames: ["workspace_id"],
|
|
381
|
-
columnTypes: ["bigint"],
|
|
382
|
-
},
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
], {
|
|
386
|
-
getConnection: () => ({
|
|
387
|
-
execute,
|
|
388
|
-
}),
|
|
389
|
-
});
|
|
390
|
-
mockBaseMigrationGenerate(WORKSPACE_MEMBER_GROUP_MEMBER_FIELD_CHANGE_DIFF);
|
|
391
|
-
const [file] = await generator.generate(WORKSPACE_MEMBER_GROUP_MEMBER_FIELD_CHANGE_DIFF);
|
|
392
|
-
expect(file).toContain('alter table "workspace_member_group_member" add column "display_name" text null;');
|
|
393
|
-
expect(file).toContain("extends Migration");
|
|
394
|
-
expect(file).not.toContain(LONG_GENERATED_POLICY_NAME_POSTGRES_TRUNCATED);
|
|
395
|
-
expect(file).not.toContain(LONG_GENERATED_POLICY_NAME);
|
|
396
|
-
expect(file).not.toContain(LONG_GENERATED_POLICY_NAME_SHORTENED);
|
|
397
|
-
});
|
|
398
|
-
it.each([
|
|
399
|
-
[
|
|
400
|
-
"deparsed-style context setting call",
|
|
401
|
-
`(select current_setting('app.user_id'::text, true)::bigint) = user_id`,
|
|
402
|
-
"(( SELECT current_setting('app.user_id'::text, true)::bigint) = user_id)",
|
|
403
|
-
],
|
|
404
|
-
[
|
|
405
|
-
"function name casing and deparsed select alias",
|
|
406
|
-
`(select current_setting('app.user_id'::text, true)::bigint) = user_id`,
|
|
407
|
-
`(( SELECT (CURRENT_SETTING('app.user_id'::text, TRUE))::bigint AS current_setting) = user_id)`,
|
|
408
|
-
],
|
|
409
|
-
[
|
|
410
|
-
"integer alias and non-keyword identifier quotes",
|
|
411
|
-
`((select current_setting('app.workspace_id', true)::integer) = "workspace_id")`,
|
|
412
|
-
"(( SELECT current_setting('app.workspace_id'::text, true)::integer) = workspace_id)",
|
|
413
|
-
],
|
|
414
|
-
[
|
|
415
|
-
"PostgreSQL keyword identifier quotes",
|
|
416
|
-
`((select current_setting('app.order_id', true)::integer) = "order")`,
|
|
417
|
-
`(( SELECT current_setting('app.order_id'::text, true)::integer) = "order")`,
|
|
418
|
-
],
|
|
419
|
-
[
|
|
420
|
-
"keyword casing and string literal casts",
|
|
421
|
-
`("status" = 'SELECT'::text AND NOT false)`,
|
|
422
|
-
"(status = 'SELECT' and not false)",
|
|
423
|
-
],
|
|
424
|
-
[
|
|
425
|
-
"JSON extraction operator spacing",
|
|
426
|
-
`claims->>'tenant_id' = 'acme'`,
|
|
427
|
-
`((claims ->> 'tenant_id'::text) = 'acme'::text)`,
|
|
428
|
-
],
|
|
429
|
-
[
|
|
430
|
-
"varchar context type alias",
|
|
431
|
-
`((select current_setting('app.tenant_id', true)::character varying) = "tenant_id")`,
|
|
432
|
-
"(( SELECT current_setting('app.tenant_id'::text, true)::character varying) = tenant_id)",
|
|
433
|
-
],
|
|
434
|
-
[
|
|
435
|
-
"parser fallback with escaped string literals",
|
|
436
|
-
`note = 'tenant''s ::character varying' AND ((select current_setting('app.tenant_id', true)::character varying) = tenant_id)`,
|
|
437
|
-
`note = 'tenant''s ::character varying' AND (( SELECT current_setting('app.tenant_id'::text, true)::character varying) = tenant_id)`,
|
|
438
|
-
],
|
|
439
|
-
[
|
|
440
|
-
"parser fallback with quoted identifiers",
|
|
441
|
-
`"tenant::character varying" = ((select current_setting('app.tenant_id', true)::character varying))`,
|
|
442
|
-
`"tenant::character varying" = (( SELECT current_setting('app.tenant_id'::text, true)::character varying))`,
|
|
443
|
-
],
|
|
444
|
-
[
|
|
445
|
-
"parser fallback with dollar-quoted strings exact match",
|
|
446
|
-
`note = $$::character varying$$ AND ((select current_setting('app.tenant_id', true)::character varying) = tenant_id)`,
|
|
447
|
-
`note = $$::character varying$$ AND ((select current_setting('app.tenant_id', true)::character varying) = tenant_id)`,
|
|
448
|
-
],
|
|
449
|
-
[
|
|
450
|
-
"array type aliases",
|
|
451
|
-
`scopes::character varying[] = array['admin']::character varying[]`,
|
|
452
|
-
`scopes::varchar[] = array['admin']::varchar[]`,
|
|
453
|
-
],
|
|
454
|
-
[
|
|
455
|
-
"timestamptz context type alias",
|
|
456
|
-
`((select current_setting('app.expires_at', true)::timestamp with time zone) > expires_at)`,
|
|
457
|
-
"(( SELECT current_setting('app.expires_at'::text, true)::timestamp with time zone) > expires_at)",
|
|
458
|
-
],
|
|
459
|
-
[
|
|
460
|
-
"PostgreSQL canonical not-equals operator",
|
|
461
|
-
`status != 'deleted'`,
|
|
462
|
-
`(status <> 'deleted'::text)`,
|
|
463
|
-
],
|
|
464
|
-
[
|
|
465
|
-
"unsupported PostgreSQL predicate syntax exact match",
|
|
466
|
-
"deleted_at IS DISTINCT FROM NULL",
|
|
467
|
-
"deleted_at IS DISTINCT FROM NULL",
|
|
468
|
-
],
|
|
469
|
-
])("does not recreate unchanged explicit policies with %s", async (_name, using, databaseQual) => {
|
|
470
|
-
let WorkspaceMemberWithExplicitPolicy = class WorkspaceMemberWithExplicitPolicy {
|
|
471
|
-
};
|
|
472
|
-
WorkspaceMemberWithExplicitPolicy = __decorate([
|
|
473
|
-
(0, policy_decorator_1.Policy)({
|
|
474
|
-
name: "workspace_member_user_select_policy",
|
|
475
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
476
|
-
using,
|
|
477
|
-
})
|
|
478
|
-
], WorkspaceMemberWithExplicitPolicy);
|
|
479
|
-
const file = await generateFieldChangeMigrationFile(WorkspaceMemberWithExplicitPolicy, databaseQual);
|
|
480
|
-
expect(file).not.toContain("workspace_member_user_select_policy");
|
|
481
|
-
});
|
|
482
|
-
it.each([
|
|
483
|
-
["quoted identifier case changes", `"SELECT" = true`, `"select" = true`],
|
|
484
|
-
[
|
|
485
|
-
"cast target type changes",
|
|
486
|
-
`cast(user_id as integer) = 1`,
|
|
487
|
-
`cast(user_id as bigint) = 1`,
|
|
488
|
-
],
|
|
489
|
-
[
|
|
490
|
-
"unsupported PostgreSQL predicate syntax",
|
|
491
|
-
"deleted_at IS DISTINCT FROM NULL",
|
|
492
|
-
"created_at IS DISTINCT FROM NULL",
|
|
493
|
-
],
|
|
494
|
-
[
|
|
495
|
-
"unsupported PostgreSQL predicate syntax is compared with parseable syntax",
|
|
496
|
-
"deleted_at IS DISTINCT FROM NULL",
|
|
497
|
-
"deleted_at IS NULL",
|
|
498
|
-
],
|
|
499
|
-
[
|
|
500
|
-
"parser fallback still fails after compatibility rewrites",
|
|
501
|
-
"note = $$::character varying$$ AND ((select current_setting('app.tenant_id', true)::character varying) = tenant_id)",
|
|
502
|
-
"note = $tag$::character varying$tag$ AND ((select current_setting('app.tenant_id', true)::character varying) = tenant_id)",
|
|
503
|
-
],
|
|
504
|
-
[
|
|
505
|
-
"string literal text inside parser fallback expressions changes",
|
|
506
|
-
`note = '::character varying' AND ((select current_setting('app.tenant_id', true)::character varying) = tenant_id)`,
|
|
507
|
-
`note = '::varchar' AND (( SELECT current_setting('app.tenant_id'::text, true)::character varying) = tenant_id)`,
|
|
508
|
-
],
|
|
509
|
-
[
|
|
510
|
-
"E-string literal text inside parser fallback expressions changes",
|
|
511
|
-
`note = E'foo\\'::character varying' AND ((select current_setting('app.tenant_id', true)::character varying) = tenant_id)`,
|
|
512
|
-
`note = E'foo\\'::varchar' AND (( SELECT current_setting('app.tenant_id'::text, true)::character varying) = tenant_id)`,
|
|
513
|
-
],
|
|
514
|
-
[
|
|
515
|
-
"quoted text-like type casts change",
|
|
516
|
-
`is_allowed('x'::"Text")`,
|
|
517
|
-
`is_allowed('x')`,
|
|
518
|
-
],
|
|
519
|
-
[
|
|
520
|
-
"large integer literal changes",
|
|
521
|
-
"id = 9007199254740992",
|
|
522
|
-
"id = 9007199254740993",
|
|
523
|
-
],
|
|
524
|
-
])("recreates changed explicit policies when %s", async (_name, using, databaseQual) => {
|
|
525
|
-
let WorkspaceMemberWithChangedExplicitPolicy = class WorkspaceMemberWithChangedExplicitPolicy {
|
|
526
|
-
};
|
|
527
|
-
WorkspaceMemberWithChangedExplicitPolicy = __decorate([
|
|
528
|
-
(0, policy_decorator_1.Policy)({
|
|
529
|
-
name: "workspace_member_user_select_policy",
|
|
530
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
531
|
-
using,
|
|
532
|
-
})
|
|
533
|
-
], WorkspaceMemberWithChangedExplicitPolicy);
|
|
534
|
-
const file = await generateFieldChangeMigrationFile(WorkspaceMemberWithChangedExplicitPolicy, databaseQual);
|
|
535
|
-
expect(file).toContain("workspace_member_user_select_policy");
|
|
536
|
-
});
|
|
537
|
-
it("skips database policy lookup when metadata has no entity classes", async () => {
|
|
538
|
-
const execute = jest.fn();
|
|
539
|
-
jest
|
|
540
|
-
.spyOn(migrations_1.TSMigrationGenerator.prototype, "generate")
|
|
541
|
-
.mockResolvedValue(["migration-file", "/tmp/Migration.ts"]);
|
|
542
|
-
const generator = createGenerator([
|
|
543
|
-
{
|
|
544
|
-
tableName: "workspace_member",
|
|
545
|
-
},
|
|
546
|
-
], {
|
|
547
|
-
getConnection: () => ({
|
|
548
|
-
execute,
|
|
549
|
-
}),
|
|
550
|
-
});
|
|
551
|
-
await generator.generate({ up: [], down: [] });
|
|
552
|
-
expect(execute).not.toHaveBeenCalled();
|
|
553
|
-
});
|
|
554
|
-
it("only diffs existing database policies for tables managed by Policy metadata", async () => {
|
|
555
|
-
const execute = jest.fn((_sql) => Promise.resolve([
|
|
556
|
-
{
|
|
557
|
-
policy_name: "unmanaged_policy",
|
|
558
|
-
schema_name: "public",
|
|
559
|
-
table_name: "unmanaged_entity",
|
|
560
|
-
permissive: true,
|
|
561
|
-
command: "r",
|
|
562
|
-
qual: "true",
|
|
563
|
-
with_check: null,
|
|
564
|
-
roles: ["authenticated"],
|
|
565
|
-
},
|
|
566
|
-
]));
|
|
567
|
-
jest
|
|
568
|
-
.spyOn(migrations_1.TSMigrationGenerator.prototype, "generate")
|
|
569
|
-
.mockImplementation(function () {
|
|
570
|
-
expect(this.existingPolicyDefinitions).toEqual([]);
|
|
571
|
-
expect(this.existingPolicyRoleDefinitions).toEqual([
|
|
572
|
-
expect.objectContaining({
|
|
573
|
-
policyName: "unmanaged_policy",
|
|
574
|
-
}),
|
|
575
|
-
]);
|
|
576
|
-
return Promise.resolve(["migration-file", "/tmp/Migration.ts"]);
|
|
577
|
-
});
|
|
578
|
-
const generator = createGenerator([
|
|
579
|
-
{
|
|
580
|
-
class: WorkspaceMember,
|
|
581
|
-
tableName: "workspace_member",
|
|
582
|
-
},
|
|
583
|
-
{
|
|
584
|
-
class: UnmanagedEntity,
|
|
585
|
-
tableName: "unmanaged_entity",
|
|
586
|
-
},
|
|
587
|
-
], {
|
|
588
|
-
getConnection: () => ({
|
|
589
|
-
execute,
|
|
590
|
-
}),
|
|
591
|
-
});
|
|
592
|
-
await generator.generate({ up: [], down: [] });
|
|
593
|
-
expect(execute.mock.calls[0]?.[0]).toContain("FROM pg_policy");
|
|
594
|
-
});
|
|
595
|
-
it("throws when MikroORM metadata is unavailable", () => {
|
|
596
|
-
const generator = new row_level_security_migration_generator_1.RowLevelSecurityMigrationGenerator({}, {}, { emit: "ts" });
|
|
597
|
-
expect(() => generator.generateMigrationFile("MigrationTest", {
|
|
598
|
-
up: [],
|
|
599
|
-
down: [],
|
|
600
|
-
})).toThrow("MikroORM metadata storage is not available");
|
|
601
|
-
});
|
|
602
|
-
it("throws when a policy entity does not expose a table name", () => {
|
|
603
|
-
const generator = createGenerator([
|
|
604
|
-
{
|
|
605
|
-
class: WorkspaceMember,
|
|
606
|
-
},
|
|
607
|
-
]);
|
|
608
|
-
expect(() => generator.generateMigrationFile("MigrationTest", {
|
|
609
|
-
up: [],
|
|
610
|
-
down: [],
|
|
611
|
-
})).toThrow("Policy entity WorkspaceMember does not have a table name");
|
|
612
|
-
});
|
|
613
|
-
it("generates RowLevelSecurityMigration files with policy up and down SQL", () => {
|
|
614
|
-
const generator = createGenerator([
|
|
615
|
-
{
|
|
616
|
-
class: WorkspaceMember,
|
|
617
|
-
schema: "*",
|
|
618
|
-
tableName: "workspace_member",
|
|
619
|
-
},
|
|
620
|
-
]);
|
|
621
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
622
|
-
up: ['create table "workspace_member" ("user_id" bigint null);'],
|
|
623
|
-
down: ['drop table if exists "workspace_member" cascade;'],
|
|
624
|
-
});
|
|
625
|
-
expect(file).toContain("import { RowLevelSecurityMigration } from '@nest-boot/row-level-security';");
|
|
626
|
-
expect(file).toContain("extends RowLevelSecurityMigration");
|
|
627
|
-
expect(file.indexOf("create policy workspace_member_user_select_policy")).toBeGreaterThan(file.indexOf('create table "workspace_member"'));
|
|
628
|
-
expect(file.indexOf("drop policy if exists workspace_member_user_select_policy")).toBeLessThan(file.indexOf('drop table if exists "workspace_member" cascade;'));
|
|
629
|
-
});
|
|
630
|
-
it("throws when MikroORM migration output cannot be converted to the RLS base class", () => {
|
|
631
|
-
jest
|
|
632
|
-
.spyOn(migrations_1.TSMigrationGenerator.prototype, "generateMigrationFile")
|
|
633
|
-
.mockReturnValue("export class MigrationTest {}");
|
|
634
|
-
const generator = createGenerator([
|
|
635
|
-
{
|
|
636
|
-
class: WorkspaceMember,
|
|
637
|
-
tableName: "workspace_member",
|
|
638
|
-
},
|
|
639
|
-
]);
|
|
640
|
-
expect(() => generator.generateMigrationFile("MigrationTest", {
|
|
641
|
-
up: [],
|
|
642
|
-
down: [],
|
|
643
|
-
})).toThrow("MikroORM migration output format is not supported");
|
|
644
|
-
});
|
|
645
|
-
it("generates policy SQL for all policy entities in a blank migration", () => {
|
|
646
|
-
const generator = createGenerator([
|
|
647
|
-
{
|
|
648
|
-
class: WorkspaceMember,
|
|
649
|
-
tableName: "workspace_member",
|
|
650
|
-
},
|
|
651
|
-
]);
|
|
652
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
653
|
-
up: [],
|
|
654
|
-
down: [],
|
|
655
|
-
});
|
|
656
|
-
expect(file).toContain("extends RowLevelSecurityMigration");
|
|
657
|
-
expect(file).not.toContain("create schema if not exists app");
|
|
658
|
-
expect(file).not.toContain("grant usage on schema app");
|
|
659
|
-
expect(file).not.toContain("create role anonymous");
|
|
660
|
-
expect(file).not.toContain("grant anonymous to current_user");
|
|
661
|
-
expect(file).not.toContain("app.get_context");
|
|
662
|
-
expect(file).not.toContain("get_tenant_id()");
|
|
663
|
-
expect(file).not.toContain("get_policy_context");
|
|
664
|
-
expect(file).not.toContain("create schema if not exists extensions");
|
|
665
|
-
expect(file).not.toContain("grant usage on schema extensions");
|
|
666
|
-
expect(file).toContain('this.addSql(`alter table "public"."workspace_member" enable row level security;`);');
|
|
667
|
-
expect(file).toContain('this.addSql(`drop policy if exists workspace_member_user_select_policy on "public"."workspace_member";`);');
|
|
668
|
-
expect(file).toContain('this.addSql(`create policy workspace_member_user_select_policy on "public"."workspace_member" as permissive for select using ((select current_setting(\'app.user_id\', true)::bigint) = "user_id");`);');
|
|
669
|
-
});
|
|
670
|
-
it("grants policy privileges for custom policy roles without role bootstrap SQL", () => {
|
|
671
|
-
const generator = createGenerator([
|
|
672
|
-
{
|
|
673
|
-
class: WorkspaceMemberWithCustomRole,
|
|
674
|
-
tableName: "workspace_member",
|
|
675
|
-
},
|
|
676
|
-
]);
|
|
677
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
678
|
-
up: [],
|
|
679
|
-
down: [],
|
|
680
|
-
});
|
|
681
|
-
expect(file).not.toContain("grant usage on schema app");
|
|
682
|
-
expect(file).not.toContain("create role anonymous");
|
|
683
|
-
expect(file).not.toContain("create role workspace_admin");
|
|
684
|
-
expect(file).not.toContain("grant anonymous to current_user");
|
|
685
|
-
expect(file).not.toContain("grant workspace_admin to current_user");
|
|
686
|
-
expect(file).toContain('this.addSql(`grant select on table "public"."workspace_member" to workspace_admin;`);');
|
|
687
|
-
expect(file).toContain('this.addSql(`revoke select on table "public"."workspace_member" from workspace_admin;`);');
|
|
688
|
-
expect(file).not.toContain("revoke usage on schema app");
|
|
689
|
-
expect(file).not.toContain("revoke workspace_admin from current_user");
|
|
690
|
-
expect(file).not.toContain("drop role workspace_admin");
|
|
691
|
-
});
|
|
692
|
-
it("keeps grants required by pre-existing policies when rolling back added policies", () => {
|
|
693
|
-
const generator = createGenerator([
|
|
694
|
-
{
|
|
695
|
-
class: WorkspaceMemberWithSharedRolePolicies,
|
|
696
|
-
tableName: "workspace_member",
|
|
697
|
-
},
|
|
698
|
-
]);
|
|
699
|
-
generator.existingPolicyDefinitions = [
|
|
700
|
-
{
|
|
701
|
-
entityName: "WorkspaceMemberWithSharedRolePolicies",
|
|
702
|
-
schemaName: "public",
|
|
703
|
-
tableName: "workspace_member",
|
|
704
|
-
policyName: "workspace_member_admin_select_policy",
|
|
705
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
706
|
-
using: "true",
|
|
707
|
-
roles: ["workspace_admin"],
|
|
708
|
-
},
|
|
709
|
-
];
|
|
710
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
711
|
-
up: [],
|
|
712
|
-
down: [],
|
|
713
|
-
});
|
|
714
|
-
expect(file).toContain("drop policy if exists workspace_member_admin_update_policy");
|
|
715
|
-
expect(file).toContain('this.addSql(`revoke update on table "public"."workspace_member" from workspace_admin;`);');
|
|
716
|
-
expect(file).not.toContain('this.addSql(`revoke select, update on table "public"."workspace_member" from workspace_admin;`);');
|
|
717
|
-
expect(file).not.toContain("this.addSql(`revoke usage on schema app from anonymous;`);");
|
|
718
|
-
expect(file).not.toContain("this.addSql(`revoke anonymous from current_user;`);");
|
|
719
|
-
expect(file).not.toContain("this.addSql(`revoke usage on schema app from workspace_admin;`);");
|
|
720
|
-
expect(file).not.toContain("this.addSql(`revoke workspace_admin from current_user;`);");
|
|
721
|
-
});
|
|
722
|
-
it("keeps schema grants required by policies outside the generated diff", () => {
|
|
723
|
-
const generator = createGenerator([
|
|
724
|
-
{
|
|
725
|
-
class: WorkspaceMemberWithCustomRole,
|
|
726
|
-
tableName: "workspace_member",
|
|
727
|
-
},
|
|
728
|
-
]);
|
|
729
|
-
generator.existingPolicyDefinitions = [];
|
|
730
|
-
generator.existingPolicyRoleDefinitions = [
|
|
731
|
-
{
|
|
732
|
-
entityName: "ExternalPolicy",
|
|
733
|
-
schemaName: "external",
|
|
734
|
-
tableName: "workspace",
|
|
735
|
-
policyName: "external_workspace_policy",
|
|
736
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
737
|
-
using: "true",
|
|
738
|
-
roles: ["workspace_admin"],
|
|
739
|
-
},
|
|
740
|
-
];
|
|
741
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
742
|
-
up: [],
|
|
743
|
-
down: [],
|
|
744
|
-
});
|
|
745
|
-
expect(file).toContain('this.addSql(`revoke select on table "public"."workspace_member" from workspace_admin;`);');
|
|
746
|
-
expect(file).not.toContain("this.addSql(`revoke usage on schema app from workspace_admin;`);");
|
|
747
|
-
expect(file).not.toContain("this.addSql(`revoke workspace_admin from current_user;`);");
|
|
748
|
-
});
|
|
749
|
-
it("handles removed policies when an existing database policy is no longer declared", () => {
|
|
750
|
-
const generator = createGenerator([
|
|
751
|
-
{
|
|
752
|
-
class: WorkspaceMember,
|
|
753
|
-
tableName: "workspace_member",
|
|
754
|
-
},
|
|
755
|
-
]);
|
|
756
|
-
generator.existingPolicyDefinitions = [
|
|
757
|
-
{
|
|
758
|
-
entityName: "WorkspaceMember",
|
|
759
|
-
schemaName: "public",
|
|
760
|
-
tableName: "workspace_member",
|
|
761
|
-
policyName: "removed_workspace_member_policy",
|
|
762
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
763
|
-
using: "true",
|
|
764
|
-
roles: ["authenticated"],
|
|
765
|
-
},
|
|
766
|
-
];
|
|
767
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
768
|
-
up: ['alter table "workspace_member" add column "display_name" text;'],
|
|
769
|
-
down: ['alter table "workspace_member" drop column "display_name";'],
|
|
770
|
-
});
|
|
771
|
-
expect(file).toContain("drop policy if exists removed_workspace_member_policy");
|
|
772
|
-
expect(file).toContain("create policy workspace_member_user_select_policy");
|
|
773
|
-
expect(file).toContain("create policy removed_workspace_member_policy");
|
|
774
|
-
expect(file).toContain("create policy removed_workspace_member_policy on");
|
|
775
|
-
expect(file).toContain("for select to authenticated using (true)");
|
|
776
|
-
});
|
|
777
|
-
it("recreates policies when an existing policy changes content", () => {
|
|
778
|
-
const generator = createGenerator([
|
|
779
|
-
{
|
|
780
|
-
class: WorkspaceMember,
|
|
781
|
-
tableName: "workspace_member",
|
|
782
|
-
},
|
|
783
|
-
]);
|
|
784
|
-
generator.existingPolicyDefinitions = [
|
|
785
|
-
{
|
|
786
|
-
entityName: "WorkspaceMember",
|
|
787
|
-
schemaName: "public",
|
|
788
|
-
tableName: "workspace_member",
|
|
789
|
-
policyName: "workspace_member_user_select_policy",
|
|
790
|
-
command: policy_command_enum_1.PolicyCommand.SELECT,
|
|
791
|
-
using: "false",
|
|
792
|
-
roles: ["authenticated"],
|
|
793
|
-
},
|
|
794
|
-
];
|
|
795
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
796
|
-
up: ['alter table "workspace_member" add column "display_name" text;'],
|
|
797
|
-
down: ['alter table "workspace_member" drop column "display_name";'],
|
|
798
|
-
});
|
|
799
|
-
expect(file).toContain("drop policy if exists workspace_member_user_select_policy");
|
|
800
|
-
expect(file).toContain("create policy workspace_member_user_select_policy on");
|
|
801
|
-
expect(file).toContain("using ((select current_setting('app.user_id', true)::bigint) = \"user_id\")");
|
|
802
|
-
expect(file).toContain("for select to authenticated using (false)");
|
|
803
|
-
});
|
|
804
|
-
it("matches create table statements for non-public schemas and collection names", () => {
|
|
805
|
-
const generator = createGenerator([
|
|
806
|
-
{
|
|
807
|
-
class: AuditLog,
|
|
808
|
-
schema: "app",
|
|
809
|
-
collection: "audit_log",
|
|
810
|
-
},
|
|
811
|
-
]);
|
|
812
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
813
|
-
up: ['create table if not exists "app"."audit_log" ("id" bigint);'],
|
|
814
|
-
down: ['drop table if exists "app"."audit_log";'],
|
|
815
|
-
});
|
|
816
|
-
expect(file).toContain('this.addSql(`create policy audit_log_select_policy on "app"."audit_log" as permissive for select using (true);`);');
|
|
817
|
-
});
|
|
818
|
-
it("generates all policies declared on an entity", () => {
|
|
819
|
-
const generator = createGenerator([
|
|
820
|
-
{
|
|
821
|
-
class: WorkspaceMemberWithMultiplePolicies,
|
|
822
|
-
tableName: "workspace_member",
|
|
823
|
-
},
|
|
824
|
-
]);
|
|
825
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
826
|
-
up: [],
|
|
827
|
-
down: [],
|
|
828
|
-
});
|
|
829
|
-
expect(file).toContain('this.addSql(`create policy workspace_member_write_policy on "public"."workspace_member" as permissive for all using ((select current_setting(\'app.workspace_id\', true)::bigint) = "workspace_id") with check ((select current_setting(\'app.workspace_id\', true)::bigint) = "workspace_id");`);');
|
|
830
|
-
expect(file).toContain('this.addSql(`create policy workspace_member_user_select_policy on "public"."workspace_member" as permissive for select using ((select current_setting(\'app.user_id\', true)::bigint) = "user_id");`);');
|
|
831
|
-
});
|
|
832
|
-
it("generates property context policy SQL from entity metadata", () => {
|
|
833
|
-
const generator = createGenerator([
|
|
834
|
-
{
|
|
835
|
-
class: WorkspaceMemberWithGeneratedPolicy,
|
|
836
|
-
tableName: "workspace_member",
|
|
837
|
-
properties: {
|
|
838
|
-
user: {
|
|
839
|
-
fieldNames: ["user_id"],
|
|
840
|
-
columnTypes: ["integer"],
|
|
841
|
-
targetMeta: {
|
|
842
|
-
primaryKeys: ["id"],
|
|
843
|
-
properties: {
|
|
844
|
-
id: {
|
|
845
|
-
columnTypes: ["bigint"],
|
|
846
|
-
},
|
|
847
|
-
},
|
|
848
|
-
},
|
|
849
|
-
},
|
|
850
|
-
},
|
|
851
|
-
},
|
|
852
|
-
]);
|
|
853
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
854
|
-
up: [],
|
|
855
|
-
down: [],
|
|
856
|
-
});
|
|
857
|
-
expect(file).toContain('this.addSql(`create policy workspace_member_user_select_policy on "public"."workspace_member" as permissive for select using ((select current_setting(\'app.user_id\', true)::bigint) = user_id);`);');
|
|
858
|
-
});
|
|
859
|
-
it("shortens generated policy names longer than PostgreSQL identifier limit", () => {
|
|
860
|
-
const generator = createGenerator([
|
|
861
|
-
{
|
|
862
|
-
class: WorkspaceMemberGroupMemberWithLongGeneratedPolicy,
|
|
863
|
-
tableName: "workspace_member_group_member",
|
|
864
|
-
properties: {
|
|
865
|
-
workspace: {
|
|
866
|
-
fieldNames: ["workspace_id"],
|
|
867
|
-
columnTypes: ["bigint"],
|
|
868
|
-
},
|
|
869
|
-
},
|
|
870
|
-
},
|
|
871
|
-
]);
|
|
872
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
873
|
-
up: [],
|
|
874
|
-
down: [],
|
|
875
|
-
});
|
|
876
|
-
expect(LONG_GENERATED_POLICY_NAME_SHORTENED).toHaveLength(62);
|
|
877
|
-
expect(file).toContain(`create policy ${LONG_GENERATED_POLICY_NAME_SHORTENED}`);
|
|
878
|
-
expect(file).toContain(`drop policy if exists ${LONG_GENERATED_POLICY_NAME_SHORTENED}`);
|
|
879
|
-
expect(file).not.toContain(LONG_GENERATED_POLICY_NAME);
|
|
880
|
-
});
|
|
881
|
-
it("canonicalizes generated policy context type aliases", () => {
|
|
882
|
-
const generator = createGenerator([
|
|
883
|
-
{
|
|
884
|
-
class: WorkspaceMemberWithWorkspaceContextPolicy,
|
|
885
|
-
tableName: "workspace_member",
|
|
886
|
-
properties: {
|
|
887
|
-
workspace: {
|
|
888
|
-
fieldNames: ["workspace_id"],
|
|
889
|
-
columnTypes: ["int"],
|
|
890
|
-
},
|
|
891
|
-
},
|
|
892
|
-
},
|
|
893
|
-
]);
|
|
894
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
895
|
-
up: [],
|
|
896
|
-
down: [],
|
|
897
|
-
});
|
|
898
|
-
expect(file).toContain("current_setting('app.workspace_id', true)::integer");
|
|
899
|
-
});
|
|
900
|
-
it("preserves quoted PostgreSQL keyword column names in generated policies", () => {
|
|
901
|
-
const generator = createGenerator([
|
|
902
|
-
{
|
|
903
|
-
class: WorkspaceMemberWithWorkspaceContextPolicy,
|
|
904
|
-
tableName: "workspace_member",
|
|
905
|
-
properties: {
|
|
906
|
-
workspace: {
|
|
907
|
-
fieldNames: ["between"],
|
|
908
|
-
columnTypes: ["integer"],
|
|
909
|
-
},
|
|
910
|
-
},
|
|
911
|
-
},
|
|
912
|
-
]);
|
|
913
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
914
|
-
up: [],
|
|
915
|
-
down: [],
|
|
916
|
-
});
|
|
917
|
-
expect(file).toContain(`using ((select current_setting('app.workspace_id', true)::integer) = "between")`);
|
|
918
|
-
});
|
|
919
|
-
it("keeps default MikroORM output for unrelated schema changes", () => {
|
|
920
|
-
const generator = createGenerator([
|
|
921
|
-
{
|
|
922
|
-
class: WorkspaceMember,
|
|
923
|
-
tableName: "workspace_member",
|
|
924
|
-
},
|
|
925
|
-
]);
|
|
926
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
927
|
-
up: [
|
|
928
|
-
'alter table "workspace_member" add column "display_name" text null;',
|
|
929
|
-
],
|
|
930
|
-
down: ['alter table "workspace_member" drop column "display_name";'],
|
|
931
|
-
});
|
|
932
|
-
expect(file).toContain("import { Migration } from '@mikro-orm/migrations';");
|
|
933
|
-
expect(file).toContain("extends Migration");
|
|
934
|
-
expect(file).not.toContain("workspace_member_user_select_policy");
|
|
935
|
-
});
|
|
936
|
-
it("keeps default MikroORM output when no entities use Policy", () => {
|
|
937
|
-
const generator = createGenerator([]);
|
|
938
|
-
const file = generator.generateMigrationFile("MigrationTest", {
|
|
939
|
-
up: ["select 1;"],
|
|
940
|
-
down: [],
|
|
941
|
-
});
|
|
942
|
-
expect(file).toContain("import { Migration } from '@mikro-orm/migrations';");
|
|
943
|
-
expect(file).toContain("extends Migration");
|
|
944
|
-
});
|
|
945
|
-
it("is a MikroORM TypeScript migration generator", () => {
|
|
946
|
-
expect(row_level_security_migration_generator_1.RowLevelSecurityMigrationGenerator.prototype).toBeInstanceOf(migrations_1.TSMigrationGenerator);
|
|
947
|
-
});
|
|
948
|
-
});
|
|
949
|
-
describe("RowLevelSecurityMigrator", () => {
|
|
950
|
-
afterEach(() => {
|
|
951
|
-
jest.restoreAllMocks();
|
|
952
|
-
});
|
|
953
|
-
it("creates a migration when only row-level-security policies changed", async () => {
|
|
954
|
-
const diff = { up: [], down: [] };
|
|
955
|
-
const generator = createGenerator([]);
|
|
956
|
-
const hasPendingPolicyChanges = jest
|
|
957
|
-
.spyOn(generator, "hasPendingPolicyChanges")
|
|
958
|
-
.mockResolvedValue(true);
|
|
959
|
-
const generate = jest
|
|
960
|
-
.spyOn(generator, "generate")
|
|
961
|
-
.mockResolvedValue(["migration-file", "Migration.ts"]);
|
|
962
|
-
const storeCurrentSchema = jest.fn();
|
|
963
|
-
const migrator = Object.assign(Object.create(row_level_security_migrator_1.RowLevelSecurityMigrator.prototype), {
|
|
964
|
-
generator,
|
|
965
|
-
ensureMigrationsDirExists: jest.fn(),
|
|
966
|
-
getSchemaDiff: jest.fn().mockResolvedValue(diff),
|
|
967
|
-
storeCurrentSchema,
|
|
968
|
-
});
|
|
969
|
-
await expect(migrator.createMigration("/tmp", false, false, "Migration")).resolves.toEqual({
|
|
970
|
-
fileName: "Migration.ts",
|
|
971
|
-
code: "migration-file",
|
|
972
|
-
diff,
|
|
973
|
-
});
|
|
974
|
-
expect(hasPendingPolicyChanges).toHaveBeenCalledWith(diff);
|
|
975
|
-
expect(generate).toHaveBeenCalledWith(diff, "/tmp", "Migration");
|
|
976
|
-
expect(storeCurrentSchema).toHaveBeenCalledTimes(1);
|
|
977
|
-
});
|
|
978
|
-
it("generates a policy diff after an applied context policy changes", async () => {
|
|
979
|
-
const migrationPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "rls-policy-diff-"));
|
|
980
|
-
const diff = { up: [], down: [] };
|
|
981
|
-
const execute = jest.fn((_sql) => Promise.resolve([
|
|
982
|
-
{
|
|
983
|
-
policy_name: "workspace_member_workspace_select_policy",
|
|
984
|
-
schema_name: "public",
|
|
985
|
-
table_name: "workspace_member",
|
|
986
|
-
permissive: true,
|
|
987
|
-
command: "r",
|
|
988
|
-
qual: "(( SELECT current_setting('app.tenant_id'::text, true)::bigint) = workspace_id)",
|
|
989
|
-
with_check: null,
|
|
990
|
-
roles: ["authenticated"],
|
|
991
|
-
},
|
|
992
|
-
]));
|
|
993
|
-
const generator = createGenerator([
|
|
994
|
-
{
|
|
995
|
-
class: WorkspaceMemberWithWorkspaceContextPolicy,
|
|
996
|
-
tableName: "workspace_member",
|
|
997
|
-
properties: {
|
|
998
|
-
workspace: {
|
|
999
|
-
fieldNames: ["workspace_id"],
|
|
1000
|
-
columnTypes: ["bigint"],
|
|
1001
|
-
},
|
|
1002
|
-
},
|
|
1003
|
-
},
|
|
1004
|
-
], {
|
|
1005
|
-
getConnection: () => ({
|
|
1006
|
-
execute,
|
|
1007
|
-
}),
|
|
1008
|
-
}, {
|
|
1009
|
-
path: migrationPath,
|
|
1010
|
-
pathTs: migrationPath,
|
|
1011
|
-
});
|
|
1012
|
-
const storeCurrentSchema = jest.fn();
|
|
1013
|
-
const migrator = Object.assign(Object.create(row_level_security_migrator_1.RowLevelSecurityMigrator.prototype), {
|
|
1014
|
-
generator,
|
|
1015
|
-
ensureMigrationsDirExists: jest.fn(),
|
|
1016
|
-
getSchemaDiff: jest.fn().mockResolvedValue(diff),
|
|
1017
|
-
storeCurrentSchema,
|
|
1018
|
-
});
|
|
1019
|
-
try {
|
|
1020
|
-
const result = await migrator.createMigration(migrationPath, false, false, "MigrationPolicyContextDiff");
|
|
1021
|
-
expect(result.fileName).toBe("MigrationPolicyContextDiff.ts");
|
|
1022
|
-
expect(result.diff).toBe(diff);
|
|
1023
|
-
expect(result.code).toContain("extends RowLevelSecurityMigration");
|
|
1024
|
-
expect(result.code).toContain("drop policy if exists workspace_member_workspace_select_policy");
|
|
1025
|
-
expect(result.code).toContain("current_setting('app.workspace_id', true)::bigint");
|
|
1026
|
-
expect(result.code).toContain("current_setting('app.tenant_id'::text, true)::bigint");
|
|
1027
|
-
expect(result.code.indexOf("drop policy if exists workspace_member_workspace_select_policy")).toBeLessThan(result.code.indexOf("create policy workspace_member_workspace_select_policy"));
|
|
1028
|
-
expect(storeCurrentSchema).toHaveBeenCalledTimes(1);
|
|
1029
|
-
}
|
|
1030
|
-
finally {
|
|
1031
|
-
(0, node_fs_1.rmSync)(migrationPath, { force: true, recursive: true });
|
|
1032
|
-
}
|
|
1033
|
-
});
|
|
1034
|
-
it("keeps MikroORM no-op behavior when schema and policies are unchanged", async () => {
|
|
1035
|
-
const diff = { up: [], down: [] };
|
|
1036
|
-
const generator = createGenerator([]);
|
|
1037
|
-
jest.spyOn(generator, "hasPendingPolicyChanges").mockResolvedValue(false);
|
|
1038
|
-
const generate = jest.spyOn(generator, "generate");
|
|
1039
|
-
const storeCurrentSchema = jest.fn();
|
|
1040
|
-
const migrator = Object.assign(Object.create(row_level_security_migrator_1.RowLevelSecurityMigrator.prototype), {
|
|
1041
|
-
generator,
|
|
1042
|
-
ensureMigrationsDirExists: jest.fn(),
|
|
1043
|
-
getSchemaDiff: jest.fn().mockResolvedValue(diff),
|
|
1044
|
-
storeCurrentSchema,
|
|
1045
|
-
});
|
|
1046
|
-
await expect(migrator.createMigration()).resolves.toEqual({
|
|
1047
|
-
fileName: "",
|
|
1048
|
-
code: "",
|
|
1049
|
-
diff,
|
|
1050
|
-
});
|
|
1051
|
-
expect(generate).not.toHaveBeenCalled();
|
|
1052
|
-
expect(storeCurrentSchema).not.toHaveBeenCalled();
|
|
1053
|
-
});
|
|
1054
|
-
it("reports migration needed when only row-level-security policies changed", async () => {
|
|
1055
|
-
const diff = { up: [], down: [] };
|
|
1056
|
-
const generator = createGenerator([]);
|
|
1057
|
-
jest.spyOn(generator, "hasPendingPolicyChanges").mockResolvedValue(true);
|
|
1058
|
-
const migrator = Object.assign(Object.create(row_level_security_migrator_1.RowLevelSecurityMigrator.prototype), {
|
|
1059
|
-
generator,
|
|
1060
|
-
ensureMigrationsDirExists: jest.fn(),
|
|
1061
|
-
getSchemaDiff: jest.fn().mockResolvedValue(diff),
|
|
1062
|
-
});
|
|
1063
|
-
await expect(migrator.checkMigrationNeeded()).resolves.toBe(true);
|
|
1064
|
-
});
|
|
1065
|
-
it("reports migration needed when MikroORM schema diff has up statements", async () => {
|
|
1066
|
-
const diff = { up: ["create table workspace (id int);"], down: [] };
|
|
1067
|
-
const generator = createGenerator([]);
|
|
1068
|
-
const hasPendingPolicyChanges = jest.spyOn(generator, "hasPendingPolicyChanges");
|
|
1069
|
-
const migrator = Object.assign(Object.create(row_level_security_migrator_1.RowLevelSecurityMigrator.prototype), {
|
|
1070
|
-
generator,
|
|
1071
|
-
ensureMigrationsDirExists: jest.fn(),
|
|
1072
|
-
getSchemaDiff: jest.fn().mockResolvedValue(diff),
|
|
1073
|
-
});
|
|
1074
|
-
await expect(migrator.checkMigrationNeeded()).resolves.toBe(true);
|
|
1075
|
-
expect(hasPendingPolicyChanges).not.toHaveBeenCalled();
|
|
1076
|
-
});
|
|
1077
|
-
});
|
|
1078
|
-
function createGenerator(metadata, driverOptions = {}, generatorOptions = {}) {
|
|
1079
|
-
return new row_level_security_migration_generator_1.RowLevelSecurityMigrationGenerator({
|
|
1080
|
-
config: {
|
|
1081
|
-
get: (key) => (key === "baseDir" ? process.cwd() : undefined),
|
|
1082
|
-
getMetadata: () => ({
|
|
1083
|
-
getAll: () => metadata,
|
|
1084
|
-
}),
|
|
1085
|
-
},
|
|
1086
|
-
...driverOptions,
|
|
1087
|
-
}, {
|
|
1088
|
-
classToMigrationName: (_timestamp, name) => name ?? "Migration",
|
|
1089
|
-
}, {
|
|
1090
|
-
emit: "ts",
|
|
1091
|
-
fileName: (_timestamp, name) => name ?? "Migration",
|
|
1092
|
-
...generatorOptions,
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
function mockBaseMigrationGenerate(diff = WORKSPACE_MEMBER_FIELD_CHANGE_DIFF) {
|
|
1096
|
-
return jest
|
|
1097
|
-
.spyOn(migrations_1.TSMigrationGenerator.prototype, "generate")
|
|
1098
|
-
.mockImplementation(function () {
|
|
1099
|
-
return Promise.resolve([
|
|
1100
|
-
this.generateMigrationFile("MigrationTest", diff),
|
|
1101
|
-
"/tmp/Migration.ts",
|
|
1102
|
-
]);
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
function createExistingPolicyLookup(databaseQual) {
|
|
1106
|
-
return jest.fn((_sql) => Promise.resolve([
|
|
1107
|
-
{
|
|
1108
|
-
policy_name: "workspace_member_user_select_policy",
|
|
1109
|
-
schema_name: "public",
|
|
1110
|
-
table_name: "workspace_member",
|
|
1111
|
-
permissive: true,
|
|
1112
|
-
command: "r",
|
|
1113
|
-
qual: databaseQual,
|
|
1114
|
-
with_check: null,
|
|
1115
|
-
roles: [],
|
|
1116
|
-
},
|
|
1117
|
-
]));
|
|
1118
|
-
}
|
|
1119
|
-
async function generateFieldChangeMigrationFile(policyClass, databaseQual) {
|
|
1120
|
-
const execute = createExistingPolicyLookup(databaseQual);
|
|
1121
|
-
const generator = createGenerator([
|
|
1122
|
-
{
|
|
1123
|
-
class: policyClass,
|
|
1124
|
-
tableName: "workspace_member",
|
|
1125
|
-
},
|
|
1126
|
-
], {
|
|
1127
|
-
getConnection: () => ({
|
|
1128
|
-
execute,
|
|
1129
|
-
}),
|
|
1130
|
-
});
|
|
1131
|
-
mockBaseMigrationGenerate();
|
|
1132
|
-
const [file] = await generator.generate(WORKSPACE_MEMBER_FIELD_CHANGE_DIFF);
|
|
1133
|
-
return file;
|
|
1134
|
-
}
|
|
1135
|
-
//# sourceMappingURL=row-level-security-migration-generator.spec.js.map
|