# NetResource.java

    /*
    ** Module   : NetResource.java
    ** Abstract : security mgr plugin for the "net" abstract resource.
    **
    ** Copyright (c) 2005-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------Description----------------------
    ** 001 NVS 20050413   @20695 Created the initial version. This class is
    **                           responsible for the "net" abstract resource
    **                           which offers controllable access to the
    **                           exported server's entry points
    ** 002 NVS 20050414   @20710 toString now encloses permissions in {}.
    **                           Added rights search traceing for debugging.
    ** 003 NVS 20050415   @20738 Added class description to javadoc.
    ** 004 NVS 20050421   @20825 Fixed an object cast bug in isRightsSetValid.
    ** 005 NVS 20050524   @21267 Some methods and members are moved to base
    **                           abstract class.
    ** 006 NVS 20060426   @25727 Fixed bug in nested rights search.
    ** 007 ECF 20080214   @37068 Minor optimization. Replaced string
    **                           concatenation with StringBuilder.
    ** 008 GES 20081025   @40317 Minor optimization. Eliminate string
    **                           concatenation for logging unless the logging
    **                           level is high enough to force logging to
    **                           occur.
    ** 009 NVS 20090630   @42999 Put the NetRights class into a separate file
    **                           due to a new requirement for all rights classes
    **                           to be separate and serializable.
    ** 010 NVS 20090707   @43080 Added getRightsEditorName() method.
    ** 011 HC  20170612          Changes related to implementation of new GWT-based Admin client.
    ** 012 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.net;

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

    import java.util.logging.*;

    /**
     * Implements the "net" abstract resource. Instances of this resource
     * control access to exported server's entry points. They form a two level
     * hierarchy of names, with the top level name being a group name and the
     * bottom level name being a member of the group. The syntax for this
     * composite name is group:api.
     * <p>
     * There are three access modes:
     * <ul>
     *   <li>read - getting routing key from group:api;
     *   <li>write - add a group:api method to the registry (export);
     *   <li>execute - call a method
     * </ul>
     * <p>
     * Rights objects for this resource are made of one bitfield with 4 bits.
     * First three bits correspond to the listed access modes above. The last bit
     * is for explicit denial of access. When printed, they are shown as RWXN.
     * <p>
     * There is a predefined "system" group of methods, containing the following
     * method names:
     * <ul>
     *   <li>route<br>
     *       this method is used by peers to resolve exported symbolic API names
     *       into registry indices
     *   <li>authenticate<br>
     *       this method is used internally by the net package to do remote
     *       subject authentication in a routed configuration
     *   <li>terminate<br>
     *       this method is used internally by the net package to do remote
     *       session termination in a routed configuration
     *   <li>shutdown<br>
     *       this method is used to request the net package shutdown, which
     *       normally also initiates the server shutdown
     * </ul>
     * <p>
     * These names can be used in ACLs. The server's account should provide ACLs
     * that allow the server's process have RWX rights for the "system" group.
     * On the other hand, application related accounts never need any access to
     * system:authenticate and system:terminate.
     * They need RX access to system:route to be able to perform remote calls.
     * system:shutdown may be given RX or N access.
     * <p>
     * Non-system API access control is completely up to the admin.
     */
    public class NetResource
    extends AbstractResource
    {
       /** READ access mode for net resource */
       static final int NET_READ_ACCESS = 1;

       /** WRITE access mode for net resource */
       static final int NET_WRITE_ACCESS = 2;

       /** EXECUTE access mode for net resource */
       static final int NET_EXECUTE_ACCESS = 4;

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

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

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

       /**
        * Returns an array of descriptions, one object per the plugin's access
        * rights item.
        * <p>
        * This plugin uses one field of type BitField with three bits: read, write
        * end execute.
        * @return array of <code>Description</code>s, one per each plugin's access
        *         rights item.
        */
       public Description[] describeRights()
       {
          Description[] items = new Description[1];
          items[0] = new Description(AttributeType.ATTR_BITFIELD,  // string
                                     false,                        // mandatory
                                     false,                        // fix.size
                                     4,                            // size
                                     "permissions",                // text label
                                     "bit set to ON enables access",
                                     new String[]                  // bit names
                                     {
                                        "read",
                                        "write",
                                        "execute",
                                        "denied",
                                     },
                                     new BitSet(4)                 // no unused
                                     );

          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 NetRights(BitSetHelper.fromBitField((BitField)rights[0]));
       }

       /**
        * Checks whether a given string is a valid resource name
        * for this resource type.
        * <p>Valid names contain no slashes and 0 or 1 colon.
        *
        * @param  resource
        *         string naming a resource
        *
        * @return <code>true</code> if the name is syntactically correct
        */
       public boolean isInstanceNameValid(String resource)
       {
          if (resource.indexOf('/') != -1)
             return false;

          if (resource.indexOf(':') != resource.lastIndexOf(':'))
             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>DENIED bit is not set, or
        *   <li>DENIED bit is set and no other bit 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 != 1)
             return false;

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

          BitSet bs = BitSetHelper.fromBitField((BitField)rights[0]);
          NetRights newRights = new NetRights(bs);

          boolean denied = newRights.get(NetRights.BIT_DENIED_ACCESS);
          if (!denied)
             return true;

          boolean set = newRights.get(NetRights.BIT_READ_ACCESS)  |
                        newRights.get(NetRights.BIT_WRITE_ACCESS) |
                        newRights.get(NetRights.BIT_EXECUTE_ACCESS);
          if (set)
             return false;

          return true;
       }

       /**
        * Checks READ access rights of the current subject with regards to the
        * given instance of the "net" abstract resource.
        *
        * @param  group
        *         group name
        *
        * @param  api
        *         method name within the group
        *
        * @return <code>true</code> if access is allowed.
        */
       boolean checkReadAccess(String group, String api)
       {
          return netAccessWorker(group, api, NET_READ_ACCESS);
       }

       /**
        * Checks WRITE access rights of the current subject with regards to the
        * given instance of the "net" abstract resource.
        *
        * @param  group
        *         group name
        *
        * @param  api
        *         method name within the group
        *
        * @return <code>true</code> if access is allowed.
        */
       boolean checkWriteAccess(String group, String api)
       {
          return netAccessWorker(group, api, NET_WRITE_ACCESS);
       }

       /**
        * Checks EXECUTE access rights of the current subject with regards to the
        * given instance of the "net" abstract resource.
        *
        * @param  group
        *         group name
        *
        * @param  api
        *         method name within the group
        *
        * @return <code>true</code> if access is allowed.
        */
       boolean checkExecuteAccess(String group, String api)
       {
          return netAccessWorker(group, api, NET_EXECUTE_ACCESS);
       }

       /**
        * Implements generalized access rights check worker.
        * As this resource is a two level hierarchy of names, the full name is
        * checked first. In case there was no rights objects found for the full
        * name, the group name is checked.
        *
        * @param  group
        *         group name
        *
        * @param  api
        *         method name within the group
        *
        * @param mode
        *        an integer value encoding the access mode specific to this
        *        resource
        *
        * @return <code>true</code> if access is allowed.
        */
       private boolean netAccessWorker(String group, String api, int mode)
       {
          boolean isFinerLoggable = LOG.isLoggable(Level.FINER);

          Boolean cached = null;
          boolean decision = false;
          int check = 0;

          StringBuilder buf = new StringBuilder(group);
          buf.append(":");
          buf.append(api);
          String instanceName = buf.toString();

          // 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);

          if (isFinerLoggable)
          {
             LOG.finer("Search open: resId " + resourceIndex + ", instance " +
                          instanceName + ", mode " + mode + ", handle " + handle);
          }

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

          if (rights == null)
          {
             // no rights defined for the fully qualified name; try group only
             if (isFinerLoggable)
             {
                LOG.finer("Search done: handle " + handle + ", decision " +
                             decision + ", cache false");
             }

             sm.closeRightsSearch(handle, decision, false);
             handle = sm.openRightsSearch(resourceIndex, group, mode);

             if (isFinerLoggable)
             {
                LOG.finer("Search open: resId " + resourceIndex + ", instance " +
                             group + ", mode " + mode + ", handle " + handle);
             }

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

          loop:
          while (rights != null)
          {
             if (isFinerLoggable)
             {
                LOG.finer("Search next: handle " + handle + ", rights " + rights);
             }

             check = checkSingle(rights, mode);

             switch (check)
             {
                case -1:
                   break loop;

                case 0:
                   break;

                case 1:
                   decision = true;
                   break loop;
             }

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

          // close the ACL search
          if (isFinerLoggable)
          {
             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 NetRights object.
        *
        * @param rights
        *        instance of NetRights to check
        *
        * @param mode
        *        an integer value encoding the access mode specific to this
        *        resource
        *
        * @return one of the following values:
        *         <ul>
        *            <li>-1 if access is explicitly denied
        *            <li>0 if permissions bit for this access mode was OFF
        *            <li>1 if permissions bit for this access mode was ON
        *         </ul>
        */
       private int checkSingle(NetRights rights, int mode)
       {
          if (rights.get(NetRights.BIT_DENIED_ACCESS))
             return -1;

          int bit = 0;
          switch (mode)
          {
             case NET_READ_ACCESS:
                bit = NetRights.BIT_READ_ACCESS;
                break;

             case NET_WRITE_ACCESS:
                bit = NetRights.BIT_WRITE_ACCESS;
                break;

             case NET_EXECUTE_ACCESS:
                bit = NetRights.BIT_EXECUTE_ACCESS;
                break;

             default:
                return -1;
          }

          if (rights.get(bit))
             return 1;

          return 0;
       }
    }