May 20, 2020
Estimated Post Reading Time ~

Copy Users, Groups, Permissions (ACLs) between Adobe CQ Instances

It is common to have multiple CQ Publisher instances in different environments (DEV, QA, PROD, etc.). When you make user administration and security changes toward users and groups on the production server, you may want to bring them down to the lower DEV and QA environments so they don't go out of sync. In such a scenario, you have to copy both user/group definition and the permission definition. This post shows you how to copy users and groups you set up on one CQ instance (source CQ) to another instance (destination CQ), and how to bring over the permissions (resource-based ACLs) from the source CQ to the destination CQ.

Copy users and groups definition
To copy over users and groups definition from one CQ to another, we'd take the approach of packaging users/groups definition up into a package, then install the package to the destination CQ. We simply use CQ's package capability to package up everything under /home which holds users/groups definition into a package with AC Handling set to 'overwrite.' The 'overwrite' access control tells JcrPackageDefinition to overwrite the ACLs in the destination CQ upon installing a package. When copy over users/groups definition via package installation, we want the underlying ACLs also be copied over (overwritten).

To create such package, go on to the source CQ to create a package with the following package named UsersGroups.zip with filter and AC Handling:



Then, move on to the destination CQ side, install the above created UsersGroups.zip package. The 'overwrite' AC Handling will tell the package importer to overwrite ACL's upon installation. The installation process of the package will complete in a minute but the front-end javascript will hang for unknown reason (may be related to the ACLs?) until you use another window to log yourself (admin user) back in. When you're back in, check out User Admin console on the destination CQ instance (e.g., at http://<host>:4502/useradmin) to make sure now the console reflects what's there on the source CQ.
copy permissions (ACLs) pertaining to all nodes
Policies that control how users and groups can access resources are saved in ACL nodes:

Resource-based ACLs are stored per resource/node in a special child rep:policy node. This one will have a list of rep:GrantACE child nodes (usually named allow, allow0,...) for grant access control entries and rep:DenyACE child nodes (usually named deny, deny0,...) for deny access control entries.

Each ACE node has a rep:principalName STRING property pointing to the user or group this ACE belongs to, and a rep:privileges NAME multi-value property, containing all the privileges of this ACE.

- How Resource-based ACLs are stored, Jackrabbit Wiki - Access Control/Authorization






These ACLs that contains ACEs are all over the places under content nodes. Unfortunately, the out-of-the-box package builder does not support to extract these nodes out, we can use a 'create-package' tool developed by Yogesh Upadhyay to create a package based on XPath to extract ACL nodes :

Download create_package-4.zip package written by Yogesh Upadhyay.
Manually install create_package-4.zip package to your source CQ.
Go on access the source CQ: <host>:<port>/apps/tools/components/createPackage/run.html
Create a package named ACLDump with all nodes of rep:ACL type, select AC handling behavior of 'overwrite' to assure the ACLs are overwritten upon installing the package.





Lastly, on the destination CQ instance, install ACLDump.zip package to overwrite all ACL permissions on the destination CQ. Installation should complete in a minute or two but the front-end javascript will hang until you use another window to log back in.

Congratulations, you've just completed copying over users, groups, and security policies from one CQ to another.

code walkthrough
The following code snippets are extracted from the POST.jsp file of createPackage by yogi1306 on sourceforge. In his code, he uses JcrPackageManager to create a package of matched resources for download.


<%@include file="/libs/foundation/global.jsp"%> // 1

...

<%
Session sess = resourceResolver.adaptTo(Session.class);
BundleContext bundleContext = sling.getService(BundleContext.class);

...

Iterator<Resource> iter = resourceResolver.findResources(path+xpath,"xpath"); // 2
JcrPackageManager packMgr = PackagingService.getPackageManager(sess); // 3

// check if package already exists
String packPath = pkgGroupName + "/" + packageName + ".zip";
Node packages = packMgr.getPackageRoot();
if (packages.hasNode(packPath)) {
packages.getNode(packPath).remove();
packages.getSession().save();
}
JcrPackage pack = packMgr.create(pkgGroupName,packageName,null);
//packMgr.ensureVersion(pack);
DefaultWorkspaceFilter filters = new DefaultWorkspaceFilter();
out.println("<br> Adding following filter path <br> ");
while(iter.hasNext()){
Resource curRes = iter.next();

//Don't include exlude path in the filter.
if(!isExclude(curRes.getPath(),excludePath)){
out.println("<br><font color=red>"+curRes.getPath()+"</font>");
filters.add(new PathFilterSet(curRes.getPath())); // 4

}
}
JcrPackageDefinition jcrPackageDefinition = pack.getDefinition();
jcrPackageDefinition.setFilter(filters, true);
jcrPackageDefinition.set("acHandling",acHandling,false);

packMgr.assemble(pack, null); // 5
String serverName = (request.getProtocol().split("/")[0]).toLowerCase()+"://"+request.getServerName();
if(!"".equals(request.getServerPort())){
serverName+=":"+request.getServerPort();
}

String docroot = serverName +"/crx/packmgr/service.jsp";
out.println("<br><br><strong>Package Creation Done with name "+ packageName +"</strong>");
%>

<%!
boolean isExclude(String path,String exludePath){
if("".equals(exludePath) || null==exludePath){
return false;
}else{
String[] allExcludePath = exludePath.split(",");
for(String eachPath:allExcludePath){
if(path.startsWith(eachPath)){
return true;
}
}
}
return false;
}

%>
<form method="get" action="<%=docroot %>"> // 6
<input name="cmd" id="cmd" value="get"></input>
<input name="name" id="name" value="<%=Text.escape(packageName) %>"></input>
<input name="group" id="group" value="<%=Text.escape(pkgGroupName) %>"></input>
<input value="Download Package"></input>
</form>
</body>
</html>

[1] declares the sling, cq and jstl taglibs and exposes the regularly used scripting objects (e.g. resourceResolver) defined by the <cq:defineObjects /> tag.
[2] Searches for resources using the given query (e.g. /jcr:root//element(*,rep:ACL)) formulated in "jcr xpath" language.
[3] Returns a repository-based package manager.
[4] Add matched resources to be packaged up to the filter of the package definition.
[5] Assemble a package based on its definition. Generate the package under repository's default location (/etc/packages/<package-group>/), which is available for download through CQ Package Manager.
[6] A button to download the package available in the CQ Package Manager.

References
User Administration and Security
User, Group and Access Rights Administration
Jackrabbit Wiki - Access Control / Authorization
Creating packages based on query parameters
How to create a package based on Xpath in CQ5 / WEM
Querying and Searching using JCR


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.