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