Skip to content

Commit 729e097

Browse files
Merge pull request #252 from wttech/authorized-execution
Granular permission control
2 parents cda618f + c75bfbd commit 729e097

File tree

88 files changed

+899
-324
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+899
-324
lines changed

.github/workflows/test.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ jobs:
4747
path: all/target/*.zip
4848
retention-days: 3
4949

50+
- name: Upload ACM Content Example Package Artifact
51+
uses: actions/upload-artifact@v4
52+
with:
53+
name: acm-content-example
54+
path: ui.content.example/target/*.zip
55+
retention-days: 3
56+
5057
test:
5158
name: 'Test ACM Package'
5259
runs-on: ubuntu-latest
@@ -91,6 +98,12 @@ jobs:
9198
name: acm-package
9299
path: ./aem/home/lib/acm
93100

101+
- name: Download ACM Content Example Artifact
102+
uses: actions/download-artifact@v4
103+
with:
104+
name: acm-content-example
105+
path: ./aem/home/lib/acm-content-example
106+
94107
- name: Setup AEM Instance
95108
run: |
96109
set -e
@@ -113,6 +126,13 @@ jobs:
113126
sh aemw instance restart
114127
echo "Deployed ACM Package '$PACKAGE_PATH'"
115128
129+
- name: Deploy ACM Content Example Package
130+
run: |
131+
PACKAGE_PATH=$(find ./aem/home/lib/acm-content-example -name "*.zip" | head -1)
132+
echo "Deploying ACM Content Example Package '$PACKAGE_PATH'"
133+
sh aemw pkg deploy --file "$PACKAGE_PATH"
134+
echo "Deployed ACM Content Example Package '$PACKAGE_PATH'"
135+
116136
- name: Run Playwright Tests
117137
working-directory: ./test/e2e
118138
run: npx playwright test
@@ -121,6 +141,6 @@ jobs:
121141
uses: actions/upload-artifact@v4
122142
if: ${{ !cancelled() }}
123143
with:
124-
name: playwright-report
144+
name: acm-playwright-report
125145
path: ./test/e2e/playwright-report/
126146
retention-days: 30

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
### Project-specific
22

3-
ui.apps/src/main/content/jcr_root/apps/acm/spa
3+
ui.apps/src/main/content/jcr_root/apps/acm/gui/spa/build/
44
/var
55

66
# Created by https://www.gitignore.io/api/eclipse,java,maven

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
"search.exclude": {
33
"**/aem/home": true,
44
"**/node": true
5-
}
5+
},
6+
"java.configuration.updateBuildConfiguration": "interactive"
67
}

README.md

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ It works seamlessly across AEM on-premise, AMS, and AEMaaCS environments.
4949
- [Permissions Management](#permissions-management)
5050
- [Data Imports \& Exports](#data-imports--exports)
5151
- [Installation](#installation)
52+
- [Package Installation](#package-installation)
53+
- [Tools Access Configuration](#tools-access-configuration)
54+
- [Feature Permissions](#feature-permissions)
55+
- [API Permissions](#api-permissions)
5256
- [Compatibility](#compatibility)
5357
- [Documentation](#documentation)
5458
- [Usage](#usage)
@@ -107,6 +111,8 @@ By simplifying data import implementation, ACM allows developers to focus more o
107111

108112
## Installation
109113

114+
### Package Installation
115+
110116
The ready-to-install AEM packages are available on:
111117

112118
- [GitHub releases](https://github.com/wttech/acm/releases).
@@ -155,27 +161,62 @@ Adjust file 'all/pom.xml':
155161

156162
Repeat the same for [ui.content.example](https://central.sonatype.com/artifact/dev.vml.es/acm.ui.content.example) package if you want to install demonstrative ACM scripts to get you started quickly.
157163

158-
3. Consider refining the ACL settings
159-
160-
The default settings are defined in the [repo init OSGi config](https://github.com/wttech/acm/blob/main/ui.config/src/main/content/jcr_root/apps/acm-config/osgiconfig/config/org.apache.sling.jcr.repoinit.RepositoryInitializer~acmcore.config), which effectively restrict access to the tool and script execution to administrators only - a recommended practice for production environments.
161-
If you require further customization, you can create your own repo init OSGi config to override or extend the default configuration.
162-
163-
For example:
164-
```ini
165-
service.ranking=I"100"
166-
scripts=["
167-
set ACL for everyone
168-
deny jcr:read on /apps/acm
169-
deny jcr:read on /apps/cq/core/content/nav/tools/acm
170-
end
171-
172-
create group acm-users
173-
set ACL for acm-users
174-
allow jcr:read on /apps/acm
175-
allow jcr:read on /apps/cq/core/content/nav/tools/acm
176-
end
177-
"]
178-
```
164+
### Tools Access Configuration
165+
166+
The default settings are defined in the [repo init OSGi config](https://github.com/wttech/acm/blob/main/ui.config/src/main/content/jcr_root/apps/acm-config/osgiconfig/config/org.apache.sling.jcr.repoinit.RepositoryInitializer~acmcore.config), which effectively restrict access to the tool and script execution to administrators only - a recommended practice for production environments.
167+
168+
If you require further customization, you can create your own repo init OSGi config to override or extend the default configuration.
169+
170+
#### Feature Permissions
171+
172+
ACM supports fine-grained permission control through individual features. This allows you to grant specific capabilities to different user groups without providing full access to ACM tool. For a complete list of available features, see the [ACM features directory](https://github.com/wttech/acm/tree/main/ui.apps/src/main/content/jcr_root/apps/acm/feature).
173+
174+
**Example: Create groups for full and limited access:**
175+
176+
```ini
177+
service.ranking=I"100"
178+
scripts=["
179+
set ACL for everyone
180+
deny jcr:read on /apps/cq/core/content/nav/tools/acm
181+
deny jcr:read on /apps/acm
182+
end
183+
184+
create group acm-admins
185+
set ACL for acm-admins
186+
allow jcr:read on /apps/cq/core/content/nav/tools/acm
187+
allow jcr:read on /apps/acm
188+
end
189+
190+
create group acm-script-users
191+
set ACL for acm-script-users
192+
allow jcr:read on /apps/cq/core/content/nav/tools/acm
193+
allow jcr:read on /apps/acm/gui
194+
allow jcr:read on /apps/acm/api
195+
196+
allow jcr:read on /apps/acm/feature/script/list
197+
allow jcr:read on /apps/acm/feature/script/view
198+
allow jcr:read on /apps/acm/feature/execution/view
199+
200+
allow jcr:read on /conf/acm/settings/script
201+
end
202+
"]
203+
```
204+
205+
Later on when AEM is running, just assign users to the created groups (`acm-admins` or `acm-script-users`) to grant them the corresponding access.
206+
207+
#### API Permissions
208+
209+
Access to ACM's REST API endpoints is controlled through nodes under `/apps/acm/api`. For a complete list of available endpoints, see the [ACM API directory](https://github.com/wttech/acm/tree/main/ui.apps/src/main/content/jcr_root/apps/acm/api).
210+
211+
**Important:** Code execution requires authorization at three levels: API endpoint, feature, and e.g. script path. Example:
212+
213+
```ini
214+
set ACL for acm-automation-user
215+
allow jcr:read on /apps/acm/api
216+
allow jcr:read on /apps/acm/feature
217+
allow jcr:read on /conf/acm/settings/script
218+
end
219+
```
179220

180221
## Compatibility
181222

Taskfile.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ tasks:
249249
desc: build & deploy frontend to instances
250250
cmds:
251251
- (cd ui.frontend && npm install && npm run build)
252-
- sh aemw content push --dir 'ui.apps/src/main/content/jcr_root/apps/acm/spa'
252+
- sh aemw content push --dir 'ui.apps/src/main/content/jcr_root/apps/acm/gui/spa/build'
253253

254254
develop:frontend:dev:
255255
desc: build & deploy frontend to instances in development mode

core/src/main/java/dev/vml/es/acm/core/AcmConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ public class AcmConstants {
88

99
public static final String NOTIFIER_ID = "acm";
1010

11+
public static final String APPS_ROOT = "/apps/acm";
12+
1113
public static final String SETTINGS_ROOT = "/conf/acm/settings";
1214

1315
public static final String VAR_ROOT = "/var/acm";

core/src/main/java/dev/vml/es/acm/core/acl/authorizable/PermissionsOptions.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
public class PermissionsOptions {
1313

14+
public static final String GLOB_STRICT = "strict";
15+
16+
private static final String GLOB_STRICT_EFFECTIVE = "";
17+
1418
private String path;
1519

1620
private List<String> permissions = Collections.emptyList();
@@ -33,6 +37,11 @@ public void setPath(String path) {
3337
this.path = path;
3438
}
3539

40+
public void setPathStrict(String path) {
41+
this.path = path;
42+
this.glob = GLOB_STRICT_EFFECTIVE;
43+
}
44+
3645
public List<String> getPermissions() {
3746
return permissions;
3847
}
@@ -46,7 +55,7 @@ public String getGlob() {
4655
}
4756

4857
public void setGlob(String glob) {
49-
this.glob = glob;
58+
this.glob = GLOB_STRICT.equalsIgnoreCase(glob) ? GLOB_STRICT_EFFECTIVE : glob;
5059
}
5160

5261
public List<String> getTypes() {

core/src/main/java/dev/vml/es/acm/core/acl/check/PermissionsOptions.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
public class PermissionsOptions extends AuthorizableOptions {
1313

14+
public static final String GLOB_STRICT = "strict";
15+
16+
private static final String GLOB_STRICT_EFFECTIVE = "";
17+
1418
private String path;
1519

1620
private List<String> permissions = Collections.emptyList();
@@ -31,6 +35,11 @@ public void setPath(String path) {
3135
this.path = path;
3236
}
3337

38+
public void setPathStrict(String path) {
39+
this.path = path;
40+
this.glob = GLOB_STRICT_EFFECTIVE;
41+
}
42+
3443
public List<String> getPermissions() {
3544
return permissions;
3645
}
@@ -44,7 +53,7 @@ public String getGlob() {
4453
}
4554

4655
public void setGlob(String glob) {
47-
this.glob = glob;
56+
this.glob = GLOB_STRICT.equalsIgnoreCase(glob) ? GLOB_STRICT_EFFECTIVE : glob;
4857
}
4958

5059
public List<String> getTypes() {

core/src/main/java/dev/vml/es/acm/core/code/CodePrintStream.java

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class CodePrintStream extends PrintStream {
2828
public static final String[] LOGGER_NAMES = {LOGGER_NAME_ACL, LOGGER_NAME_REPO};
2929

3030
// have to match pattern in 'monaco/log.ts'
31-
private static final DateTimeFormatter LOGGER_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
31+
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
3232

3333
private final Logger logger;
3434

@@ -40,6 +40,8 @@ public class CodePrintStream extends PrintStream {
4040

4141
private final LogAppender logAppender;
4242

43+
private boolean printerTimestamps;
44+
4345
public CodePrintStream(OutputStream output, String id) {
4446
super(output);
4547

@@ -48,6 +50,8 @@ public CodePrintStream(OutputStream output, String id) {
4850
this.loggerTimestamps = true;
4951
this.logger = loggerContext.getLogger(id);
5052
this.logAppender = new LogAppender();
53+
54+
this.printerTimestamps = true;
5155
}
5256

5357
private class LogAppender extends AppenderBase<ILoggingEvent> {
@@ -60,7 +64,7 @@ protected void append(ILoggingEvent event) {
6064
if (loggerTimestamps) {
6165
LocalDateTime eventTime = LocalDateTime.ofInstant(
6266
Instant.ofEpochMilli(event.getTimeStamp()), ZoneId.systemDefault());
63-
String timestamp = eventTime.format(LOGGER_TIMESTAMP_FORMATTER);
67+
String timestamp = eventTime.format(TIMESTAMP_FORMATTER);
6468
println(timestamp + " [" + level + "] " + event.getFormattedMessage());
6569
} else {
6670
println('[' + level + "] " + event.getFormattedMessage());
@@ -116,7 +120,7 @@ public boolean isLoggerTimestamps() {
116120
return loggerTimestamps;
117121
}
118122

119-
public void withLoggerTimestamps(boolean flag) {
123+
public void setLoggerTimestamps(boolean flag) {
120124
this.loggerTimestamps = flag;
121125
}
122126

@@ -147,33 +151,45 @@ public void fromLoggers(List<String> loggerNames) {
147151
loggerNames.forEach(this::fromLogger);
148152
}
149153

154+
public void setPrinterTimestamps(boolean flag) {
155+
this.printerTimestamps = flag;
156+
}
157+
158+
public boolean isPrinterTimestamps() {
159+
return printerTimestamps;
160+
}
161+
162+
public void printTimestamped(String level, String message) {
163+
printTimestamped(CodePrintLevel.of(level), message);
164+
}
165+
166+
public void printTimestamped(CodePrintLevel level, String message) {
167+
if (printerTimestamps) {
168+
LocalDateTime now = LocalDateTime.now();
169+
String timestamp = now.format(TIMESTAMP_FORMATTER);
170+
println(timestamp + " [" + level + "] " + message);
171+
} else {
172+
println("[" + level + "] " + message);
173+
}
174+
}
175+
150176
public void success(String message) {
151-
printStamped(CodePrintLevel.SUCCESS, message);
177+
printTimestamped(CodePrintLevel.SUCCESS, message);
152178
}
153179

154180
public void info(String message) {
155-
printStamped(CodePrintLevel.INFO, message);
181+
printTimestamped(CodePrintLevel.INFO, message);
156182
}
157183

158184
public void error(String message) {
159-
printStamped(CodePrintLevel.ERROR, message);
185+
printTimestamped(CodePrintLevel.ERROR, message);
160186
}
161187

162188
public void warn(String message) {
163-
printStamped(CodePrintLevel.WARN, message);
189+
printTimestamped(CodePrintLevel.WARN, message);
164190
}
165191

166192
public void debug(String message) {
167-
printStamped(CodePrintLevel.DEBUG, message);
168-
}
169-
170-
public void printStamped(String level, String message) {
171-
printStamped(CodePrintLevel.of(level), message);
172-
}
173-
174-
public void printStamped(CodePrintLevel level, String message) {
175-
LocalDateTime now = LocalDateTime.now();
176-
String timestamp = now.format(LOGGER_TIMESTAMP_FORMATTER);
177-
println(timestamp + " [" + level + "] " + message);
193+
printTimestamped(CodePrintLevel.DEBUG, message);
178194
}
179195
}

core/src/main/java/dev/vml/es/acm/core/code/Conditions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public InstanceInfo instanceInfo() {
209209
// Executable-based
210210

211211
public boolean isConsole() {
212-
return Executable.ID_CONSOLE.equals(executableId());
212+
return Executable.CONSOLE_ID.equals(executableId());
213213
}
214214

215215
public boolean isAutomaticScript() {

0 commit comments

Comments
 (0)