# DirectoryResource.java

    /*
    ** Module   : DirectoryResource.java
    ** Abstract : Implements "directory" abstract resource.
    **
    ** Copyright (c) 2004-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------Description----------------------
    ** 001 NVS 20050420   @20800 Created the initial version. This class is
    **                           responsible for the "directory" abstract
    **                           resource which offers controllable access to
    **                           the P2J directory objects.
    ** 002 NVS 20050524   @21266 Some methods and members are moved to base
    **                           abstract class.
    ** 003 NVS 20090701   @42998 Put the DirectoryRights class into a separate file
    **                           due to a new requirement for all rights classes
    *                            to be separate and serializable.
    ** 004 NVS 20090707   @43081 Added getRightsEditorName() method.
    ** 005 EVL 20160222          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 006 HC  20170612          Changes related to implementation of new GWT-based Admin client.
    ** 007 SVL 20180530          Made access mode constants public.
    ** 008 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.directory;

    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Implements the "directory" abstract resource. Instances of this resource
     * control access to the P2J directory objects.
     * <p>The following is the defined access modes:
     * <ul>
     *   <li>enumerate - lists nodes and attributes;
     *   <li>read - reads nodes and attribute values;
     *   <li>write - changes nodes and attribute values;
     *   <li>add - adds new attributes or values to attributes;
     *   <li>create - creates new nodes and attributes;
     *   <li>delete - deletes nodes and attributes
     * </ul>
     * <p>Rights objects for this resource are made of one mandatory bitfield
     * having 8 bits of permissions and one optional boolean expression.
     * <p>
     * The permissions bits are defined as follows:
     * <ul>
     *   <li>enumerate
     *   <li>read
     *   <li>write
     *   <li>add
     *   <li>create
     *   <li>delete
     *   <li>denied
     *   <li>logic
     * </ul>
     * Bits from "enumerate" through "delete" control the corresponding access
     * modes. Bit "denied" explicitly denies any access, regardless of the
     * boolean expression and the "logic" bit (veto).
     * <p>
     * Bit "logic" specifies the operation of the optional boolean expression and
     * is only taken into consideration when the expression is present:
     * <ul>
     *   <li>if set to 1, the <b>AND</b> operation is performed between the result
     *       of the permissions check and the boolean expression; the effect of
     *       the expression in this case is restrictive;
     *   <li>if set to 0, the <b>OR</b> operation is performed between the result
     *       of the permissions check and the boolean expression; the effect of
     *       the expression in this case is permissive;
     * </ul>
     * <p>
     * The directory package uses package private methods defined in this class
     * for access rights checks. The interpretation of various access modes and
     * the meaning of particular operations against the directory nodes and
     * attributes is beyond control of this resource plugin.
     */
    public class DirectoryResource
    extends AbstractResource
    {
       /** ENUMERATE access mode for directory resource */
       public static final int DIR_ENUMERATE_ACCESS = 1;

       /** READ access mode for directory resource */
       public static final int DIR_READ_ACCESS = 2;

       /** WRITE access mode for directory resource */
       public static final int DIR_WRITE_ACCESS = 4;

       /** ADD access mode for directory resource */
       public static final int DIR_ADD_ACCESS = 8;

       /** CREATE access mode for directory resource */
       public static final int DIR_CREATE_ACCESS = 16;

       /** DELETE access mode for directory resource */
       public static final int DIR_DELETE_ACCESS = 32;

       /** Logger. */
       private static final CentralLogger LOG = CentralLogger.get(DirectoryResource.class);

       /**
        * Constructor.
        */
       public DirectoryResource()
       {
       }

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

       /**
        * Returns an array of descriptions, one object per the plugin's access
        * rights item.
        *
        * @return array of <code>Description</code>s, one per each plugin's access
        *         rights item.
        */
       public Description[] describeRights()
       {
          Description[] items = new Description[2];
          items[0] = new Description(AttributeType.ATTR_BITFIELD,  // bitfield
                                     false,                        // mandatory
                                     false,                        // fix.size
                                     8,                            // size
                                     "permissions",                // text label
                                     "bit set to ON enables access",
                                     new String[]                  // bit names
                                     {
                                        "enumerate",
                                        "read",
                                        "write",
                                        "add",
                                        "create",
                                        "delete",
                                        "denied",
                                        "logic",
                                     },
                                     new BitSet(8)                 // no unused
                                     );
          items[1] = new Description(AttributeType.ATTR_STRING,    // string
                                     true,                         // optional
                                     true,                         // var.size
                                     0,                            // size
                                     "condition",                  // text label
                                     "extra condition"
                                     );

          return items;
       }

       /**
        * Instantiates a plugin's class that implements the Rights interface,
        * using the array of objects representing a set of access rights fields.
        *
        * @param  rights
        *         an array of objects of proper types representing items in a set
        *         of access rights
        *
        * @return the reference to the interface.
        */
       public Rights getRightsInstance(Object[] rights)
       {
          return new DirectoryRights(BitSetHelper.fromBitField((BitField)rights[0]),
                                     (String)rights[1]);
       }

       /**
        * Checks whether a given string is a valid resource name
        * for this resource type.
        * <p>Valid names are:
        * <ul>
        *   <li>empty string for the root object;
        *   <li>multiple links in the path, separated with '/', where every link
        *       is made of alpha-numeric characters, dashes and underscores and is
        *       no longer than 256 characters.
        * </ul>
        * <p>
        * All these checks are performed by IdUtils.normalize method.
        *
        * @param  resource
        *         string naming a resource
        *
        * @return <code>true</code> if the name is syntactically correct
        */
       public boolean isInstanceNameValid(String resource)
       {
          if (IdUtils.normalize(resource) == null)
             return false;

          return true;
       }

       /**
        * Checks whether a given array of objects representing a set of access
        * rights fields is acceptable.
        * <p>This method returns <code>true</code> only if:
        * <ul>
        *   <li>fields are of proper number and type;
        *   <li>DENIED bit is not set, or
        *   <li>DENIED bit is set and no other bit from ENUMERATE through DELETE
        *       is set.
        * </ul>
        *
        * @param  rights
        *         an array of objects of proper types representing items in a set
        *         of access rights
        *
        * @return <code>true</code> if the array is acceptable for an instance
        *         of access rights
        */
       public boolean isRightsSetValid(Object[] rights)
       {
          if (rights.length != 2)
             return false;

          if (rights[0] == null)
             return false;

          if (!rights[0].getClass().isInstance(new BitField(1)))
             return false;

          if (rights[1] != null)
          {
             if (!rights[1].getClass().isInstance(""))
                return false;
          }

          BitSet bs = BitSetHelper.fromBitField((BitField)rights[0]);
          DirectoryRights newRights = new DirectoryRights(bs, (String)rights[1]);

          boolean denied = newRights.getPermission(DirectoryRights.
                                                   BIT_DENIED_ACCESS);
          if (!denied)
             return true;

          boolean set = newRights.getPermission(DirectoryRights.
                                                BIT_ENUMERATE_ACCESS)  |
                        newRights.getPermission(DirectoryRights.
                                                BIT_READ_ACCESS)       |
                        newRights.getPermission(DirectoryRights.
                                                BIT_WRITE_ACCESS)      |
                        newRights.getPermission(DirectoryRights.
                                                BIT_ADD_ACCESS)        |
                        newRights.getPermission(DirectoryRights.
                                                BIT_CREATE_ACCESS)     |
                        newRights.getPermission(DirectoryRights.
                                                BIT_DELETE_ACCESS);
          if (set)
             return false;

          return true;
       }

       /**
        * Returns the plugin's exported variables and functions.
        * <p>
        * This implementation exports a library.
        *
        * @return plugin's instance of a library class for symbol resolution
        */
       public Object getLibrary()
       {
          return new DirectoryPool();
       }

       /**
        * Checks the given node for the specified access.
        *
        * @param  oId
        *         directory object ID - resource instance name
        *
        * @param  mode
        *         requested access mode
        *
        * @return <code>true</code> if access is allowed.
        */
        boolean checkAccess(String oId, int mode)
       {
          return dirAccessWorker(oId, mode);
       }

       /**
        * Implements generalized access rights check worker.
        * As this resource is a hierarchy of names, the full name is
        * checked first. In case there was no rights objects found for the full
        * name, the partial names are checked.
        *
        * @param  oId
        *         directory object ID - resource instance name
        *
        * @param  mode
        *         requested access mode
        *
        * @return <code>true</code> if access is allowed.
        */
       private boolean dirAccessWorker(String oId, int mode)
       {
          Boolean cached = null;
          boolean decision = false;
          int check = 0;

          String instanceName = oId;

          // check to see if there is a cached decision
          cached = sm.getCachedDecision(resourceIndex, instanceName, mode);
          if (cached != null)
          {
             return cached.booleanValue();
          }

          // initiate the ACL search
          int handle = sm.openRightsSearch(resourceIndex, instanceName, mode);
          LOG.finer("Search open: resId " + resourceIndex + ", instance " +
                    instanceName + ", mode " + mode + ", handle " + handle);

          // rights check loop
          DirectoryRights rights = (DirectoryRights)sm.getNextRights(handle);

          if (rights == null)
          {
             // no rights defined for this name; climb the hierarchy up
             if (instanceName.length() == 0)     // reached the root
                return false;

             int last = instanceName.lastIndexOf('/');
             if (last == -1)                     // safety net; never happens
                return false;

             instanceName = instanceName.substring(0, last);
             decision = dirAccessWorker(instanceName, mode);
          }
          else
          {
          loop:
             while (rights != null)
             {
                LOG.finer("Search next: handle " + handle + ", rights " + rights);
                check = checkSingle(instanceName, mode, rights);
                switch (check)
                {
                   case -1:
                      break loop;

                   case 0:
                      break;

                   case 1:
                      decision = true;
                      break loop;
                }

                rights = (DirectoryRights)sm.getNextRights(handle);
             }
          }

          // close the ACL search
          LOG.finer("Search done: handle " + handle + ", decision " + decision +
                    ", cache true");
          sm.closeRightsSearch(handle, decision, true);

          return decision;
       }

       /**
        * Implements a worker that performs access rights check on a single
        * instance of the DirectoryRights object.
        *
        * @param  oId
        *         directory object ID - resource instance name
        *
        * @param  mode
        *         requested access mode
        *
        * @param rights
        *        instance of DirectoryRights to check
        *
        * @return one of the following values:
        *         <ul>
        *            <li>-1 if access is explicitly denied or access mode is
        *                invalid
        *            <li>0 if decision for this access mode is FALSE
        *            <li>1 if decision for this access mode is TRUE
        *         </ul>
        */
       private int checkSingle(String oId, int mode, DirectoryRights rights)
       {
          // look at the DENIED permission first
          if (rights.getPermission(DirectoryRights.BIT_DENIED_ACCESS))
             return -1;

          // second, query the appropriate permission bit
          int bit = 0;
          switch (mode)
          {
             case DIR_ENUMERATE_ACCESS:
                bit = DirectoryRights.BIT_ENUMERATE_ACCESS;
                break;

             case DIR_READ_ACCESS:
                bit = DirectoryRights.BIT_READ_ACCESS;
                break;

             case DIR_WRITE_ACCESS:
                bit = DirectoryRights.BIT_WRITE_ACCESS;
                break;

             case DIR_ADD_ACCESS:
                bit = DirectoryRights.BIT_ADD_ACCESS;
                break;

             case DIR_CREATE_ACCESS:
                bit = DirectoryRights.BIT_CREATE_ACCESS;
                break;

             case DIR_DELETE_ACCESS:
                bit = DirectoryRights.BIT_DELETE_ACCESS;
                break;

             default:
                return -1;
          }

          boolean decision = rights.getPermission(bit);
          boolean logic = rights.getPermission(DirectoryRights.BIT_LOGIC);

          // third, figure out if the decision can be made now:
          // - no condition ==> return decision (0 or 1);
          // - logic is OR  and decision is TRUE  ==> return 1;
          // - logic is AND and decision is FALSE ==> return 0

          String condition = rights.getCondition();
          if (condition == null)
          {
             if (decision)
                return 1;
             else
                return 0;
          }

          if (logic == false && decision == true)
             return 1;

          if (logic == true && decision == false)
             return 0;

          // fourth, evaluate the condition and make the final decision
          SharedData share = new SharedData(oId, mode);
          Boolean result = null;
          Object  o = sm.evaluate(resourceIndex, share, condition);
          if (o == null || !Boolean.class.isInstance(o))
             result = null;
          else
             result = (Boolean)o;

          boolean cond = result == null ? false : result.booleanValue();
          if (logic == false)
             decision = decision | cond;
          else
             decision = decision & cond;

          if (decision)
             return 1;
          else
             return 0;
       }

       /**
        * Implements the directory variable pool.
        * <p>
        * Variables defined in this private pool are:
        * <ul>
        *   <li>node - directory node name currently being accessed;
        *   <li>mode - access mode for the current operation.
        * </ul>
        * <p>
        * Methods defined in this private pool are:
        * <ul>
        *   <li>boolean under(String path, String branch)
        *       <br>checks to see whether the directory node 'path' is under the
        *       specified tree branch.
        * </ul>
        */
       public class DirectoryPool
       {
          /**
           * Constructor.
           */
          public DirectoryPool()
          {
          }

          /**
           * Gets the directory node under consideration.
           *
           * @return directory node name
           */
          public String getNode()
          {
             return ((SharedData)getLink()).getNode();
          }

          /**
           * Gets the access mode of the current operation.
           *
           * @return the string representation of the access mode as follows:
           * <ul>
           *   <li>"E" for "enumerate";
           *   <li>"R" for "read";
           *   <li>"W" for "write";
           *   <li>"A" for "add";
           *   <li>"C" for "create";
           *   <li>"D" for "delete";
           * </ul>
           */
          public String getMode()
          {
             int mode = ((SharedData)getLink()).getMode();

             switch (mode)
             {
                case DIR_ENUMERATE_ACCESS:
                   return "E";

                case DIR_READ_ACCESS:
                   return "R";

                case DIR_WRITE_ACCESS:
                   return "W";

                case DIR_ADD_ACCESS:
                   return "A";

                case DIR_CREATE_ACCESS:
                   return "C";

                case DIR_DELETE_ACCESS:
                   return "D";
             }

             return "U";     // never happens as mode is verified by now
          }

          /**
           * Relates the current directory node to a directory branch.
           * <p>
           * Since the implementation simply compares the strings using the
           * String.startsWith() method, the branch parameter should be made of
           * one or more complete links to produce a meaningful result.
           *
           * @param  branch
           *         directory tree branch to compare with
           *
           * @return <code>true</code> if the current path is under the branch,
           *         <code>false</code> otherwise.<br>
           *         <code>false</code> is returned if path is the directory root
           *         "".<br>
           *         <code>true</code> is returned if branch is the directory root
           *         "" (unless the path is the root, too).
           */
          public boolean isUnder(String branch)
          {
             return ((SharedData)getLink()).getNode().startsWith(branch);
          }
       }

       /**
        * Implements the directory variable pool shared data.
        * These are pieces of information taken from the access check being
        * performed. As these are maintained on per thread basis, the convenient
        * way to pass them between the access check method and the variable pool
        * methods is the link provided by AbstractResource class.
        * <p>
        * Variables defined in this private pool are:
        * <ul>
        *   <li>node - directory node name currently being accessed;
        *   <li>mode - access mode for the current operation.
        * </ul>
        */
       private class SharedData
       {
          /** node name */
          private String node = null;

          /** access mode */
          private int mode = 0;

          /**
           * Constructor.
           *
           * @param node
           *        node being checked
           *
           * @param mode
           *        requested access mode
           */
          SharedData(String node, int mode)
          {
             this.node = node;
             this.mode = mode;
          }

          /**
           * Gets the directory node name.
           *
           * @return node name saved in this shared data
           */
          String getNode()
          {
             return node;
          }

          /**
           * Gets the access mode.
           *
           * @return access mode saved in this shared data
           */
          int getMode()
          {
             return mode;
          }
       }
    }