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

Commit a1977fc

Browse files
committed
#441 Can now include capability queries in XML role payloads
JSON doesn't work yet due to an ML bug. The main fix here was to include capability-queries in the Role class. But also improved RoleObjectNodesSorter so that it won't drop any data.
1 parent 125e39f commit a1977fc

File tree

12 files changed

+225
-10
lines changed

12 files changed

+225
-10
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ protected String determineDatabaseNameForDatabaseResourceDirectory(CommandContex
532532
logger.info("Found database file with same name, minus its extension, as the database resource directory; " +
533533
"file: " + f);
534534
String payload = copyFileToString(f, context);
535-
String databaseName = new PayloadParser().getPayloadFieldValue(payload, "database-name");
535+
String databaseName = payloadParser.getPayloadFieldValue(payload, "database-name");
536536
logger.info("Associating database resource directory with database: " + databaseName);
537537
return databaseName;
538538
}
@@ -589,4 +589,8 @@ public boolean isSupportsResourceMerging() {
589589
public void setSupportsResourceMerging(boolean supportsResourceMerging) {
590590
this.supportsResourceMerging = supportsResourceMerging;
591591
}
592+
593+
protected PayloadParser getPayloadParser() {
594+
return payloadParser;
595+
}
592596
}

src/main/java/com/marklogic/appdeployer/command/security/DeployRolesCommand.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public class DeployRolesCommand extends AbstractResourceCommand implements Suppo
3636
private ObjectNodesSorter objectNodesSorter = new RoleObjectNodesSorter();
3737
private Set<String> defaultRolesToNotUndeploy;
3838

39+
// Keeps track of the original payloads (after token parsing) for XML files. These are needed to account for
40+
// data that is dropped when a payload is deserialized into a Role class, such as "capability-queries". When
41+
// the role is actually saved, this payload will be used instead of the serialization of the associated Role instance.
42+
private Map<String, String> roleNamesAndXmlPayloads = new HashMap<>();
43+
3944
public DeployRolesCommand() {
4045
setExecuteSortOrder(SortOrderConstants.DEPLOY_ROLES);
4146
setUndoSortOrder(SortOrderConstants.DELETE_ROLES);
@@ -61,6 +66,14 @@ protected boolean useCmaForDeployingResources(CommandContext context) {
6166
return true;
6267
}
6368

69+
/**
70+
* Similar to useCmaForDeployingResources, this tells the parent class to always build a Configuration, even if
71+
* CMA isn't available. And when it's time to deploy the configuration, a check is made to see if CMA is really
72+
* available and if it's configured to be used.
73+
*
74+
* @param context
75+
* @return
76+
*/
6477
@Override
6578
public boolean cmaShouldBeUsed(CommandContext context) {
6679
return true;
@@ -90,7 +103,7 @@ protected void deployConfiguration(CommandContext context, Configuration config)
90103
return;
91104
}
92105

93-
if (objectNodesSorter != null) {
106+
if (objectNodesSorter != null && roleNodes.size() > 1) {
94107
logger.info("Sorting roles before they are saved");
95108
roleNodes = objectNodesSorter.sortObjectNodes(roleNodes);
96109
config.setRoles(roleNodes);
@@ -121,11 +134,32 @@ protected void submitRolesIndividually(CommandContext context, List<ObjectNode>
121134
});
122135

123136
roleNodes.forEach(roleNode -> {
124-
SaveReceipt receipt = saveResource(roleManager, context, roleNode.toString());
137+
String roleName = roleNode.get("role-name").asText();
138+
String payload = this.roleNamesAndXmlPayloads.containsKey(roleName) ? this.roleNamesAndXmlPayloads.get(roleName) : roleNode.toString();
139+
SaveReceipt receipt = saveResource(roleManager, context, payload);
125140
afterResourceSaved(roleManager, context, null, receipt);
126141
});
127142
}
128143

144+
/**
145+
* Overridden so that an XML payload can be saved so that it can be used later when the role is actually saved as
146+
* opposed to the serialization of a Role instance, which as of 4.3.x will not include "capability-queries" for an
147+
* XML payload.
148+
*
149+
* @param context
150+
* @param f
151+
* @return
152+
*/
153+
@Override
154+
protected String readResourceFromFile(CommandContext context, File f) {
155+
String payload = super.readResourceFromFile(context, f);
156+
if (!getPayloadParser().isJsonPayload(payload)) {
157+
String roleName = getPayloadParser().getPayloadFieldValue(payload, "role-name");
158+
this.roleNamesAndXmlPayloads.put(roleName, payload);
159+
}
160+
return payload;
161+
}
162+
129163
/**
130164
* If a role refers to itself via permissions, that role won't be created by CMA. Instead, a separate CMA request
131165
* is constructed, with each such role only having a role-name, and then immediately submitted so that the roles
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.marklogic.mgmt.api.security;
2+
3+
import com.fasterxml.jackson.databind.node.ObjectNode;
4+
5+
public class CapabilityQuery {
6+
7+
private String capability;
8+
// Note that an XML payload will not deserialize into this, nor will it deserialize into an Object
9+
private ObjectNode query;
10+
11+
public String getCapability() {
12+
return capability;
13+
}
14+
15+
public void setCapability(String capability) {
16+
this.capability = capability;
17+
}
18+
19+
public ObjectNode getQuery() {
20+
return query;
21+
}
22+
23+
public void setQuery(ObjectNode query) {
24+
this.query = query;
25+
}
26+
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ public class Role extends Resource {
3636
@XmlElementWrapper(name = "collections")
3737
private List<String> collection;
3838

39+
// This does not yet have an XmlElementWrapper on it as CapabilityQuery does not yet support XML marshalling;
40+
// unclear how to use e.g. XmlAnyElement on the query portion of it
41+
private List<CapabilityQuery> capabilityQuery;
42+
3943
public Role() {
4044
}
4145

@@ -203,4 +207,11 @@ public void setCollection(List<String> collection) {
203207
this.collection = collection;
204208
}
205209

210+
public List<CapabilityQuery> getCapabilityQuery() {
211+
return capabilityQuery;
212+
}
213+
214+
public void setCapabilityQuery(List<CapabilityQuery> capabilityQuery) {
215+
this.capabilityQuery = capabilityQuery;
216+
}
206217
}

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,36 @@ public class RoleObjectNodesSorter implements ObjectNodesSorter {
2222
*/
2323
@Override
2424
public List<ObjectNode> sortObjectNodes(List<ObjectNode> objectNodes) {
25+
// Construct a list of roles so they can be sorted
2526
List<Role> roles = new ArrayList<>();
27+
28+
// Construct a map of roles to object nodes so that the original object nodes can be added to the list.
29+
// This is to resolve bug #441, where capability-query's are being dropped because the Role class doesn't
30+
// support them. It may be better to refactor this to not deserialize into Role instances so that ObjectNodes
31+
// are used the entire time, even though we'd lose some of the convenience methods provided by the Role class.
32+
final Map<String, ObjectNode> roleMap = new HashMap();
33+
2634
ObjectReader reader = ObjectMapperFactory.getObjectMapper().readerFor(Role.class);
27-
for (ObjectNode node : objectNodes) {
35+
for (ObjectNode objectNode : objectNodes) {
2836
try {
29-
roles.add(reader.readValue(node));
37+
Role role = reader.readValue(objectNode);
38+
roles.add(role);
39+
roleMap.put(role.getRoleName(), objectNode);
3040
} catch (IOException e) {
31-
throw new RuntimeException("Unable to read ObjectNode into Role; node: " + node, e);
41+
throw new RuntimeException("Unable to read ObjectNode into Role; JSON: " + objectNode, e);
3242
}
3343
}
3444

3545
roles = sortRoles(roles);
3646

37-
List<ObjectNode> newList = new ArrayList<>();
38-
roles.forEach(role -> newList.add(role.toObjectNode()));
39-
return newList;
47+
List<ObjectNode> sortedNodes = new ArrayList<>();
48+
roles.forEach(role -> {
49+
// Sanity check; we don't ever expect to get back a role in the sorted list that isn't in the roleMap
50+
if (roleMap.containsKey(role.getRoleName())) {
51+
sortedNodes.add(roleMap.get(role.getRoleName()));
52+
}
53+
});
54+
return sortedNodes;
4055
}
4156

4257

src/main/java/com/marklogic/rest/util/Fragment.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public Fragment(String xml, Namespace... namespaces) {
3131
List<Namespace> list = new ArrayList<Namespace>();
3232
list.add(Namespace.getNamespace("arp", "http://marklogic.com/manage/alert-rule/properties"));
3333
list.add(Namespace.getNamespace("c", "http://marklogic.com/manage/clusters"));
34-
list.add(Namespace.getNamespace("cert", "http://marklogic.com/xdmp/x509"));
34+
list.add(Namespace.getNamespace("cert", "http://marklogic.com/xdmp/x509"));
35+
list.add(Namespace.getNamespace("cts", "http://marklogic.com/cts"));
3536
list.add(Namespace.getNamespace("db", "http://marklogic.com/manage/databases"));
3637
list.add(Namespace.getNamespace("es", "http://marklogic.com/entity-services"));
3738
list.add(Namespace.getNamespace("f", "http://marklogic.com/manage/forests"));

src/test/java/com/marklogic/appdeployer/command/databases/ClearDatabaseTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public void teardown() {
2727
public void modulesDatabase() {
2828
initializeAppDeployer(new DeployRestApiServersCommand(true), buildLoadModulesCommand());
2929

30+
appConfig.setRestPort(8004);
3031
appDeployer.deploy(appConfig);
3132

3233
DatabaseManager mgr = new DatabaseManager(manageClient);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.marklogic.appdeployer.command.security;
2+
3+
import com.marklogic.appdeployer.AbstractAppDeployerTest;
4+
import com.marklogic.mgmt.resource.security.RoleManager;
5+
import com.marklogic.rest.util.Fragment;
6+
import org.junit.jupiter.api.Test;
7+
import org.springframework.web.client.HttpServerErrorException;
8+
9+
import java.io.File;
10+
11+
import static org.junit.jupiter.api.Assertions.*;
12+
13+
public class DeployRoleWithCapabilityQueryTest extends AbstractAppDeployerTest {
14+
15+
@Test
16+
void jsonRole() {
17+
appConfig.getFirstConfigDir().setBaseDir(new File("src/test/resources/sample-app/qbac-json-role"));
18+
19+
initializeAppDeployer(new DeployRolesCommand());
20+
21+
final String message = "ML 10.0-7 is not able to deploy a JSON role with capability queries; expecting to get an XDMP-NOTQUERY error: ";
22+
try {
23+
appConfig.getCmaConfig().setDeployRoles(true);
24+
appDeployer.deploy(appConfig);
25+
fail("Expected error: " + message);
26+
} catch (HttpServerErrorException ex) {
27+
assertTrue(ex.getMessage().contains("XDMP-NOTQUERY"), message + ex.getMessage());
28+
}
29+
30+
// Make sure error occurs with CMA disabled too
31+
try {
32+
appConfig.getCmaConfig().setDeployRoles(false);
33+
appDeployer.deploy(appConfig);
34+
fail("Expected error: " + message);
35+
} catch (HttpServerErrorException ex) {
36+
assertTrue(ex.getMessage().contains("XDMP-NOTQUERY"), message + ex.getMessage());
37+
}
38+
}
39+
40+
@Test
41+
void xmlRole() {
42+
appConfig.getFirstConfigDir().setBaseDir(new File("src/test/resources/sample-app/qbac-xml-role"));
43+
initializeAppDeployer(new DeployRolesCommand());
44+
45+
try {
46+
// This is necessary because the CMA config is JSON, and the XML capability-queries cannot be
47+
// converted into JSON
48+
appConfig.getCmaConfig().setDeployRoles(false);
49+
appDeployer.deploy(appConfig);
50+
51+
RoleManager mgr = new RoleManager(manageClient);
52+
assertTrue(mgr.exists("a-qbac-xml-role"));
53+
54+
Fragment props = mgr.getPropertiesAsXml("a-qbac-xml-role");
55+
assertEquals("hello", props.getElementValue("/m:role-properties/m:queries/m:capability-query[m:capability = 'read']" +
56+
"/m:query/cts:word-query/cts:text"), "Verifying that the query was saved");
57+
assertEquals("world", props.getElementValue("/m:role-properties/m:queries/m:capability-query[m:capability = 'update']" +
58+
"/m:query/cts:word-query/cts:text"), "Verifying that the query was saved");
59+
} finally {
60+
undeploySampleApp();
61+
}
62+
}
63+
64+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class ManageAmpsTest extends AbstractManageResourceTest {
2121

2222
@Test
2323
public void ampLoadedBeforeModules() {
24+
appConfig.setRestPort(8004);
2425
appConfig.setConfigDir(new ConfigDir(new File("src/test/resources/sample-app/real-amp")));
2526

2627
initializeAppDeployer(new DeployUsersCommand(), new DeployRestApiServersCommand(true),
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"role-name": "a-qbac-role",
3+
"description": "testing",
4+
"capability-query": [
5+
{
6+
"capability": "read",
7+
"query": {
8+
"word-query": {
9+
"text": {
10+
"lang": "en",
11+
"_value": "hello"
12+
}
13+
}
14+
}
15+
},
16+
{
17+
"capability": "update",
18+
"query": {
19+
"word-query": {
20+
"text": {
21+
"lang": "en",
22+
"_value": "world"
23+
}
24+
}
25+
}
26+
}
27+
]
28+
}

0 commit comments

Comments
 (0)