@adobe/helix-config 3.3.8 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [3.4.0](https://github.com/adobe/helix-config/compare/v3.3.8...v3.4.0) (2024-07-04)
2
+
3
+
4
+ ### Features
5
+
6
+ * add user groups and resolve for admin roles ([#120](https://github.com/adobe/helix-config/issues/120)) ([a325138](https://github.com/adobe/helix-config/commit/a325138650e20ef3bd662e6c3bf163c36ccb9334)), closes [#15](https://github.com/adobe/helix-config/issues/15) [#121](https://github.com/adobe/helix-config/issues/121)
7
+
1
8
  ## [3.3.8](https://github.com/adobe/helix-config/compare/v3.3.7...v3.3.8) (2024-07-02)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-config",
3
- "version": "3.3.8",
3
+ "version": "3.4.0",
4
4
  "description": "Helix Config",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -276,7 +276,12 @@ export async function loadOrgConfig(ctx, org) {
276
276
  return res.body ? res.json() : null;
277
277
  }
278
278
 
279
- function computeAdminRoles(adminConfig, orgConfig) {
279
+ /**
280
+ * Computes the access.admin.role arrays for the org users.
281
+ * @param adminConfig
282
+ * @param orgConfig
283
+ */
284
+ function computeOrgAdminRoles(adminConfig, orgConfig) {
280
285
  if (orgConfig?.users) {
281
286
  // map users[].roles[] to access.admin.role[].email
282
287
  const rolesObj = deepGetOrCreate(adminConfig, ['access', 'admin', 'role'], true);
@@ -293,6 +298,50 @@ function computeAdminRoles(adminConfig, orgConfig) {
293
298
  }
294
299
  }
295
300
 
301
+ /**
302
+ * Extract the email addresses of the users for the given group.
303
+ * @param groups
304
+ * @param name
305
+ */
306
+ function resolveGroup(groups, name) {
307
+ const users = groups?.[name]?.members ?? [];
308
+ return users.map((user) => user.email);
309
+ }
310
+
311
+ /**
312
+ * Compute the access.admin.role arrays for the admin config. Resolves site and org groups.
313
+ * @param admin
314
+ * @param configGroups
315
+ * @param orgGroups
316
+ */
317
+ function computeSiteAdminRoles(admin, configGroups = {}, orgGroups = {}) {
318
+ if (!admin.role) {
319
+ return admin;
320
+ }
321
+ const roles = {};
322
+ for (const [roleName, role] of Object.entries(admin.role)) {
323
+ const users = new Set();
324
+ for (const /* @type string */ entry of role) {
325
+ if (entry.indexOf('@') > 0) {
326
+ users.add(entry);
327
+ } else if (entry.startsWith('groups/') && entry.endsWith('.json')) {
328
+ for (const email of resolveGroup(configGroups, entry.substring(7, entry.length - 5))) {
329
+ users.add(email);
330
+ }
331
+ } else if (entry.startsWith('/groups/')) {
332
+ for (const email of resolveGroup(orgGroups, entry.substring(8, entry.length - 5))) {
333
+ users.add(email);
334
+ }
335
+ }
336
+ }
337
+ roles[roleName] = Array.from(users);
338
+ }
339
+ return {
340
+ ...admin,
341
+ role: roles,
342
+ };
343
+ }
344
+
296
345
  export async function getConfigResponse(ctx, opts) {
297
346
  const {
298
347
  ref, site, org, scope,
@@ -338,7 +387,7 @@ export async function getConfigResponse(ctx, opts) {
338
387
  // access.require.repository ?
339
388
  };
340
389
  if (opts.scope === SCOPE_ADMIN || opts.scope === SCOPE_RAW) {
341
- config.access.admin = admin;
390
+ config.access.admin = computeSiteAdminRoles(admin, config.groups, orgConfig.groups);
342
391
  }
343
392
  }
344
393
 
@@ -384,11 +433,11 @@ export async function getConfigResponse(ctx, opts) {
384
433
  ...config,
385
434
  content: {
386
435
  ...config.content,
387
- ...config.content.source,
388
436
  },
389
437
  };
390
438
  delete adminConfig.public;
391
439
  delete adminConfig.robots;
440
+ delete adminConfig.groups;
392
441
  return new PipelineResponse(JSON.stringify(adminConfig, null, 2), {
393
442
  headers: {
394
443
  'content-type': 'application/json',
@@ -478,9 +527,10 @@ export async function getOrgConfigResponse(ctx, opts) {
478
527
  ...orgConfig,
479
528
  };
480
529
 
481
- computeAdminRoles(adminConfig, orgConfig);
530
+ computeOrgAdminRoles(adminConfig, orgConfig);
482
531
  delete adminConfig.tokens;
483
532
  delete adminConfig.users;
533
+ delete adminConfig.groups;
484
534
  return new PipelineResponse(JSON.stringify(adminConfig, null, 2), {
485
535
  headers: {
486
536
  'content-type': 'application/json',
@@ -20,7 +20,7 @@
20
20
  "type": "object",
21
21
  "patternProperties": {
22
22
  "^[a-z-_]+$": {
23
- "description": "The email glob of the users with respective role.",
23
+ "description": "The email glob of the users or a group reference for the respective role.",
24
24
  "type": "array",
25
25
  "items": {"type": "string"}
26
26
  }
@@ -0,0 +1,12 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ module.exports = require('./groups.schema.json');
@@ -0,0 +1,49 @@
1
+ {
2
+ "meta:license": [
3
+ "Copyright 2024 Adobe. All rights reserved.",
4
+ "This file is licensed to you under the Apache License, Version 2.0 (the \"License\");",
5
+ "you may not use this file except in compliance with the License. You may obtain a copy",
6
+ "of the License at http://www.apache.org/licenses/LICENSE-2.0",
7
+ "",
8
+ "Unless required by applicable law or agreed to in writing, software distributed under",
9
+ "the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS",
10
+ "OF ANY KIND, either express or implied. See the License for the specific language",
11
+ "governing permissions and limitations under the License."
12
+ ],
13
+ "$schema": "http://json-schema.org/draft-07/schema#",
14
+ "$id": "https://ns.adobe.com/helix/config/groups",
15
+ "type": "object",
16
+ "title": "Groups",
17
+ "patternProperties": {
18
+ "^[a-zA-Z0-9-_=]+$": {
19
+ "type": "object",
20
+ "title": "Group",
21
+ "description": "A group of members. Can be referenced in access.admin.role.",
22
+ "properties": {
23
+ "members": {
24
+ "type": "array",
25
+ "items": {
26
+ "type": "object",
27
+ "properties": {
28
+ "email": {
29
+ "type": "string",
30
+ "format": "email"
31
+ },
32
+ "name": {
33
+ "type": "string"
34
+ }
35
+ },
36
+ "required": [
37
+ "email"
38
+ ]
39
+ }
40
+ }
41
+ },
42
+ "additionalProperties": false,
43
+ "required": [
44
+ "members"
45
+ ]
46
+ }
47
+ },
48
+ "additionalProperties": false
49
+ }
@@ -27,6 +27,9 @@
27
27
  },
28
28
  "users": {
29
29
  "$ref": "https://ns.adobe.com/helix/config/users"
30
+ },
31
+ "groups": {
32
+ "$ref": "https://ns.adobe.com/helix/config/groups"
30
33
  }
31
34
  },
32
35
  "required": [
@@ -46,6 +46,9 @@
46
46
  "access": {
47
47
  "$ref": "https://ns.adobe.com/helix/config/access"
48
48
  },
49
+ "groups": {
50
+ "$ref": "https://ns.adobe.com/helix/config/groups"
51
+ },
49
52
  "sidekick": {
50
53
  "$ref": "https://ns.adobe.com/helix/config/sidekick"
51
54
  },
@@ -49,6 +49,13 @@ const FRAGMENTS = {
49
49
  sites: {
50
50
  ...FRAGMENTS_COMMON,
51
51
  extends: 'object',
52
+ groups: {
53
+ '.': 'object',
54
+ '.param': {
55
+ '.': 'object',
56
+ members: 'object',
57
+ },
58
+ },
52
59
  },
53
60
  profiles: FRAGMENTS_COMMON,
54
61
  org: {
@@ -66,6 +73,13 @@ const FRAGMENTS = {
66
73
  '.': 'user',
67
74
  },
68
75
  },
76
+ groups: {
77
+ '.': 'object',
78
+ '.param': {
79
+ '.': 'object',
80
+ members: 'object',
81
+ },
82
+ },
69
83
  },
70
84
  };
71
85
 
@@ -218,7 +232,7 @@ export class ConfigStore {
218
232
  if (frag) {
219
233
  obj = deepGetOrCreate(obj, frag.relPath);
220
234
  }
221
- return redact(obj, frag);
235
+ return redact(obj, frag) ?? null;
222
236
  }
223
237
 
224
238
  async update(ctx, data, relPath = '') {
@@ -21,6 +21,7 @@ import commonSchema from '../schemas/common.schema.cjs';
21
21
  import codeSchema from '../schemas/code.schema.cjs';
22
22
  import contentSchema from '../schemas/content.schema.cjs';
23
23
  import foldersSchema from '../schemas/folders.schema.cjs';
24
+ import groupsSchema from '../schemas/groups.schema.cjs';
24
25
  import googleSchema from '../schemas/content-source-google.schema.cjs';
25
26
  import headersSchema from '../schemas/headers.schema.cjs';
26
27
  import markupSchema from '../schemas/content-source-markup.schema.cjs';
@@ -48,6 +49,7 @@ export const SCHEMAS = [
48
49
  codeSchema,
49
50
  foldersSchema,
50
51
  googleSchema,
52
+ groupsSchema,
51
53
  headersSchema,
52
54
  markupSchema,
53
55
  metadataSchema,
package/src/utils.js CHANGED
@@ -21,18 +21,23 @@
21
21
  */
22
22
  export function deepGetOrCreate(obj, path, create = false) {
23
23
  const parts = Array.isArray(path) ? path : path.split('/');
24
- return parts.reduce((o, prop) => {
24
+ let o = obj;
25
+ for (const prop of parts) {
25
26
  if (Array.isArray(o)) {
26
27
  // todo: better support for arrays
27
- return o.find((e) => e.id === prop);
28
+ o = o.find((e) => e.id === prop);
29
+ } else if (typeof o !== 'object') {
30
+ return undefined;
31
+ } else if (prop in o) {
32
+ o = o[prop];
33
+ } else if (create) {
34
+ // eslint-disable-next-line no-multi-assign
35
+ o = o[prop] = {};
28
36
  } else {
29
- if (create && !(prop in o)) {
30
- // eslint-disable-next-line no-param-reassign
31
- o[prop] = {};
32
- }
33
- return o[prop];
37
+ return undefined;
34
38
  }
35
- }, obj);
39
+ }
40
+ return o;
36
41
  }
37
42
 
38
43
  /**
@@ -26,6 +26,7 @@ export interface HelixOrgConfig {
26
26
  description?: string;
27
27
  tokens?: Tokens;
28
28
  users?: Users;
29
+ groups?: Groups;
29
30
  }
30
31
  export interface Tokens {
31
32
  /**
@@ -44,3 +45,19 @@ export interface HttpsNsAdobeComHelixConfigUser {
44
45
  name?: string;
45
46
  roles: ('admin' | 'author' | 'publish' | 'config')[];
46
47
  }
48
+ export interface Groups {
49
+ [k: string]: Group;
50
+ }
51
+ /**
52
+ * A group of members. Can be referenced in access.admin.role.
53
+ *
54
+ * This interface was referenced by `Groups`'s JSON-Schema definition
55
+ * via the `patternProperty` "^[a-zA-Z0-9-_=]+$".
56
+ */
57
+ export interface Group {
58
+ members: {
59
+ email: string;
60
+ name?: string;
61
+ [k: string]: unknown;
62
+ }[];
63
+ }
@@ -32,6 +32,7 @@ export interface HelixSiteConfig {
32
32
  headers?: HelixHeadersConfig;
33
33
  cdn?: CDNConfig;
34
34
  access?: Access;
35
+ groups?: Groups;
35
36
  sidekick?: SidekickConfig;
36
37
  metadata?: Metadata;
37
38
  robots?: Robots;
@@ -252,6 +253,22 @@ export interface SiteAccessConfig {
252
253
  */
253
254
  clientCertDN?: string[];
254
255
  }
256
+ export interface Groups {
257
+ [k: string]: Group;
258
+ }
259
+ /**
260
+ * A group of members. Can be referenced in access.admin.role.
261
+ *
262
+ * This interface was referenced by `Groups`'s JSON-Schema definition
263
+ * via the `patternProperty` "^[a-zA-Z0-9-_=]+$".
264
+ */
265
+ export interface Group {
266
+ members: {
267
+ email: string;
268
+ name?: string;
269
+ [k: string]: unknown;
270
+ }[];
271
+ }
255
272
  export interface SidekickConfig {
256
273
  plugins?: SidekickPlugin[];
257
274
  [k: string]: unknown;