Search.java

/*
** Module   : Search.java
** Abstract : Represents open rights searches.
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description----------------------
** 001 NVS 20050322   @20455 Created the initial version. This class 
**                           represents open rights searches. 
** 002 NVS 20061009   @30250 Resource instance names are assumed to be
**                           case insensitive for now, if they are given
**                           as EXACT.
** 003 CA  20090610   @42630 Bindings with 'all-others' as subject have
**                           lower priority then bindings with an explicit
**                           subject ID.
** 004 NVS 20090625   @42943 Opening a search collects all relevant bindings,
**                           according to the new ACL style.
** 005 CA  20110105          Fixed handle calculation in c'tor (it must sync
**                           on the class object not on the new instance).
** 006 IAS 20150217          Added check for null acls
** 007 GES 20160222          Fix for NPE.
** 008 HC  20230427          Implemented management for file-system resource. This includes Admin
**                           and server directory changes.
*/ 
/*
** 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.security;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

/**
 * Represents open seaches initiated by calls to openRightsSearch(). 
 */
class Search
{
   /** handle generator */
   private static int handles = 0;

   /** handle of this instance */
   private int handle = 0;

   /** resource instance name */
   private String instanceName = null;

   /** access mode AKA requested rights */
   private int mode = 0;

   /** array of subject IDs associated with this search */
   private int[] checkList = null;

   /** list of bindings found for this resource */
   private List bindings = null;

   /** index of the last returned item from the array or -1 if never */
   private int lastUsed = -1;

   /**
    * Package private constructor. 
    *
    * @param instance
    *        resource instance name 
    *
    * @param mode
    *        resource access mode 
    *
    * @param checkList
    *        array of subject IDs to open this search with
    *
    * @param acls
    *        stream of ACLs to find the resource instance
    */
   Search(String instance,
          int mode,
          int[] checkList,
          Stream<AccessControlList> acls)
   {
      synchronized(Search.class)
      {
         if (handles == Integer.MAX_VALUE)
         {
            handles = 1;
         }
         else
         {
            handles++;
         }
         this.handle = handles;
      }

      this.instanceName = instance;
      this.mode = mode;
      this.checkList = checkList;
      
      // denied if there are no ACLs to process
      if (acls == null)
      {
         return;
      }

      bindings = new LinkedList();
      acls.forEach(acl -> bindings.addAll(acl.getBindings()));
   }

   /**
    * Gets the handle of this search.
    *
    * @return handle of this search
    */
   int getHandle()
   {
      return handle;
   }

   /**
    * Gets the resource instance name of this search.
    *
    * @return resource instance name
    */
   String getInstanceName()
   {
      return instanceName;
   }

   /**
    * Gets the resource access mode of this search.
    *
    * @return resource access mode
    */
   int getMode()
   {
      return mode;
   }

   /**
    * Gets the next Rights object from the list of bindings.
    * <p>
    * Search is done in the order of subject IDs in the check list. The next
    * unchecked ID is selected and the whole list of bindings is searched for
    * a matching subject ID. If no matching entry exists, the next subject ID
    * from the check list is checked and so on until either a matching ID
    * is found or the end of the check list is reached.
    * <p>
    * If a matching binding is found for an ID, its index is saved so that the 
    * next call would start the search from the next unchecked ID.
    * <p>
    * When determining if a binding matches the current subject, bindings with
    * 'all-others' among their subjects (-2 subject ID) have lower priority
    * then bindings with explicit checklist subject. If no explicit binding
    * found, it will return the first binding with 'all-others' among its
    * subjects. 
    * 
    * @return <code>Rights</code> object or null
    */
   Rights next()
   {
      if (bindings == null || bindings.isEmpty())
         return null;

      // loop through the array of subject IDs
      int startPos = lastUsed + 1;
      int sid = 0;
      Binding bind = null;
      int i = 0;
      Iterator iter = null;
      Rights allOthersRights = null;

      for (i = startPos; i < checkList.length; i ++)
      {
         sid = checkList[i];
         iter = bindings.iterator();
         while (iter.hasNext())
         {
            bind = (Binding)iter.next();
            if (bind.matches(sid))
            {
               if (bind.matches(-2))
               {
                  if (allOthersRights == null)
                  {
                     // save only first encountered binding which matches 
                     // 'all-others' 
                     lastUsed = i;
                     allOthersRights = bind.getRights();
                  }
                  
                  // if this is not the first binding with 'all-others', 
                  // ignore it
               }
               else
               {
                  // if some binding doesn't match all-others, get the rights 
                  lastUsed = i;
                  return bind.getRights();
               }
            }
         }
      }

      // return the first binding with 'all-others' as subject or null, if 
      // none found
      return allOthersRights;
   }
}