Tuesday, November 5, 2024

Version Issue with Custom AssetMetadataPermissionProvider implementation

While implementing custom metadata based permission provider, you can follow this article to implement it. One issue I faced during this implementation that the version access was not working. To fix this modify below methods:

@Override

public TreePermission getTreePermission(Tree tree, TreePermission parentPermission) {

if (PermissionHelpers.isDamPath(tree) || PermissionHelpers.isDamAncestorPath(tree)) {

if (PermissionHelpers.findAncestorAsset(tree) != null) {

return PermissionHelpers.isAncestorAssetOwner(tree, principalNames) ?

             TreePermission.ALL : parentPermission;

} else {

return new EmptyAssetMetadataTreePermission(tree, TreeType.DEFAULT, this);

}

} else if (tree.getPath().startsWith("/" + JcrConstants.JCR_SYSTEM)) {

      // This condition added to allow version to path access

      // This is just an example code, optimize this condition before you use

return TreePermission.ALL;

}

return TreePermission.NO_RECOURSE;

}

Another method:

@Override

public boolean isGranted(Tree tree, PropertyState property, long permissions) {

TreeType type = treeTypeProvider.getType(tree);

switch (type) {

case HIDDEN:

return true;

case VERSION:

Tree evalTree = getEvaluationTree(tree);

if (evalTree == null) {

return false;

}

if (evalTree.exists()) {

return internalIsGranted(evalTree, property, permissions);

} else {

return false;

}

case INTERNAL:

return false;

default:

return internalIsGranted(tree, property, permissions);

}

} 

Core logic to test metadata conditions to meet business requirement is put in the below private method which is called in the above isGranted method.

private boolean internalIsGranted(@NotNull Tree tree, @Nullable PropertyState property,

   long permissions) {

boolean answer = false;

if (PermissionHelpers.isAncestorAssetOwner(tree, principalNames)) {

answer = true;

}

if (property != null) {

LOG.debug("isGranted: {}@{} ({}) = {}", tree.getPath(), property.getName(),

             permissions, answer);

} else {

LOG.debug("isGranted: {} ({}) = {}", tree.getPath(), permissions, answer);

}

return answer;

}

PermissionHelpers here is a general utility class like below:


package com.myorg.vivek.core.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.tree.TreeLocation;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.dam.api.DamConstants;
import com.myorg.vivek.core.constants.AppConstants;
/**
* Utility class for permission related operations
*/
public class PermissionHelpers {
private PermissionHelpers() {
// private constructor to hide the implicit public one
}
public static Tree findAncestorAsset(Tree tree) {
if (null == tree) {
return null;
}
if (isAsset(tree)) {
return tree;
} else if (tree.getPath().contains(JcrConstants.JCR_CONTENT)) {
while (!tree.isRoot()) {
tree = tree.getParent();
if (isAsset(tree)) {
return tree;
}
}
}
return null;
}
public static boolean isAsset(Tree tree) {
if (null != tree && tree.hasProperty(JcrConstants.JCR_PRIMARYTYPE)) {
return "dam:Asset".equals(tree.getProperty(JcrConstants.JCR_PRIMARYTYPE)
.getValue(Type.STRING));
} else {
return false;
}
}
public static boolean isDamAncestorPath(final Tree tree) {
return tree != null && DamConstants.MOUNTPOINT_ASSETS.startsWith(tree.getPath());
}
public static boolean isAncestorAssetOwner(Tree tree, Set<String> principalNames) {
if (tree == null) {
return false;
}
Tree asset = PermissionHelpers.findAncestorAsset(tree);
if (asset == null) {
return false;
}
String assetPath = asset.getPath();
if (isProtectedPath(assetPath)) {
// custom logic to return true based on business logic.
String isPrivate = getAssetMetadata(asset, "isPrivate").get(0);
if ("true".equals(isPrivate)) {
String[] allowedPersonsArray =
getAssetMetadata(asset, "allowedPersons").toArray(String[]::new);
return (CollectionUtils.containsAny(principalNames, allowedPersonsArray));
}
}
// no access given for paths that are not meant to be handled by this
return false;
}
private static List<String> getAssetMetadata(Tree asset, String metadataPropertyName) {
if (asset.hasChild(JcrConstants.JCR_CONTENT)) {
Tree assetContent = asset.getChild(JcrConstants.JCR_CONTENT);
if (assetContent.hasChild(DamConstants.METADATA_FOLDER)) {
Tree assetMetadata = assetContent.getChild(DamConstants.METADATA_FOLDER);
if (assetMetadata.getProperty(metadataPropertyName) != null) {
Type type = assetMetadata.getProperty(metadataPropertyName).getType();
if (type.equals(Type.STRINGS)) {
return StreamSupport.stream(
assetMetadata.getProperty(metadataPropertyName).getValue(Type.STRINGS)
.spliterator(), false).map(propValue -> propValue.toLowerCase())
.collect(Collectors.toList());
} else {
List<String> result = new ArrayList<>();
result.add(assetMetadata.getProperty(metadataPropertyName)
.getValue(Type.STRING).toLowerCase());
return result;
}
}
}
}
return List.of("");
}
private static boolean isProtectedPath(final String assetPath) {
// List of assets to be protected by this permission provider
return assetPath.startsWith(AppConstants.PRIVATE_FOLDER_ROOT);
}
public static boolean isDamPath(Tree tree) {
return null != tree && tree.getPath().startsWith(DamConstants.MOUNTPOINT_ASSETS);
}
public static Tree getTreeFromLocation(TreeLocation location) {
Tree tree = (location.getProperty() == null) ? location.getTree()
: location.getParent().getTree();
while (tree == null && !PathUtils.denotesRoot(location.getPath())) {
location = location.getParent();
tree = location.getTree();
}
return tree;
}
}

No comments:

Post a Comment

CDN | Clearing Cloudflare cache

In order to clear Cloudflare cache automatically via code, follow below steps: 1. Develop Custom TransportHandler Develop a custom Trans...