Skip to content

Commit 43687ff

Browse files
committed
governance with labels
1 parent 37358fc commit 43687ff

File tree

5 files changed

+179
-1
lines changed

5 files changed

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

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)