Skip to content

Commit a450674

Browse files
committed
governance with labels
1 parent 37358fc commit a450674

File tree

5 files changed

+181
-1
lines changed

5 files changed

+181
-1
lines changed

component-filters.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
"fragment"
1919
]
2020
},
21+
{
22+
"id": "empty",
23+
"components": []
24+
},
2125
{
2226
"id": "cards",
2327
"components": [

models/_section.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@
5252
"columns",
5353
"fragment"
5454
]
55+
},
56+
{
57+
"id": "empty",
58+
"components": [
59+
]
5560
}
5661
]
5762
}

scripts/editor-support.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from './aem.js';
1111
import { decorateRichtext } from './editor-support-rte.js';
1212
import { decorateMain } from './scripts.js';
13+
import applyGovernance from './governance.js';
1314

1415
async function applyChanges(event) {
1516
// redecorate default content and blocks on patches (in the properties rail)
@@ -104,7 +105,11 @@ function attachEventListners(main) {
104105
].forEach((eventType) => main?.addEventListener(eventType, async (event) => {
105106
event.stopPropagation();
106107
const applied = await applyChanges(event);
107-
if (!applied) window.location.reload();
108+
if (!applied) {
109+
window.location.reload();
110+
} else {
111+
await applyGovernance();
112+
}
108113
}));
109114
}
110115

scripts/governance.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
function applyRestrictions(allowedSelectors) {
2+
// for each allowedSelectors entry, separate the value by /
3+
// if the value is image,text,button or title replace it with "[data-aue-model="value"]"
4+
// otherwise replace it with "[data-aue-label="value"]"
5+
const cssSelectors = allowedSelectors.map((allowedSelector) => {
6+
const values = allowedSelector.split('/');
7+
let cssSelector = '';
8+
values.forEach((value) => {
9+
if (value === 'Image' || value === 'Title' || value === 'Button') {
10+
cssSelector += `[data-aue-model="${value.toLowerCase()}"] `;
11+
} else if (value === 'Text') {
12+
cssSelector += `[data-aue-type="richtext"] `;
13+
} else {
14+
cssSelector += `[data-aue-label="${value}"] `;
15+
}
16+
});
17+
return cssSelector;
18+
});
19+
20+
// collect all the elements that match the allowed selectors
21+
const combinedSelector = cssSelectors.join(', ');
22+
const selectorElements = document.querySelector('main')?.querySelectorAll(combinedSelector);
23+
24+
// go through the elements found by selectors
25+
selectorElements.forEach((selectorElement) => {
26+
// mark it as verified by the selector
27+
selectorElement.dataset.aueVerified = 'true';
28+
// mark all instrumented children as verified
29+
const children = selectorElement.querySelectorAll('[data-aue-type]');
30+
children.forEach((child) => {
31+
child.dataset.aueVerified = 'true';
32+
});
33+
// if its a container block item (or default content)
34+
// if it doesnt have a class 'block' try to find the closest parent that has a class block
35+
if (!selectorElement.classList.contains('block')) {
36+
const blockParent = selectorElement.closest('.block');
37+
if (blockParent) {
38+
// ift not already marked asverified, mark it as ancestor element
39+
if (!blockParent.dataset.aueVerified) {
40+
blockParent.dataset.aueVerified = 'ancestor';
41+
}
42+
}
43+
}
44+
// mark parent sections as ancestor elements
45+
const sectionParent = selectorElement.closest('.section');
46+
if (sectionParent && sectionParent !== selectorElement && !sectionParent.dataset.aueVerified) {
47+
sectionParent.dataset.aueVerified = 'ancestor';
48+
}
49+
// get all ancestor elements and remove data-aue-model and data-aue-filter attributes
50+
// so author can't edit properties that they are not allowed to edit
51+
const ancestorElements = document.querySelector('main')?.querySelectorAll('[data-aue-verified="ancestor"]');
52+
ancestorElements.forEach((element) => {
53+
element.removeAttribute('data-aue-model');
54+
// filter must exist and be empty otherwise no filter gets applied
55+
element.dataset.aueFilter = 'empty';
56+
element.dataset.aueVerified = 'true';
57+
});
58+
});
59+
// select all instrumented elements that dont have data-aue-verified="true"
60+
// so author cant edit properties he is not allowed to edit
61+
const unverifiedElements = document.querySelector('main')?.querySelectorAll('*[data-aue-type]:not([data-aue-verified="true"])');
62+
unverifiedElements.forEach((element) => {
63+
// remove all data-aue attributes from the element
64+
element.removeAttribute('data-aue-resource');
65+
element.removeAttribute('data-aue-prop');
66+
element.removeAttribute('data-aue-label');
67+
element.removeAttribute('data-aue-filter');
68+
element.removeAttribute('data-aue-type');
69+
element.removeAttribute('data-aue-behavior');
70+
element.removeAttribute('data-aue-model');
71+
});
72+
}
73+
74+
/**
75+
* Fetches the current user's group memberships
76+
* @returns {Promise<string[]>} An array of group names
77+
*/
78+
async function fetchUserGroupMemberships() {
79+
try {
80+
const response = await fetch('/libs/granite/security/currentuser.json?props=memberOf');
81+
const data = await response.json();
82+
// extract group names from memberOf array
83+
const groupMemberships = data.memberOf
84+
? data.memberOf.map((group) => group.authorizableId)
85+
: [];
86+
return groupMemberships;
87+
} catch (error) {
88+
// eslint-disable-next-line no-console
89+
console.error('Error fetching user group memberships:', error);
90+
return [];
91+
}
92+
}
93+
94+
/**
95+
* Fetches the restrictions data from the server
96+
*/
97+
async function fetchRestrictions() {
98+
try {
99+
const response = await fetch('/drafts/msagolj/restrictions.json');
100+
const data = await response.json();
101+
return data;
102+
} catch (error) {
103+
// eslint-disable-next-line no-console
104+
console.error('Error fetching restrictions:', error);
105+
return null;
106+
}
107+
}
108+
109+
export default async function applyGovernance() {
110+
// get the current user's group memberships
111+
const groupMemberships = await fetchUserGroupMemberships();
112+
// only fetch restrictions if the user has group memberships
113+
if (groupMemberships.length === 0) {
114+
return;
115+
}
116+
// get the list of restrictions
117+
const restrictionsData = await fetchRestrictions();
118+
if (!restrictionsData) {
119+
return;
120+
}
121+
// check if the path matches the current path
122+
const currentPath = window.location.pathname;
123+
const allAllowed = [];
124+
if (restrictionsData.data && Array.isArray(restrictionsData.data)) {
125+
restrictionsData.data.forEach((restriction) => {
126+
if (restriction.path) {
127+
let pathMatches = false;
128+
try {
129+
// treat the path as a regular expression
130+
const pathRegex = new RegExp(restriction.path);
131+
pathMatches = pathRegex.test(currentPath);
132+
} catch (error) {
133+
// if regex is invalid, try exact match
134+
pathMatches = restriction.path === currentPath;
135+
}
136+
if (pathMatches) {
137+
// split the group property by comma to get list of restricted groups
138+
const restrictedGroups = restriction.group
139+
? restriction.group.split(',').map((g) => g.trim())
140+
: [];
141+
// find which restricted groups the user is a member of
142+
const matchedGroups = restrictedGroups.filter(
143+
(restrictedGroup) => groupMemberships.includes(restrictedGroup),
144+
);
145+
146+
if (matchedGroups.length > 0) {
147+
// get the allowed values from the restriction
148+
if (restriction.allowed) {
149+
const allowed = restriction.allowed.split(',').map((s) => s.trim());
150+
allAllowed.push(...allowed);
151+
}
152+
}
153+
}
154+
}
155+
});
156+
}
157+
158+
// apply restrictions if any selectors were collected
159+
if (allAllowed.length > 0) {
160+
applyRestrictions(allAllowed);
161+
}
162+
}

scripts/scripts.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
loadSections,
1212
loadCSS,
1313
} from './aem.js';
14+
import applyGovernance from './governance.js';
1415

1516
/**
1617
* Moves all the attributes from a given elmenet to another given element.
@@ -141,6 +142,9 @@ function loadDelayed() {
141142
async function loadPage() {
142143
await loadEager(document);
143144
await loadLazy(document);
145+
// we apply governance after decorating the blocks
146+
// so that also blocks that get rearranged can be targeted by the restrictions
147+
await applyGovernance();
144148
loadDelayed();
145149
}
146150

0 commit comments

Comments
 (0)