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.
@@ -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, // Pas besoin de convertir en ObjectId si c'est déjà une string
145
- $or: [
146
- { expiresAt: { $exists: false } },
147
- { expiresAt: { $gt: now } }
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, beforeEach, beforeAll, afterAll } from 'vitest';
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
  });