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;
   }
}