Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.

Commit 323473f

Browse files
committed
#215 Can now create roles with permissions that refer to itself and non-created roles
1 parent bb5110e commit 323473f

File tree

6 files changed

+113
-209
lines changed

6 files changed

+113
-209
lines changed

src/main/java/com/marklogic/appdeployer/command/AbstractCommand.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,27 @@ protected String copyFileToString(File f, CommandContext context) {
133133
protected SaveReceipt saveResource(ResourceManager mgr, CommandContext context, File f) {
134134
String payload = copyFileToString(f, context);
135135
mgr = adjustResourceManagerForPayload(mgr, context, payload);
136+
payload = adjustPayloadBeforeSavingResource(mgr, context, f, payload);
136137
SaveReceipt receipt = mgr.save(payload);
137138
if (storeResourceIdsAsCustomTokens) {
138139
storeTokenForResourceId(receipt, context);
139140
}
140141
return receipt;
141142
}
142143

144+
/**
145+
* Allow subclass to override this in order to fiddle with the payload before it's saved; called by saveResource.
146+
*
147+
* @param mgr
148+
* @param context
149+
* @param f
150+
* @param payload
151+
* @return
152+
*/
153+
protected String adjustPayloadBeforeSavingResource(ResourceManager mgr, CommandContext context, File f, String payload) {
154+
return payload;
155+
}
156+
143157
/**
144158
* A subclass can override this when the ResourceManager needs to be adjusted based on data in the payload.
145159
*
Lines changed: 31 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,61 @@
11
package com.marklogic.appdeployer.command.security;
22

3-
import com.fasterxml.jackson.databind.JsonNode;
4-
import com.fasterxml.jackson.databind.node.ArrayNode;
53
import com.marklogic.appdeployer.command.AbstractResourceCommand;
64
import com.marklogic.appdeployer.command.CommandContext;
75
import com.marklogic.appdeployer.command.SortOrderConstants;
8-
import com.marklogic.mgmt.PayloadParser;
9-
import com.marklogic.mgmt.resource.ResourceManager;
6+
import com.marklogic.mgmt.api.API;
107
import com.marklogic.mgmt.api.security.Role;
8+
import com.marklogic.mgmt.mapper.DefaultResourceMapper;
9+
import com.marklogic.mgmt.mapper.ResourceMapper;
10+
import com.marklogic.mgmt.resource.ResourceManager;
1111
import com.marklogic.mgmt.resource.security.RoleManager;
12-
import com.marklogic.rest.util.Fragment;
1312

1413
import java.io.File;
15-
import java.util.*;
1614

1715
public class DeployRolesCommand extends AbstractResourceCommand {
1816

19-
private int maxSortAttempts = 100;
17+
// Used internally
18+
private boolean removeRolesAndPermissionsDuringDeployment = false;
19+
private ResourceMapper resourceMapper;
2020

2121
public DeployRolesCommand() {
2222
setExecuteSortOrder(SortOrderConstants.DEPLOY_ROLES);
2323
setUndoSortOrder(SortOrderConstants.DELETE_ROLES);
2424
}
2525

2626
/**
27-
* Overriding this so we can list the files based on role dependencies.
27+
* The set of roles is processed twice. The first time, the roles are saved without any permissions or dependent roles.
28+
* This is to avoid issues where the roles depend on each other or on themselves. The second time, the roles are
29+
* saved with permissions and dependent roles, which is guaranteed to work now that the roles have all been created.
2830
*
29-
* @param dir
30-
* @return
31+
* @param context
3132
*/
3233
@Override
33-
protected File[] listFilesInDirectory(File dir, CommandContext context) {
34-
File[] files = super.listFilesInDirectory(dir);
35-
36-
if (context.getAppConfig().isSortRolesByDependencies() && files != null && files.length > 0) {
37-
if (logger.isInfoEnabled()) {
38-
logger.info("Sorting role files by role dependencies");
39-
}
40-
List<RoleFile> roleFiles = sortFilesBasedOnRoleDependencies(files, context);
41-
files = new File[files.length];
42-
for (int i = 0; i < roleFiles.size(); i++) {
43-
files[i] = roleFiles.get(i).file;
44-
}
34+
public void execute(CommandContext context) {
35+
removeRolesAndPermissionsDuringDeployment = true;
36+
if (logger.isInfoEnabled()) {
37+
logger.info("Deploying roles without any permissions or dependent roles");
4538
}
46-
47-
return files;
39+
super.execute(context);
40+
if (logger.isInfoEnabled()) {
41+
logger.info("Deploying roles with permissions and dependent roles");
42+
}
43+
removeRolesAndPermissionsDuringDeployment = false;
44+
super.execute(context);
4845
}
4946

50-
protected List<RoleFile> sortFilesBasedOnRoleDependencies(File[] files, CommandContext context) {
51-
List<RoleFile> roleFiles = new ArrayList<>();
52-
if (files == null || files.length < 1) {
53-
return roleFiles;
54-
}
55-
PayloadParser parser = new PayloadParser();
56-
for (File f : files) {
57-
RoleFile rf = new RoleFile(f);
58-
String payload = copyFileToString(f, context);
59-
if (parser.isJsonPayload(payload)) {
60-
JsonNode json = parser.parseJson(payload);
61-
rf.role.setRoleName(json.get("role-name").asText());
62-
if (json.has("role")) {
63-
ArrayNode roles = (ArrayNode) json.get("role");
64-
Iterator<JsonNode> iter = roles.elements();
65-
while (iter.hasNext()) {
66-
rf.role.getRole().add(iter.next().asText());
67-
}
68-
}
69-
} else {
70-
Fragment frag = new Fragment(payload);
71-
rf.role.setRoleName(frag.getElementValue("/node()/m:role-name"));
72-
rf.role.setRole(frag.getElementValues("/node()/m:roles/m:role"));
47+
@Override
48+
protected String adjustPayloadBeforeSavingResource(ResourceManager mgr, CommandContext context, File f, String payload) {
49+
if (removeRolesAndPermissionsDuringDeployment) {
50+
if (resourceMapper == null) {
51+
API api = new API(context.getManageClient(), context.getAdminManager());
52+
resourceMapper = new DefaultResourceMapper(api);
7353
}
74-
roleFiles.add(rf);
54+
Role role = resourceMapper.readResource(payload, Role.class);
55+
role.clearPermissionsAndRoles();
56+
return role.getJson();
7557
}
76-
77-
return keepSortingRoleFilesUntilOrderDoesntChange(roleFiles);
78-
}
79-
80-
/**
81-
* Some sets of role files require multiple sorts until the order no longer changes. The maxSortAttempts class
82-
* attribute controls how many times this command will try to sort the roles.
83-
*
84-
* @param roleFiles
85-
* @return
86-
*/
87-
protected List<RoleFile> keepSortingRoleFilesUntilOrderDoesntChange(List<RoleFile> roleFiles) {
88-
List<RoleFile> previousRoleFiles;
89-
int counter = 0;
90-
do {
91-
previousRoleFiles = roleFiles;
92-
roleFiles = new ArrayList<>();
93-
roleFiles.addAll(previousRoleFiles);
94-
roleFiles = sortRoleFiles(roleFiles);
95-
counter++;
96-
} while (!previousRoleFiles.equals(roleFiles) && counter < maxSortAttempts);
97-
return roleFiles;
98-
}
99-
100-
protected List<RoleFile> sortRoleFiles(List<RoleFile> roleFiles) {
101-
RoleFileComparator comparator = new RoleFileComparator(roleFiles);
102-
Collections.sort(roleFiles, comparator);
103-
return roleFiles;
58+
return payload;
10459
}
10560

10661
protected File[] getResourceDirs(CommandContext context) {
@@ -111,104 +66,5 @@ protected File[] getResourceDirs(CommandContext context) {
11166
protected ResourceManager getResourceManager(CommandContext context) {
11267
return new RoleManager(context.getManageClient());
11368
}
114-
115-
public void setMaxSortAttempts(int maxSortAttempts) {
116-
this.maxSortAttempts = maxSortAttempts;
117-
}
118-
}
119-
120-
/**
121-
* Simple data structure for associating a File that defines a role, and the parsed Role that is used for
122-
* sorting the role files.
123-
*/
124-
class RoleFile {
125-
126-
File file;
127-
Role role;
128-
129-
public RoleFile(File file) {
130-
this.file = file;
131-
this.role = new Role();
132-
this.role.setRole(new ArrayList<String>());
133-
}
134-
135-
@Override
136-
public int hashCode() {
137-
return role.getRoleName().hashCode();
138-
}
139-
140-
@Override
141-
public boolean equals(Object obj) {
142-
return role.getRoleName().equals(((RoleFile)obj).role.getRoleName());
143-
}
14469
}
14570

146-
/**
147-
* This comparator is designed to handle a scenario where two roles are next to each other, but they don't have any
148-
* dependencies in common, nor does one depend on the other. In this scenario, we need to know which role has a
149-
* dependency on a role furthest to the end of the list of roles. In order to know that, we first build up a data
150-
* structure that tracks the highest position of a dependency in the list of roles for each role.
151-
*/
152-
class RoleFileComparator implements Comparator<RoleFile> {
153-
154-
private Map<String, Integer> highestDependencyPositionMap;
155-
156-
public RoleFileComparator(List<RoleFile> roleFiles) {
157-
Map<String, Integer> rolePositions = new HashMap<>();
158-
for (int i = 0; i < roleFiles.size(); i++) {
159-
rolePositions.put(roleFiles.get(i).role.getRoleName(), i);
160-
}
161-
162-
highestDependencyPositionMap = new HashMap<>();
163-
for (RoleFile rf : roleFiles) {
164-
String roleName = rf.role.getRoleName();
165-
int highest = -1;
166-
for (String role : rf.role.getRole()) {
167-
if (rolePositions.containsKey(role)) {
168-
int pos = rolePositions.get(role);
169-
if (pos > highest) {
170-
highest = pos;
171-
}
172-
}
173-
}
174-
highestDependencyPositionMap.put(roleName, highest);
175-
}
176-
}
177-
178-
@Override
179-
public int compare(RoleFile o1, RoleFile o2) {
180-
if (o1.role.getRole().isEmpty() && o2.role.getRole().isEmpty()) {
181-
return 0;
182-
}
183-
if (o1.role.getRole().isEmpty()) {
184-
return -1;
185-
}
186-
if (o2.role.getRole().isEmpty()) {
187-
return 1;
188-
}
189-
if (o2.role.getRole().contains(o1.role.getRoleName())) {
190-
return -1;
191-
}
192-
if (o1.role.getRole().contains(o2.role.getRoleName())) {
193-
return 1;
194-
}
195-
196-
/**
197-
* If the roles aren't dependent on each other, then we want to base this on which role has a dependency further
198-
* to the right.
199-
*/
200-
int o1Pos = highestDependencyPositionMap.get(o1.role.getRoleName());
201-
int o2Pos = highestDependencyPositionMap.get(o2.role.getRoleName());
202-
if (o1Pos > o2Pos) {
203-
return 1;
204-
}
205-
if (o2Pos > o1Pos) {
206-
return -1;
207-
}
208-
209-
/**
210-
* This would be for two roles that depend on the same other role.
211-
*/
212-
return 0;
213-
}
214-
}

src/main/java/com/marklogic/mgmt/api/security/Role.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,23 @@ public Role(API api, String roleName) {
5555
this.roleName = roleName;
5656
}
5757

58+
public void clearPermissionsAndRoles() {
59+
if (role != null) {
60+
role.clear();
61+
}
62+
if (permission != null) {
63+
permission.clear();
64+
}
65+
}
66+
5867
public boolean hasPermissionWithOwnRoleName() {
59-
if (permission != null && roleName != null) {
68+
return hasPermissionWithRoleName(this.roleName);
69+
}
70+
71+
public boolean hasPermissionWithRoleName(String someRoleName) {
72+
if (permission != null && someRoleName != null) {
6073
for (Permission perm : permission) {
61-
if (roleName.equals(perm.getRoleName())) {
74+
if (someRoleName.equals(perm.getRoleName())) {
6275
return true;
6376
}
6477
}

src/test/java/com/marklogic/appdeployer/command/security/DeployRolesWithDependenciesTest.java

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,42 @@
11
package com.marklogic.appdeployer.command.security;
22

33
import com.marklogic.appdeployer.AbstractAppDeployerTest;
4-
import com.marklogic.appdeployer.command.CommandContext;
54
import com.marklogic.mgmt.resource.security.RoleManager;
65
import org.junit.Test;
76

87
import java.io.File;
98

9+
/**
10+
* Each of these tests verifies that the roles in the given config dir can be deployed successfully based on their
11+
* dependencies with one another.
12+
*/
1013
public class DeployRolesWithDependenciesTest extends AbstractAppDeployerTest {
1114

15+
@Test
16+
public void roleWithPermissions() {
17+
appConfig.getConfigDir().setBaseDir(new File("src/test/resources/sample-app/roles-with-permissions"));
18+
19+
initializeAppDeployer(new DeployRolesCommand());
20+
try {
21+
deploySampleApp();
22+
} finally {
23+
undeploySampleApp();
24+
}
25+
}
26+
1227
/**
1328
* This scenario has a mix of XML/JSON files (to verify that we parse each correctly) and a big jumble of roles.
1429
*/
1530
@Test
1631
public void testSorting() {
1732
appConfig.getConfigDir().setBaseDir(new File("src/test/resources/sample-app/roles-with-dependencies"));
1833

19-
DeployRolesCommand command = new DeployRolesCommand();
20-
File[] files = command.listFilesInDirectory(appConfig.getConfigDir().getRolesDir(),
21-
new CommandContext(appConfig, manageClient, null));
22-
23-
assertEquals("role3.json", files[0].getName());
24-
assertEquals("role2.xml", files[1].getName());
25-
assertEquals("role1.json", files[2].getName());
26-
assertEquals("role4.xml", files[3].getName());
27-
assertEquals("role5.json", files[4].getName());
28-
assertEquals("role0.json", files[5].getName());
34+
initializeAppDeployer(new DeployRolesCommand());
35+
try {
36+
deploySampleApp();
37+
} finally {
38+
undeploySampleApp();
39+
}
2940
}
3041

3142
/**
@@ -36,19 +47,12 @@ public void testSorting() {
3647
public void testEvenMoreRoles() {
3748
appConfig.getConfigDir().setBaseDir(new File("src/test/resources/sample-app/even-more-roles-with-dependencies"));
3849

39-
DeployRolesCommand command = new DeployRolesCommand();
40-
File[] files = command.listFilesInDirectory(appConfig.getConfigDir().getRolesDir(),
41-
new CommandContext(appConfig, manageClient, null));
42-
43-
assertEquals("abc-login-role.json", files[0].getName());
44-
assertEquals("abc-ui-developer.json", files[1].getName());
45-
assertEquals("xyz-reader.json", files[2].getName());
46-
assertEquals("xyz-writer.json", files[3].getName());
47-
assertEquals("xyz-admin.json", files[4].getName());
48-
assertEquals("abc-sss-ui-role.json", files[5].getName());
49-
assertEquals("abc-ui-offline-user.json", files[6].getName());
50-
assertEquals("abc-ui-offline-admin.json", files[7].getName());
51-
assertEquals("abc-ui-admin.json", files[8].getName());
50+
initializeAppDeployer(new DeployRolesCommand());
51+
try {
52+
deploySampleApp();
53+
} finally {
54+
undeploySampleApp();
55+
}
5256
}
5357

5458
/**
@@ -59,14 +63,12 @@ public void testEvenMoreRoles() {
5963
public void anotherSortingTest() {
6064
appConfig.getConfigDir().setBaseDir(new File("src/test/resources/sample-app/more-roles-with-dependencies"));
6165

62-
DeployRolesCommand command = new DeployRolesCommand();
63-
File[] files = command.listFilesInDirectory(appConfig.getConfigDir().getRolesDir(),
64-
new CommandContext(appConfig, manageClient, null));
65-
66-
assertEquals("role0.json", files[0].getName());
67-
assertEquals("role2.json", files[1].getName());
68-
assertEquals("role3.json", files[2].getName());
69-
assertEquals("role1.json", files[3].getName());
66+
initializeAppDeployer(new DeployRolesCommand());
67+
try {
68+
deploySampleApp();
69+
} finally {
70+
undeploySampleApp();
71+
}
7072
}
7173

7274
@Test

0 commit comments

Comments
 (0)