cancel
Showing results for 
Search instead for 
Did you mean: 

Authority disable a permission ?

ribz33
Champ on-the-rise
Champ on-the-rise
Hi,

Is it possible that an authority can restrict a permission ?

Example :
I want to make a dynamic authority, if confidential aspect is found i want
to restrict permission on node and not give more rights.

Is it possible ?
3 REPLIES 3

andy
Champ on-the-rise
Champ on-the-rise
Hi

Yes and no. It is possible to deny. But at the moment any allow-allows.

It would be better to remove inherited permissions for a confidential item and then add the permissions you want for dynamic authorities that can see confidential stuff.

You could add a rule to manipulate the permissions and inheritance, or bind policy behaviour to the aspect, or wrap the add aspects calls with  a method interceptor.

Andy

burriad
Champ in-the-making
Champ in-the-making
Hi,

I have found an unconventional approach to this problem: I wrote my own AccessDecisionVoter which allows/denies based on a confidentiality property. Additionally, since alfresco uses an affirmativeBased approach to voters (something I find hard to understand, among others…), I needed to encapsulate it in a CompositeVoter, which bundles the vote of the ACLEntryVoter and of myConfidentialityVoter.

Here's the code:

public class CompositeVoter implements AccessDecisionVoter, InitializingBean {

   List<AccessDecisionVoter> containedVoters;
   
   private int combine(int currentResult, int newResult) {
      // Deny always wins
      if (currentResult == ACCESS_DENIED || newResult == ACCESS_DENIED) {
         return ACCESS_DENIED;
      }
      // Allow precedes abstain
      if (currentResult == ACCESS_GRANTED || newResult == ACCESS_GRANTED) {
         return ACCESS_GRANTED;
      }
         
      return ACCESS_ABSTAIN;
   }
   
   @Override
   public boolean supports(ConfigAttribute arg0) {
      boolean result = false;
      for (AccessDecisionVoter voter : containedVoters) {
         result |= voter.supports(arg0);
      }
      return result;
   }
   @Override
   public boolean supports(Class arg0) {
      boolean result = false;
      for (AccessDecisionVoter voter : containedVoters) {
         result |= voter.supports(arg0);
      }
      return result;
   }
   @Override
   public int vote(Authentication arg0, Object arg1,
         ConfigAttributeDefinition arg2) {
      int result = ACCESS_ABSTAIN;
      for (AccessDecisionVoter voter : containedVoters) {
         // Could be optimized: on a deny, you can give up immediately
         result = combine(result, voter.vote(arg0, arg1, arg2));
      }      
      return result;
   }
   @Override
   public void afterPropertiesSet() throws Exception {
      assert containedVoters != null;
   }   
   public void setContainedVoters(List<AccessDecisionVoter> containedVoters) {
      this.containedVoters = containedVoters;
   }
}
And the confidentiality voter:

public class ConfidentialityVoter implements InitializingBean, AccessDecisionVoter {

   private Logger logger = Logger.getLogger(ConfidentialityVoter.class);

   private static final String ACL_NODE = "ACL_NODE";
    private static final String ACL_PARENT = "ACL_PARENT";
    private static final String ACL_ALLOW = "ACL_ALLOW";
    private static final String ACL_METHOD = "ACL_METHOD";
    private static final String TOP_SECRET = "AllowStrengVertraulich";
    private static final String RESTRICTED = "AllowVertraulich";
    private static final String INTERNAL = "AllowIntern";
   
   
    private static final QName nameProp = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "name");
   
   private AuthenticationService authenticationService;
   private NodeService nodeService;
    private NamespacePrefixResolver nspr;
    private AuthorityService authorityService;
    private PermissionServiceSPI permissionService;
   
    private PermissionReference allowTopSecret = SimplePermissionReference.getPermissionReference(MyModel.QNAME_PROP_INFORMATIONSSCHUTZ, TOP_SECRET);
    private PermissionReference allowRestricted = SimplePermissionReference.getPermissionReference(MyModel.QNAME_PROP_INFORMATIONSSCHUTZ, RESTRICTED);
    private PermissionReference allowInternal = SimplePermissionReference.getPermissionReference(MyModel.QNAME_PROP_INFORMATIONSSCHUTZ, INTERNAL);
   
   @Override
   public void afterPropertiesSet() throws Exception {
      logger.debug("Entered afterPropertiesSet");
      assert authenticationService != null;
      assert nodeService != null;
      assert nspr != null;
   }

   @Override
   public boolean supports(ConfigAttribute arg0) {
      logger.debug("Entered supports (configAttribute : " + arg0.getAttribute() + ")");
      if ((arg0.getAttribute() != null) && (arg0.getAttribute().startsWith(ACL_NODE))) {
         return true;
      }
      else {
         return false;
      }
   }

   @Override
   public boolean supports(Class arg0) {
      logger.debug("Entered supports (class)");
      return MethodInvocation.class.isAssignableFrom(arg0);
   }
   
   @Override
   public int vote(Authentication authentication, Object obj, ConfigAttributeDefinition attributes) {
      logger.debug("Entered vote: obj = " + obj.toString() + ", config = " + attributes.toString());
      
      // The system user can do everything
      if (authenticationService.isCurrentUserTheSystemUser()) {
         logger.debug("system user -> allow");
         return ACCESS_GRANTED;
      }
      
      // Find the called method and the applicable config attributes
      List<ConfigAttributeHelper> supportedDefinitions = this.extractSupportedDefinitions(attributes);
        MethodInvocation invocation = (MethodInvocation) obj;
        Method method = invocation.getMethod();
        Class[] params = method.getParameterTypes();
      
        // Check for the permissions
        for (ConfigAttributeHelper attribute : supportedDefinitions) {
           logger.debug("Processing attribute " + attribute.toString());
           // The parameter needs to be interpreted as a node ref
           if (NodeRef.class.isAssignableFrom(params[attribute.parameter])) {
              NodeRef node = (NodeRef) invocation.getArguments()[attribute.parameter];
              
              logger.debug("Authorities: " + authorityService.getAuthorities().toString());
              
              // The aspect must be present on the node
              //if (nodeService.hasAspect(node, MyModel.QNAME_ASPECT_CONFIDENTIALITY)) {
              if (this.hasConfidentialityAspect(node) == Boolean.TRUE) {
                 String confidentiality = (String) nodeService.getProperty(node, MyModel.QNAME_PROP_INFORMATIONSSCHUTZ);
                 logger.debug("Found node with confidentiality '" + confidentiality+ "'");
              
                 // For unrestricted nodes, access is granted no matter what
                 if ("unrestricted".contentEquals(confidentiality)) {
                    return ACCESS_GRANTED;
                 }
                 if (INTERNAL.contentEquals(confidentiality)) {
                    if (permissionService.hasPermission(node, this.allowInternal) != AccessStatus.ALLOWED) {
                       logger.debug("ACCESS DENIED on node: " + nodeService.getProperty(node, nameProp));
                    }
                    return (permissionService.hasPermission(node, this.allowInternal) == AccessStatus.ALLOWED ?
                          ACCESS_GRANTED : ACCESS_DENIED);
                 }
                 
                 // For restricted nodes, the principal must have the restricted allowed permission
                 if (RESTRICTED.contentEquals(confidentiality)) {
                    if (permissionService.hasPermission(node, this.allowRestricted) != AccessStatus.ALLOWED) {
                       logger.debug("ACCESS DENIED on node: " + nodeService.getProperty(node, nameProp));
                    }
                    return (permissionService.hasPermission(node, this.allowRestricted) == AccessStatus.ALLOWED ?
                          ACCESS_GRANTED : ACCESS_DENIED);
                 }
                 // For top secret nodes, the principal must have the top secret permission
                 if (TOP_SECRET.contentEquals(confidentiality)) {
                    if (permissionService.hasPermission(node, this.allowTopSecret) != AccessStatus.ALLOWED) {
                       logger.debug("ACCESS DENIED on node: " + nodeService.getProperty(node, nameProp));
                    }
                    return (permissionService.hasPermission(node, this.allowTopSecret) == AccessStatus.ALLOWED ?
                          ACCESS_GRANTED : ACCESS_DENIED);
                 }
              }
           }
        }
      
        // if no match, then abstain
        logger.debug("No match -> abstain");
      return ACCESS_ABSTAIN;
   }

   public void setAuthenticationService(AuthenticationService authenticationService) {
      this.authenticationService = authenticationService;
   }

   public AuthenticationService getAuthenticationService() {
      return authenticationService;
   }

   public void setNodeService(NodeService nodeService) {
      this.nodeService = nodeService;
   }

   public NodeService getNodeService() {
      return nodeService;
   }
   
    public NamespacePrefixResolver getNamespacePrefixResolver()
    {
        return nspr;
    }

    public void setNamespacePrefixResolver(NamespacePrefixResolver nspr)
    {
        this.nspr = nspr;
    }

    public void setAuthorityService(AuthorityService authorityService) {
      this.authorityService = authorityService;
   }

   public AuthorityService getAuthorityService() {
      return authorityService;
   }

   public void setPermissionService(PermissionServiceSPI permissionService) {
      this.permissionService = permissionService;
   }

   public PermissionServiceSPI getPermissionService() {
      return permissionService;
   }
   
   // Private methods
   private List<ConfigAttributeHelper> extractSupportedDefinitions(ConfigAttributeDefinition config)
    {
        List<ConfigAttributeHelper> definitions = new ArrayList<ConfigAttributeHelper>(2);
        Iterator iter = config.getConfigAttributes();
       
        while (iter.hasNext()) {
            ConfigAttribute attr = (ConfigAttribute) iter.next();
            logger.debug("Config attribute: " + attr.getAttribute());
           
            if (this.supports(attr)) {
                logger.debug("Matched");
               definitions.add(new ConfigAttributeHelper(attr));
            }
        }
        return definitions;
    }
   
   private Boolean hasConfidentialityAspect(final NodeRef node) {
      //if (nodeService.hasAspect(node, MyModel.QNAME_ASPECT_CONFIDENTIALITY))
      return AuthenticationUtil.runAs(
            new AuthenticationUtil.RunAsWork<Boolean>() {
               @Override
               public Boolean doWork() throws Exception {
                  return nodeService.hasAspect(node, MyModel.QNAME_ASPECT_NEEDED);
               }            
            },
            AuthenticationUtil.getSystemUserName());
   }

   private class ConfigAttributeHelper
    {
       String attr;
       
        String typeString;

        SimplePermissionReference required;

        int parameter;

        String authority;
       
        public String toString() {
           return attr;
        }

        ConfigAttributeHelper(ConfigAttribute attr)
        {
           logger.debug("Create ConfigAttributeHelper with attr = " + attr.getAttribute());
           
           this.attr = attr.getAttribute();
           
            StringTokenizer st = new StringTokenizer(attr.getAttribute(), ".", false);
            if (st.countTokens() < 1)
            {
                throw new ACLEntryVoterException("There must be at least one token in a config attribute");
            }
            typeString = st.nextToken();

            if (!(typeString.equals(ACL_NODE) || typeString.equals(ACL_PARENT) || typeString.equals(ACL_ALLOW) || typeString
                    .equals(ACL_METHOD)))
            {
                throw new ACLEntryVoterException("Invalid type: must be ACL_NODE, ACL_PARENT or ACL_ALLOW");
            }

            if (typeString.equals(ACL_NODE) || typeString.equals(ACL_PARENT))
            {
                if (st.countTokens() != 3)
                {
                    throw new ACLEntryVoterException("There must be four . separated tokens in each config attribute");
                }
                String numberString = st.nextToken();
                String qNameString = st.nextToken();
                String permissionString = st.nextToken();

                parameter = Integer.parseInt(numberString);
                QName qName = QName.createQName(qNameString, nspr);

                required = SimplePermissionReference.getPermissionReference(qName, permissionString);
            }
            else if (typeString.equals(ACL_METHOD))
            {
                if (st.countTokens() != 1)
                {
                    throw new ACLEntryVoterException(
                            "There must be two . separated tokens in each group or role config attribute");
                }
                authority = st.nextToken();
            }

        }
    }

}

Of course, you need to configure all of this in the public-services-security-context.xml as well. And have the aspect with its properties in place. And the permissions in the permissionDefinitions.xml. But then it works like a charm.

burriad
Champ in-the-making
Champ in-the-making
The above solution has one little drawback: you get an AccessDeniedException which is propagated right to the UI  :shock: .

To prevent angry calls from unhappy users, you can extend the above scheme by implementing your own AfterInvocationProvider (just copy the one from Alfresco, named ACLEntryAfterInvocationProvider, and replace the calls to permissionService.hasPermission() with your own access permission logic)

Again, configure your new class public-services-security-context.xml and you're back in business  Smiley Very Happy .