# AccountExtUtil.java

    /*
     ** Module   : AccountExtUtil.java
     ** Abstract : Wrapper class for account ext methods (moved from SecurityManager).
     **
     ** Copyright (c) 2023, Golden Code Development Corporation.
     **
     ** -#- -I- --Date-- ------------------------------------Description-------------------------------------------
     ** 001 GBB  20230825 Initial version.
     */
    /*
     ** 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 com.goldencode.p2j.directory.*;

    /**
     * Wrapper class for account ext methods.
     * <p>
     * Using it through {@link SecurityManager#accountExt} allows resetting SecurityManager instance.
     */
    public class AccountExtUtil
    {
       /** SecurityManager instance. */
       private final SecurityManager sm;

       /**
        * Package-private constructor.
        *
        * @param sm
        *        SecurityManager instance
        */
       AccountExtUtil(SecurityManager sm)
       {
          this.sm = sm;
       }

       /**
        * Gets the named piece of account extension data of type int for the
        * current user account.
        *
        * @param extName
        *        account extension name
        *
        * @return extension value wrapped into <code>Integer</code> or
        *         <code>null</code> if no such extension exists.
        */
       public Integer getExtInteger(String extName)
       {
          return getExtInteger(extName, sm.getUserId());
       }

       /**
        * Gets the named piece of account extension data of type boolean for the
        * current user account.
        *
        * @param extName
        *        account extension name
        *
        * @return extension value wrapped into <code>Boolean</code> or
        *         <code>null</code> if no such extension exists.
        */
       public Boolean getExtBoolean(String extName)
       {
          return getExtBoolean(extName, sm.getUserId());
       }

       /**
        * Gets the named piece of account extension data of type String for the
        * current user account.
        *
        * @param extName
        *        account extension name
        *
        * @return extension value or
        *         <code>null</code> if no such extension exists.
        */
       public String getExtString(String extName)
       {
          return getExtString(extName, sm.getUserId());
       }

       /**
        * Gets the named piece of account extension data of type String for the
        * current user account.
        *
        * @param extName
        *        account extension name
        *
        * @return extension value or
        *         <code>null</code> if no such extension exists.
        */
       public DateValue getExtDate(String extName)
       {
          return getExtDate(extName, sm.getUserId());
       }

       /**
        * Gets the named piece of account extension data of type byte[] for the
        * current user account.
        *
        * @param extName
        *        account extension name
        *
        * @return extension value or
        *         <code>null</code> if no such extension exists.
        */
       public byte[] getExtBytes(String extName)
       {
          return getExtBytes(extName, sm.getUserId());
       }

       /**
        * Gets the named piece of account extension data of type int for the
        * specified account.
        *
        * @param extName
        *        account extension name
        * @param subjectId
        *        subject ID
        *
        * @return extension value wrapped into <code>Integer</code> or
        *         <code>null</code> if no such extension exists.
        */
       public Integer getExtInteger(String extName, String subjectId)
       {
          // check the access rights
          if (sm.getUserId().compareToIgnoreCase(subjectId) != 0)
          {
             SystemResource sr = (SystemResource) sm.getPluginInstance("system");
             boolean decision = sr.checkExtensionsAccess();
             if (!decision)
                return null;
          }

          String path = locateExtensionPath(subjectId);
          if (path == null)
             return null;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return null;
          if (!ds.bind())
             return null;

          // get the target directory object's "value" attribute of type Integer
          Integer value = ds.getNodeInteger(path + "/" + extName, "value");

          // deactivate directory services
          ds.unbind();

          return value;
       }

       /**
        * Gets the named piece of account extension data of type boolean for the
        * specified account.
        *
        * @param extName
        *        account extension name
        * @param subjectId
        *        subject ID
        *
        * @return extension value wrapped into <code>Boolean</code> or
        *         <code>null</code> if no such extension exists.
        */
       public Boolean getExtBoolean(String extName, String subjectId)
       {
          // check the access rights
          if (sm.getUserId().compareToIgnoreCase(subjectId) != 0)
          {
             SystemResource sr = (SystemResource) sm.getPluginInstance("system");
             boolean decision = sr.checkExtensionsAccess();
             if (!decision)
                return null;
          }

          String path = locateExtensionPath(subjectId);
          if (path == null)
             return null;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return null;
          if (!ds.bind())
             return null;

          // get the target directory object's "value" attribute of type Integer
          Boolean value = ds.getNodeBoolean(path + "/" + extName, "value");

          // deactivate directory services
          ds.unbind();

          return value;
       }

       /**
        * Gets the named piece of account extension data of type String for the
        * specified account.
        *
        * @param extName
        *        account extension name
        * @param subjectId
        *        subject ID
        *
        * @return extension value or
        *         <code>null</code> if no such extension exists.
        */
       public String getExtString(String extName, String subjectId)
       {
          // check the access rights
          if (sm.getUserId().compareToIgnoreCase(subjectId) != 0)
          {
             SystemResource sr = (SystemResource) sm.getPluginInstance("system");
             boolean decision = sr.checkExtensionsAccess();
             if (!decision)
                return null;
          }

          String path = locateExtensionPath(subjectId);
          if (path == null)
             return null;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return null;
          if (!ds.bind())
             return null;

          // get the target directory object's "value" attribute of type Integer
          String value = ds.getNodeString(path + "/" + extName, "value");

          // deactivate directory services
          ds.unbind();

          return value;
       }

       /**
        * Gets the named piece of account extension data of type date for the
        * specified account.
        *
        * @param extName
        *        account extension name
        * @param subjectId
        *        subject ID
        *
        * @return extension value or
        *         <code>null</code> if no such extension exists.
        */
       public DateValue getExtDate(String extName, String subjectId)
       {
          // check the access rights
          if (sm.getUserId().compareToIgnoreCase(subjectId) != 0)
          {
             SystemResource sr = (SystemResource) sm.getPluginInstance("system");
             boolean decision = sr.checkExtensionsAccess();
             if (!decision)
                return null;
          }

          String path = locateExtensionPath(subjectId);
          if (path == null)
             return null;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return null;
          if (!ds.bind())
             return null;

          // get the target directory object's "value" attribute of type Integer
          DateValue value = ds.getNodeDate(path + "/" + extName, "value");

          // deactivate directory services
          ds.unbind();

          return value;
       }

       /**
        * Gets the named piece of account extension data of type byte[] for the
        * specified account.
        *
        * @param extName
        *        account extension name
        * @param subjectId
        *        subject ID
        *
        * @return extension value or
        *         <code>null</code> if no such extension exists.
        */
       public byte[] getExtBytes(String extName, String subjectId)
       {
          // check the access rights
          if (sm.getUserId().compareToIgnoreCase(subjectId) != 0)
          {
             SystemResource sr = (SystemResource) sm.getPluginInstance("system");
             boolean decision = sr.checkExtensionsAccess();
             if (!decision)
                return null;
          }

          String path = locateExtensionPath(subjectId);
          if (path == null)
             return null;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return null;
          if (!ds.bind())
             return null;

          // get the target directory object's "value" attribute of type Integer
          byte[] value = ds.getNodeByteArray(path + "/" + extName, "value");

          // deactivate directory services
          ds.unbind();

          return value;
       }

       /**
        * Sets the named piece of account extension data of type int for the
        * current user account.
        *
        * @param extName
        *        account extension name
        * @param value
        *        new value for the account extension
        *
        * @return <code>true</code> if successfully set the value
        */
       public boolean setExtInteger(String extName, int value)
       {
          return setExtInteger(extName, sm.getUserId(), value);
       }

       /**
        * Sets the named piece of account extension data of type boolean for the
        * current user account.
        *
        * @param extName
        *        account extension name
        * @param value
        *        new value for the account extension
        *
        * @return <code>true</code> if successfully set the value
        */
       public boolean setExtBoolean(String extName, boolean value)
       {
          return setExtBoolean(extName, sm.getUserId(), value);
       }

       /**
        * Sets the named piece of account extension data of type String for the
        * current user account.
        *
        * @param extName
        *        account extension name
        * @param value
        *        new value for the account extension
        *
        * @return <code>true</code> if successfully set the value
        */
       public boolean setExtString(String extName, String value)
       {
          return setExtString(extName, sm.getUserId(), value);
       }

       /**
        * Sets the named piece of account extension data of type byte[] for the
        * current user account.
        *
        * @param extName
        *        account extension name
        * @param value
        *        new value for the account extension
        *
        * @return <code>true</code> if successfully set the value
        */
       public boolean setExtBytes(String extName, byte[] value)
       {
          return setExtBytes(extName, sm.getUserId(), value);
       }

       /**
        * Sets the named piece of account extension data of type int for the
        * specified account.
        *
        * @param extName
        *        account extension name
        * @param subjectId
        *        subject ID
        * @param value
        *        new value for the account extension
        *
        * @return <code>true</code> if successfully set the value
        */
       public boolean setExtInteger(String extName, String subjectId, int value)
       {
          // check the access rights
          if (sm.getUserId().compareToIgnoreCase(subjectId) != 0)
          {
             SystemResource sr = (SystemResource) sm.getPluginInstance("system");
             boolean decision = sr.checkExtensionsAccess();
             if (!decision)
                return false;
          }

          String path = locateExtensionPath(subjectId);
          if (path == null)
             return false;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return false;
          if (!ds.bind())
             return false;

          // open an editing batch
          if (!ds.openBatch(path))
          {
             ds.unbind();
             return false;
          }

          // set the target directory object's "value" attribute of type Integer
          boolean rc = ds.setNodeInteger(path + "/" + extName, "value", value);

          // close the batch
          boolean brc = ds.closeBatch(rc);

          // deactivate directory services
          ds.unbind();

          if (!brc)
             return false;

          return rc;
       }

       /**
        * Sets the named piece of account extension data of type boolean for the
        * specified account.
        *
        * @param extName
        *        account extension name
        * @param subjectId
        *        subject ID
        * @param value
        *        new value for the account extension
        *
        * @return <code>true</code> if successfully set the value
        */
       public boolean setExtBoolean(String extName, String subjectId, boolean value)
       {
          // check the access rights
          if (sm.getUserId().compareToIgnoreCase(subjectId) != 0)
          {
             SystemResource sr = (SystemResource) sm.getPluginInstance("system");
             boolean decision = sr.checkExtensionsAccess();
             if (!decision)
                return false;
          }

          String path = locateExtensionPath(subjectId);
          if (path == null)
             return false;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return false;
          if (!ds.bind())
             return false;

          // open an editing batch
          if (!ds.openBatch(path))
          {
             ds.unbind();
             return false;
          }

          // set the target directory object's "value" attribute of type Integer
          boolean rc = ds.setNodeBoolean(path + "/" + extName, "value", value);

          // close the batch
          if (!ds.closeBatch(true))
          {
             ds.unbind();
             return false;
          }

          // deactivate directory services
          ds.unbind();

          return rc;
       }

       /**
        * Sets the named piece of account extension data of type String for the
        * specified account.
        *
        * @param extName
        *        account extension name
        * @param subjectId
        *        subject ID
        * @param value
        *        new value for the account extension
        *
        * @return <code>true</code> if successfully set the value
        */
       public boolean setExtString(String extName, String subjectId, String value)
       {
          // check the access rights
          if (sm.getUserId().compareToIgnoreCase(subjectId) != 0)
          {
             SystemResource sr = (SystemResource) sm.getPluginInstance("system");
             boolean decision = sr.checkExtensionsAccess();
             if (!decision)
                return false;
          }

          String path = locateExtensionPath(subjectId);
          if (path == null)
             return false;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return false;
          if (!ds.bind())
             return false;

          // open an editing batch
          if (!ds.openBatch(path))
          {
             ds.unbind();
             return false;
          }

          // set the target directory object's "value" attribute of type Integer
          boolean rc = ds.setNodeString(path + "/" + extName, "value", value);

          // close the batch
          if (!ds.closeBatch(true))
          {
             ds.unbind();
             return false;
          }

          // deactivate directory services
          ds.unbind();

          return rc;
       }

       /**
        * Sets the named piece of account extension data of type byte[] for the
        * specified account.
        *
        * @param extName
        *        account extension name
        * @param subjectId
        *        subject ID
        * @param value
        *        new value for the account extension
        *
        * @return <code>true</code> if successfully set the value
        */
       public boolean setExtBytes(String extName, String subjectId, byte[] value)
       {
          // check the access rights
          if (sm.getUserId().compareToIgnoreCase(subjectId) != 0)
          {
             SystemResource sr = (SystemResource) sm.getPluginInstance("system");
             boolean decision = sr.checkExtensionsAccess();
             if (!decision)
                return false;
          }

          String path = locateExtensionPath(subjectId);
          if (path == null)
             return false;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return false;
          if (!ds.bind())
             return false;

          // open an editing batch
          if (!ds.openBatch(path))
          {
             ds.unbind();
             return false;
          }

          // set the target directory object's "value" attribute of type Integer
          boolean rc = ds.setNodeByteArray(path + "/" + extName, "value", value);

          // close the batch
          if (!ds.closeBatch(true))
          {
             ds.unbind();
             return false;
          }

          // deactivate directory services
          ds.unbind();

          return rc;
       }

       /**
        * Gets the user ID of a user having a matching value in a specified
        * named account extension of integer type.
        *
        * @param extName
        *        account extension name
        *
        * @param extValue
        *        extension value
        *
        * @return user ID or <code>null</code> if no such extension exists.
        */
       public String getIdByExtInteger(String extName, int extValue)
       {
          // check the access rights
          SystemResource sr = (SystemResource) sm.getPluginInstance("system");
          boolean decision = sr.checkExtensionsAccess();
          if (!decision)
             return null;

          // get all user account IDs
          String[] uid = sm.getAllUsers();

          String retValue = null;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return null;
          if (!ds.bind())
             return null;

          // try each user ID
          String path = null;
          Integer value = null;

          for (int i = 0; i < uid.length; i++)
          {
             path = locateExtensionPath(uid[i]);
             if (path == null)
                continue;

             // get the directory object's "value" attribute of type Integer
             value = ds.getNodeInteger(path + "/" + extName, "value");
             if (value == null)
             {
                continue;
             }

             if (value == extValue)
             {
                retValue = uid[i];
                break;
             }
          }

          // deactivate directory services
          ds.unbind();

          return retValue;
       }

       /**
        * Gets the user ID of a user having a matching value in a specified
        * named account extension of string type.
        *
        * @param extName
        *        account extension name
        *
        * @param extValue
        *        extension value
        *
        * @param cs
        *        case sensitivity for comparison. <code>true</code> means
        *        case-sensitive comparison.
        *
        * @return user ID or <code>null</code> if no such extension exists.
        */
       public String getIdByExtString(String extName, String extValue, boolean cs)
       {
          // check the access rights
          SystemResource sr = (SystemResource) sm.getPluginInstance("system");
          boolean decision = sr.checkExtensionsAccess();
          if (!decision)
             return null;

          // get all user account IDs
          String[] uid = sm.getAllUsers();

          String retValue = null;

          // activate directory services
          DirectoryService ds = DirectoryService.getInstance();
          if (ds == null)
             return null;
          if (!ds.bind())
             return null;

          // try each user ID
          String path = null;
          String value = null;
          boolean comp = false;

          for (int i = 0; i < uid.length; i++)
          {
             path = locateExtensionPath(uid[i]);
             if (path == null)
                continue;

             // get the target directory object's "value" attribute of type Integer
             value = ds.getNodeString(path + "/" + extName, "value");
             if (value == null)
             {
                continue;
             }

             // compare extension value accounting for requested case sensitivity
             if (cs)
                comp = value.equals(extValue);
             else
                comp = value.compareToIgnoreCase(extValue) == 0;

             if (comp)
             {
                retValue = uid[i];
                break;
             }
          }

          // deactivate directory services
          ds.unbind();

          return retValue;
       }

       /**
        * Locates the account extension directory path for the specified user ID.
        *
        * @param  uid
        *         user ID
        *
        * @return directory path to the account extensions
        */
       private String locateExtensionPath(String uid)
       {
          // Pick up the generation of security cache
          SecurityContextStack ctxs = SecurityContextStack.getContext();
          SecurityContext ctx = ctxs.getEffectiveContext();
          SecurityCache sc = ctx.getCache();

          // find the account
          Account acc = sc.getAccountById(uid);
          if (acc == null)
             return null;

          // figure out account type and extension path
          switch (acc.getAccountType())
          {
             case Account.ACC_USER:
                return "/security/accounts/users/" + uid;
             case Account.ACC_GROUP:
                return null;
             case Account.ACC_PROCESS:
                return "/security/accounts/processes/" + uid;
          }

          return null;
       }
    }