data-primals-engine 1.6.0 → 1.6.1
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/README.md +21 -7
- package/client/src/DataTable.jsx +1 -1
- package/client/src/ModelCreator.jsx +683 -686
- package/client/src/ModelCreator.scss +1 -1
- package/client/src/ModelImporter.jsx +3 -0
- package/package.json +1 -1
- package/src/defaultModels.js +7 -0
- package/src/index.js +1 -0
- package/src/modules/data/data.operations.js +3484 -3401
- package/src/modules/user.js +14 -9
- package/test/data.integration.test.js +75 -0
- package/test/user.test.js +106 -1
package/src/modules/user.js
CHANGED
|
@@ -108,7 +108,7 @@ export async function onInit(defaultEngine) {
|
|
|
108
108
|
* @returns {Promise<Set<string>>} Un Set contenant les noms de toutes les permissions actives.
|
|
109
109
|
* @private
|
|
110
110
|
*/
|
|
111
|
-
export async function getUserActivePermissions(user) {
|
|
111
|
+
export async function getUserActivePermissions(user, env = null) {
|
|
112
112
|
const datasCollection = await getCollectionForUser(user);
|
|
113
113
|
const now = new Date();
|
|
114
114
|
const activePermissions = new Set();
|
|
@@ -139,12 +139,17 @@ export async function getUserActivePermissions(user) {
|
|
|
139
139
|
// --- ÉTAPE 2: Appliquer les exceptions de permission ---
|
|
140
140
|
const exceptions = await datasCollection.aggregate([
|
|
141
141
|
{
|
|
142
|
-
$match: {
|
|
142
|
+
$match: { // Filtre de base pour les exceptions de l'utilisateur
|
|
143
143
|
_model: "userPermission",
|
|
144
|
-
user: user._id,
|
|
145
|
-
$
|
|
146
|
-
{
|
|
147
|
-
|
|
144
|
+
user: user._id,
|
|
145
|
+
$and: [ // Filtre sur l'environnement et l'expiration
|
|
146
|
+
{
|
|
147
|
+
$or: [ // La permission est soit globale, soit spécifique à l'environnement demandé
|
|
148
|
+
{ env: { $exists: false } },
|
|
149
|
+
{ env: env }
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{ $or: [{ expiresAt: { $exists: false } }, { expiresAt: { $gt: now } }] }
|
|
148
153
|
]
|
|
149
154
|
}
|
|
150
155
|
},
|
|
@@ -193,7 +198,7 @@ export async function getUserActivePermissions(user) {
|
|
|
193
198
|
* @param {object} user - L'objet utilisateur authentifié.
|
|
194
199
|
* @returns {Promise<boolean>} - True si l'utilisateur a la permission, sinon false.
|
|
195
200
|
*/
|
|
196
|
-
export async function hasPermission(permissionNames, user) {
|
|
201
|
+
export async function hasPermission(permissionNames, user, env = null) {
|
|
197
202
|
// Garde la compatibilité pour les utilisateurs non-locaux (ex: système)
|
|
198
203
|
if (!isLocalUser(user)) {
|
|
199
204
|
const userRoles = new Set(user.roles || []);
|
|
@@ -209,10 +214,10 @@ export async function hasPermission(permissionNames, user) {
|
|
|
209
214
|
}
|
|
210
215
|
|
|
211
216
|
// 1. Obtenir l'ensemble final et à jour des permissions de l'utilisateur
|
|
212
|
-
const activePermissions = await getUserActivePermissions(user);
|
|
217
|
+
const activePermissions = await getUserActivePermissions(user, env);
|
|
213
218
|
|
|
214
219
|
// 2. Vérifier si au moins une des permissions requises est dans l'ensemble des permissions actives
|
|
215
|
-
return requiredPermissions.some(pName => activePermissions.has(pName));
|
|
220
|
+
return requiredPermissions.some(pName => activePermissions.has(pName)) || false;
|
|
216
221
|
|
|
217
222
|
} catch (e) {
|
|
218
223
|
logger.error("Erreur lors de la vérification des permissions :", e);
|
|
@@ -1204,4 +1204,79 @@ describe('Data integration tests (CRUD, validation...)', () => {
|
|
|
1204
1204
|
});
|
|
1205
1205
|
});
|
|
1206
1206
|
|
|
1207
|
+
describe('searchData avec filtre _env', () => {
|
|
1208
|
+
let user, testModel, noEnvDocId;
|
|
1209
|
+
|
|
1210
|
+
beforeAll(async () => {
|
|
1211
|
+
// Setup
|
|
1212
|
+
const context = await setupTestContext();
|
|
1213
|
+
user = context.currentTestUser;
|
|
1214
|
+
testModel = {
|
|
1215
|
+
name: generateUniqueName('envTestModel'),
|
|
1216
|
+
description: '',
|
|
1217
|
+
_user: user.username,
|
|
1218
|
+
fields: [{ name: 'title', type: 'string' }]
|
|
1219
|
+
};
|
|
1220
|
+
await createModel(testModel);
|
|
1221
|
+
|
|
1222
|
+
// Insert test data
|
|
1223
|
+
await insertData(testModel.name, { title: 'Prod Data', _env: 'prod' }, {}, user, false);
|
|
1224
|
+
await insertData(testModel.name, { title: 'Staging Data', _env: 'staging' }, {}, user, false);
|
|
1225
|
+
const noEnvResult = await insertData(testModel.name, { title: 'No Env Data' }, {}, user, false);
|
|
1226
|
+
noEnvDocId = noEnvResult.insertedIds[0];
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
afterAll(async () => {
|
|
1230
|
+
// Cleanup
|
|
1231
|
+
await deleteModels({ name: testModel.name, _user: user.username });
|
|
1232
|
+
await deleteData(testModel.name, {}, user);
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
it('devrait trouver uniquement les données avec _env="prod"', async () => {
|
|
1236
|
+
const { data, count } = await searchData({
|
|
1237
|
+
model: testModel.name,
|
|
1238
|
+
env: 'prod'
|
|
1239
|
+
}, user);
|
|
1240
|
+
expect(count).toBe(1);
|
|
1241
|
+
expect(data).toHaveLength(1);
|
|
1242
|
+
expect(data[0].title).toBe('Prod Data');
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
it('devrait trouver uniquement les données avec _env="staging"', async () => {
|
|
1246
|
+
const { data, count } = await searchData({
|
|
1247
|
+
model: testModel.name,
|
|
1248
|
+
env: 'staging'
|
|
1249
|
+
}, user);
|
|
1250
|
+
expect(count).toBe(1);
|
|
1251
|
+
expect(data).toHaveLength(1);
|
|
1252
|
+
expect(data[0].title).toBe('Staging Data');
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
it('devrait trouver uniquement les données SANS _env spécifié', async () => {
|
|
1256
|
+
const { data, count } = await searchData({
|
|
1257
|
+
model: testModel.name
|
|
1258
|
+
// _env est omis intentionnellement
|
|
1259
|
+
}, user);
|
|
1260
|
+
expect(count).toBe(1);
|
|
1261
|
+
expect(data).toHaveLength(1);
|
|
1262
|
+
expect(data[0].title).toBe('No Env Data');
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
it('ne devrait trouver aucune donnée pour un _env inexistant', async () => {
|
|
1266
|
+
const { data, count } = await searchData({
|
|
1267
|
+
model: testModel.name,
|
|
1268
|
+
env: 'nonexistent'
|
|
1269
|
+
}, user);
|
|
1270
|
+
expect(count).toBe(0);
|
|
1271
|
+
expect(data).toHaveLength(0);
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
it('devrait permettre de modifier le champ _env via editData', async () => {
|
|
1275
|
+
const result = await editData(testModel.name, { _id: noEnvDocId }, { _env: 'newly_assigned' }, {}, user);
|
|
1276
|
+
expect(result.success).toBe(true);
|
|
1277
|
+
const { data, count } = await searchData({ model: testModel.name, env: 'newly_assigned' }, user);
|
|
1278
|
+
expect(count).toBe(1);
|
|
1279
|
+
expect(data[0]._id.toString()).toBe(noEnvDocId.toString());
|
|
1280
|
+
});
|
|
1281
|
+
});
|
|
1207
1282
|
});
|
package/test/user.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expect, describe, it,
|
|
1
|
+
import { expect, describe, it, afterEach, beforeAll, afterAll } from 'vitest';
|
|
2
2
|
import { hasPermission, getUserActivePermissions } from '../src/modules/user.js';
|
|
3
3
|
import {initEngine} from "../src/setenv";
|
|
4
4
|
import {generateUniqueName} from "../src/setenv.js";
|
|
@@ -207,4 +207,109 @@ describe('User Permission Logic', () => {
|
|
|
207
207
|
await coll.drop();
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
|
+
|
|
211
|
+
describe('hasPermission() with environment context', () => {
|
|
212
|
+
let user, coll;
|
|
213
|
+
let permRead, permWrite;
|
|
214
|
+
let envProdId, envStagingId;
|
|
215
|
+
|
|
216
|
+
beforeAll(async () => {
|
|
217
|
+
// --- Setup ---
|
|
218
|
+
coll = await setupTestContext();
|
|
219
|
+
user = currentTestUser; // Récupère l'utilisateur du contexte
|
|
220
|
+
|
|
221
|
+
// 1. Créer les permissions
|
|
222
|
+
const perms = await coll.insertMany([
|
|
223
|
+
{ _model: 'permission', name: 'env:read', _user: user.username },
|
|
224
|
+
{ _model: 'permission', name: 'env:write', _user: user.username }
|
|
225
|
+
]);
|
|
226
|
+
permRead = perms.insertedIds[0];
|
|
227
|
+
permWrite = perms.insertedIds[1];
|
|
228
|
+
|
|
229
|
+
// 2. Créer les environnements
|
|
230
|
+
const envs = await coll.insertMany([
|
|
231
|
+
{ _model: 'env', name: 'prod', _user: user.username },
|
|
232
|
+
{ _model: 'env', name: 'staging', _user: user.username }
|
|
233
|
+
]);
|
|
234
|
+
envProdId = envs.insertedIds[0].toString();
|
|
235
|
+
envStagingId = envs.insertedIds[1].toString();
|
|
236
|
+
|
|
237
|
+
// 3. Créer un rôle de base et l'assigner à l'utilisateur
|
|
238
|
+
const role = await coll.insertOne({
|
|
239
|
+
_model: 'role',
|
|
240
|
+
name: 'EnvTester',
|
|
241
|
+
permissions: [permRead.toString()],
|
|
242
|
+
_user: user.username
|
|
243
|
+
});
|
|
244
|
+
await coll.updateOne(
|
|
245
|
+
{ _id: user._id },
|
|
246
|
+
{ $set: { roles: [role.insertedId.toString()] } }
|
|
247
|
+
);
|
|
248
|
+
// Mettre à jour l'objet utilisateur en mémoire
|
|
249
|
+
user.roles = [role.insertedId.toString()];
|
|
250
|
+
user.permissions = ['env:read'];
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
afterAll(async () => {
|
|
254
|
+
await coll.drop();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
afterEach(async () => {
|
|
258
|
+
// Nettoyer les exceptions de permission après chaque test
|
|
259
|
+
await coll.deleteMany({ _model: 'userPermission', user: user._id });
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should grant permission if exception is global (no env)', async () => {
|
|
263
|
+
await coll.insertOne({
|
|
264
|
+
_model: 'userPermission', user: user._id, permission: permWrite, isGranted: true
|
|
265
|
+
});
|
|
266
|
+
// L'utilisateur doit avoir la permission dans n'importe quel environnement
|
|
267
|
+
expect(await hasPermission(['env:write'], user, envProdId)).toBe(true);
|
|
268
|
+
expect(await hasPermission(['env:write'], user, envStagingId)).toBe(true);
|
|
269
|
+
expect(await hasPermission(['env:write'], user, null)).toBe(true); // Environnement par défaut
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should grant permission only in the specified environment', async () => {
|
|
273
|
+
await coll.insertOne({
|
|
274
|
+
_model: 'userPermission', user: user._id, permission: permWrite, isGranted: true, env: envProdId
|
|
275
|
+
});
|
|
276
|
+
// L'utilisateur ne doit avoir la permission qu'en 'prod'
|
|
277
|
+
expect(await hasPermission(['env:write'], user, envProdId)).toBe(true);
|
|
278
|
+
expect(await hasPermission(['env:write'], user, envStagingId)).toBe(false);
|
|
279
|
+
expect(await hasPermission(['env:write'], user, null)).toBe(false);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should revoke a base permission globally if exception has no env', async () => {
|
|
283
|
+
await coll.insertOne({
|
|
284
|
+
_model: 'userPermission', user: user._id, permission: permRead, isGranted: false
|
|
285
|
+
});
|
|
286
|
+
// La permission de lecture (du rôle) doit être révoquée partout
|
|
287
|
+
expect(await hasPermission(['env:read'], user, envProdId)).toBe(false);
|
|
288
|
+
expect(await hasPermission(['env:read'], user, envStagingId)).toBe(false);
|
|
289
|
+
expect(await hasPermission(['env:read'], user, null)).toBe(false);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should revoke a base permission only in the specified environment', async () => {
|
|
293
|
+
await coll.insertOne({
|
|
294
|
+
_model: 'userPermission', user: user._id, permission: permRead, isGranted: false, env: envStagingId
|
|
295
|
+
});
|
|
296
|
+
// La permission de lecture ne doit être révoquée qu'en 'staging'
|
|
297
|
+
expect(await hasPermission(['env:read'], user, envProdId)).toBe(true);
|
|
298
|
+
expect(await hasPermission(['env:read'], user, envStagingId)).toBe(false);
|
|
299
|
+
expect(await hasPermission(['env:read'], user, null)).toBe(true);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should prioritize specific env revocation over a global grant', async () => {
|
|
303
|
+
await coll.insertMany([
|
|
304
|
+
// On accorde 'env:write' partout
|
|
305
|
+
{ _model: 'userPermission', user: user._id, permission: permWrite, isGranted: true },
|
|
306
|
+
// Mais on le révoque spécifiquement pour 'staging'
|
|
307
|
+
{ _model: 'userPermission', user: user._id, permission: permWrite, isGranted: false, env: envStagingId }
|
|
308
|
+
]);
|
|
309
|
+
|
|
310
|
+
expect(await hasPermission(['env:write'], user, envProdId)).toBe(true);
|
|
311
|
+
expect(await hasPermission(['env:write'], user, envStagingId)).toBe(false);
|
|
312
|
+
expect(await hasPermission(['env:write'], user, null)).toBe(true);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
210
315
|
});
|