# SecurityUtil.java

    /*
     ** Module   : SecurityUtil.java
     ** Abstract : Wrapper class for security util methods (moved from SecurityManager).
     **
     ** Copyright (c) 2023-2025, Golden Code Development Corporation.
     **
     ** -#- -I- --Date-- ------------------------------------Description-------------------------------------------
     ** 001 GBB  20230825 Initial version.
     ** 002 CA   20231018 Added LegacyWebSecurityManager as an internal class.
     ** 003 GBB  20230825 Added MultiSessionAppserverSecurityManager as an internal class.
     */
    /*
     ** 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.io.*;
    import java.util.*;

    /** Wrapper class for security util methods */
    public class SecurityUtil
    {

       /** A list of all security classes that can forward a check call and should be ignored. */
       private static final Set<String> INTERNAL_CLASS_NAMES = new HashSet<String>() {{
          add(SecurityUtil.class.getName());
          add(SecurityManager.class.getName());
          add(SecurityManager.SessionSecurityManager.class.getName());
          add(SecurityManager.ContextSecurityManager.class.getName());
          add(SecurityManager.VirtualConnectionSecurityManager.class.getName());
          add(SecurityManager.SsoSecurityManager.class.getName());
          add(MultiSessionAppserverSecurityManager.class.getName());
          add(LegacyWebSecurityManager.class.getName());
       }};

       /**
        * Package a user ID and password into a single entity for transmission.
        *
        * @param    userid
        *           The subject ID.
        * @param    password
        *           The password.
        *
        * @return   The object containing the packaged values.
        */
       public static byte[] packageIdPassword(String userid, String password)
       {
          ByteArrayOutputStream bos = new ByteArrayOutputStream();
          DataOutputStream      dos = new DataOutputStream(bos);

          try
          {
             dos.writeUTF(userid);
             dos.writeUTF(password);
          }

          catch (IOException ioe)
          {
             return new byte[0];
          }

          return bos.toByteArray();
       }

       /**
        * Checks the call stack to see if the caller is any of the methods
        * specified.
        * <p>
        * The closest entry to the top of the stack where the class name differs
        * from both the <code>SecurityManager</code> and the given
        * <code>ignore</code> class name is the entry checked.
        * <p>
        * A function of <code>SecurityManager</code> can be specified as the
        * allowed function. In this case it should be the closest entry to the
        * top except <code>ignore</code> class functions and other functions of
        * <code>SecurityManager</code>.
        * <p>
        * This arrangement allows for multiple levels of calls within this class
        * and the class which is checking the caller without affecting the check.
        *
        * @param    allowed
        *           A list of fully qualified (package.class.method) names from
        *           which the call is allowed.
        * @param    ignore
        *           A class name of entries (other than that of the
        *           <code>SecurityManager</code>) which should be ignored at
        *           the top of the stack.  May be <code>null</code>.
        *
        * @throws   RestrictedUseException
        *           If the check fails.
        */
       public static void checkCallerAbort(String[] allowed, String ignore)
       throws RestrictedUseException
       {
          if (!checkCallerWorker(allowed, null, ignore))
          {
             throw new RestrictedUseException("Unauthorized caller!");
          }
       }

       /**
        * Checks the call stack to see if the caller is any of the methods
        * specified.
        * <p>
        * The closest entry to the top of the stack where the class name differs
        * from the <code>SecurityManager</code> is the entry checked.
        * <p>
        * This arrangement allows for multiple levels of calls within this class
        * and the class which is checking the caller without affecting the check.
        *
        * @param    allowed
        *           A list of fully qualified (package.class.method) names from
        *           which the call is allowed.
        *
        * @throws   RestrictedUseException
        *           If the check fails.
        */
       public static void checkCallerAbort(String[] allowed)
       throws RestrictedUseException
       {
          checkCallerAbort(allowed, null);
       }

       /**
        * Checks the call stack to see if the caller is the method specified.
        * <p>
        * The closest entry to the top of the stack where the class name differs
        * from both the <code>SecurityManager</code> and the given
        * <code>ignore</code> class name is the entry checked.
        * <p>
        * A function of <code>SecurityManager</code> can be specified as the
        * allowed function. In this case it should be the closest entry to the
        * top except <code>ignore</code> class functions and other functions of
        * <code>SecurityManager</code>.
        * <p>
        * This arrangement allows for multiple levels of calls within this class
        * and the class which is checking the caller without affecting the check.
        *
        * @param    allowed
        *           A fully qualified (package.class.method) name from which the
        *           call is allowed.
        * @param    ignore
        *           A class name of entries (other than that of the
        *           <code>SecurityManager</code>) which should be ignored at
        *           the top of the stack.  May be <code>null</code>.
        *
        * @throws   RestrictedUseException
        *           If the check fails.
        */
       public static void checkCallerAbort(String allowed, String ignore)
       throws RestrictedUseException
       {
          StringBuilder sb = new StringBuilder();

          if (!checkCallerWorker(new String[] {allowed}, sb, ignore))
          {
             throw new RestrictedUseException("This restricted use method " +
                                              "cannot be called from " +
                                              sb.toString());
          }
       }

       /**
        * Checks the call stack to see if the caller is the method specified.
        * <p>
        * The closest entry to the top of the stack where the class name differs
        * from the <code>SecurityManager</code> is the entry checked.
        * <p>
        * This arrangement allows for multiple levels of calls within this class
        * and the class which is checking the caller without affecting the check.
        *
        * @param    allowed
        *           A fully qualified (package.class.method) name from which the
        *           call is allowed.
        *
        * @throws   RestrictedUseException
        *           If the check fails.
        */
       public static void checkCallerAbort(String allowed)
       throws RestrictedUseException
       {
          checkCallerAbort(allowed, null);
       }

       /**
        * Checks the call stack to see if the caller is the method specified.
        * <p>
        * The closest entry to the top of the stack where the class name differs
        * from both the <code>SecurityManager</code> and the given
        * <code>ignore</code> class name is the entry checked.
        * <p>
        * A function of <code>SecurityManager</code> can be specified as the
        * allowed function. In this case it should be the closest entry to the
        * top except <code>ignore</code> class functions and other functions of
        * <code>SecurityManager</code>.
        * <p>
        * This arrangement allows for multiple levels of calls within this class
        * and the class which is checking the caller without affecting the check.
        *
        * @param    allowed
        *           A fully qualified (package.class.method) name from which the
        *           call is allowed.
        * @param    ignore
        *           A class name of entries (other than that of the
        *           <code>SecurityManager</code>) which should be ignored at
        *           the top of the stack.  May be <code>null</code>.
        *
        * @return   <code>true</code> if allowed.
        */
       public static boolean checkCaller(String allowed, String ignore)
       {
          return checkCallerWorker(new String[] {allowed}, null, ignore);
       }

       /**
        * Checks the call stack to see if the caller is the method specified.
        * <p>
        * The closest entry to the top of the stack where the class name differs
        * from both the <code>SecurityManager</code> is the entry checked.
        * <p>
        * This arrangement allows for multiple levels of calls within this class
        * and the class which is checking the caller without affecting the check.
        *
        * @param    allowed
        *           A fully qualified (package.class.method) name from which the
        *           call is allowed.
        *
        * @return   <code>true</code> if allowed.
        */
       public static boolean checkCaller(String allowed)
       {
          return checkCaller(allowed, null);
       }

       /**
        * Checks the call stack to see if the caller is the method specified.
        * <p>
        * The closest entry to the top of the stack where the class name differs
        * from both the <code>SecurityManager</code> and the given
        * <code>ignore</code> class name is the entry checked.
        * <p>
        * A function of <code>SecurityManager</code> can be specified as the
        * allowed function. In this case it should be the closest entry to the
        * top except <code>ignore</code> class functions and other functions of
        * <code>SecurityManager</code>.
        * <p>
        * This arrangement allows for multiple levels of calls within this class
        * and the class which is checking the caller without affecting the check.
        *
        * @param    allowed
        *           A list of fully qualified (package.class.method) names from
        *           which the call is allowed.
        * @param    entry
        *           Optional user-supplied buffer so that on failure the invalid
        *           value will be available.  May be <code>null</code> if not
        *           needed by the caller.
        * @param    ignore
        *           A class name of entries (other than that of the
        *           <code>SecurityManager</code>) which should be ignored at
        *           the top of the stack.  May be <code>null</code>.
        *
        * @return   <code>true</code> if allowed.
        */
       private static boolean checkCallerWorker(String[] allowed, StringBuilder entry, String ignore)
       {
          if (allowed == null)
             return false;

          if (entry == null)
             entry = new StringBuilder();

          // obtain the stack trace (expensive!)
          Throwable thread = new Throwable();
          StackTraceElement[] stack = thread.getStackTrace();

          // the following loop starts at the 1 index position because the
          // 0 index pos will always be the current method... which doesn't
          // need checking <g>

          for (int i = 1; i < stack.length; i++)
          {
             String possibleClass = stack[i].getClassName();

             // check ignore class
             if (ignore != null && possibleClass.equals(ignore))
                continue;

             entry.setLength(0);
             entry.append(possibleClass).append(".");
             entry.append(stack[i].getMethodName());
             String possibleMethod = entry.toString();

             for (int j = 0; j < allowed.length; j++)
             {
                if (possibleMethod.equals(allowed[j]))
                {
                  return true;
                }
             }

             if (INTERNAL_CLASS_NAMES.contains(possibleClass))
             {
                // avoid internal calls
                continue;
             }

             // no match
             break;
          }

          return false;
       }

       /**
        * Creates a valid filename using a fully qualified class name.
        *
        * @param    name
        *           A fully qualified class name (without the ".class" at the
        *           end). Note that any "." characters are converted into the
        *           system-specific file separators.
        *
        * @return   The filename.
        */
       static String buildClassFileName(String name)
       {
          // this is preparation to get the file from jar; always use "/", this is correct even on
          // Windows
          String path = name.replaceAll("\\.", "/");
          String sb   = path + ".class";

          return sb;
       }

       /**
        * Checks the validity (existence) of a class, first creating a filename
        * using a given fully qualified class name.
        *
        * @param    name
        *           A fully qualified class name (without the ".class" at the
        *           end). Note that any "." characters are converted into path
        *           separators.
        *
        * @return   <code>true</code> if the class file exists.
        */
       static boolean classExists(String name)
       {
          String path = buildClassFileName(name);
          return (ClassLoader.getSystemResource(path) != null);
       }

       /**
        * Gets a byte array filled with the binary class data for the given
        * class.
        *
        * @param    name
        *           A fully qualified class name (without the ".class" at the
        *           end).
        *
        * @return   The array of bytes of the binary class file image.
        */
       static byte[] getClassBytes(String name)
       {
          // compose resource name
          String path = buildClassFileName(name);
          InputStream in = ClassLoader.getSystemResourceAsStream(path);

          if (in == null)
             return null;

          // create the output stream for byte array
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          final int BUFFSIZE = 8192;
          byte[] buff = new byte[BUFFSIZE];
          int count = 0;

          // read the class file manually
          try
          {
             do
             {
                count = in.read(buff);
                if (count > 0)
                   baos.write(buff, 0, count);
             } while (count > 0);
          }
          catch (IOException exc)
          {
             return null;
          }
          finally
          {
             try
             {
                in.close();
             }
             catch (IOException exc)
             {
             }
          }

          // return the class data
          return baos.toByteArray();
       }
    }