FileSystemResource.java

package com.goldencode.p2j.security;

import com.goldencode.p2j.concurrent.*;
import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.util.logging.*;

import java.nio.file.*;
import java.util.function.*;
import java.util.logging.*;

/**
 * Implements the "FileSystemResource" resource. Access to such resources have rights which can
 * be represented by a small set of flags:
 * <ul>
 *   <li> create - a runtime instance of the resource can be created
 *   <li> delete - an existing runtime instance can be destroyed
 *   <li> read - the state or data associated with an existing runtime instance can be accessed
 *   <li> write - the state or data associated with an existing runtime instance can be modified
 *   <li> execute - the runtime instance can be executed
 *   <li> denied - no access is allowed (overrides any other access flags)
 * </ul>
 * <p>
 * Rights objects for this resource are made of one bitfield with 8 bits.  The first four bits
 * correspond to the create, delete, read and write respectively.  There are 2 reserved bits.
 * The last bit is for explicit denial of access. When printed, they are shown as
 * {@code CDRWE}
 * <p>
 * Resource names will be namespaced using the subclass' resource type name.  The naming approach
 * is {@code /resource_type_name/instance_name}.  Each different instance of a particular
 * resource type will share the same type name but must have a unique, non-empty instance name.
 */
public class FileSystemResource
extends BitFlagsResource
{
   /** Logger */
   private static CentralLogger LOG = CentralLogger.get(FileSystemResource.class);

   /** EXECUTE permissions bit for the bitflags resource. */
   public static final int BIT_EXECUTE_ACCESS = 4;

   /** Resource type name */
   public static final String TYPE_NAME = "file-system";

   /**
    * Returns the plugin resource type name as a string.
    *
    * @return plugin resource type name
    */
   @Override
   public String getTypeName()
   {
      return TYPE_NAME;
   }

   /**
    * Override the method to provide extended bit names to augment the default set of bits.
    * The method must return either {@code null} for no extended bits or an array of length 1, 2, 3.
    * An array of length 1 will carry name of bit 4, length 2 bits 4 and 5, and length 3 bits 4, 5 and 6.
    *
    * @return  See above.
    */
   @Override
   public String[] getExtendedBits()
   {
      return new String[] {"execute"};
   }

   /**
    * Instantiates a plugin's class that implements the <code>Rights</code> interface, using
    * the array of objects representing a set of access rights fields.
    *
    * @param    rights
    *           The objects needed to create an access rights instance.
    *
    * @return   The newly created rights instance.
    */
   public Rights getRightsInstance(Object[] rights)
   {
      return new FileSystemRights(BitSetHelper.fromBitField((BitField)rights[0]));
   }

   /**
    * Checks whether a given string is a syntactically valid resource name
    * for this resource type.
    *
    * @param   resource
    *          string naming a resource
    *
    * @return  <code>true</code> if the name is syntactically correct
    */
   @Override
   public boolean isInstanceNameValid(String resource)
   {
      if (resource == null)
      {
         return false;
      }

      // validate the glob pattern
      try
      {
         FileSystems.getDefault().getPathMatcher("glob:" + resource);
         return true;
      }
      catch (Exception ex)
      {
         LOG.log(Level.WARNING, "Resource instance " + resource + " is invalid.", ex);
         return false;
      }
   }

   /**
    * Returns a bi-predicate allowing to match ACLs based on a supplied resource instance name.
    *
    * @return  A valid bi-predicate.
    */
   @Override
   public BiPredicate<AccessControlList, String> getACLMatcher()
   {
      return (acl, resInst) ->
      {
         // the glob pattern should always be valid, it is validated in isInstanceNameValid
         PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + acl.getInstanceName());
         Path input = Paths.get(resInst);
         return matcher.matches(input);
      };
   }

   /**
    * Reports if EXECUTE is allowed for the given resource name.
    *
    * @param    name
    *           Resource instance name.
    *
    * @return   <code>true</code> if access is allowed.
    */
   public boolean canExecute(String name)
   {
      return accessWorker(name, BIT_EXECUTE_ACCESS);
   }
}