SecurityCache.java
/*
** Module : SecurityCache.java
** Abstract : reads P2J directory and creates a cache of accounts, ACLs etc
**
** Copyright (c) 2005-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description----------------------------------
** 001 NVS 20050307 @20208 Created. This class reads P2J directory and
** creates a cache of accounts, ACLs etc.
** 002 SIY 20050329 @20558 Minor cleanups in code and comments.
** 003 NVS 20050414 @20711 Fixed bug in readACLs() showed only in multi-
** plugin configuration. Changed printACLs()
** output for rights.
** 004 NVS 20050415 @20735 Fixed a bug in mergeACLs() method. The bug
** was caused by using single instance of
** bindings list for multiple instances of the
** resource. Each resource instance has to have
** its private copy of the bindings list or else
** the merge results would be visible for other
** resource instances, which is wrong.
** The instance of binding object (single item
** in the binding list before merge) still can
** be shared between multiple resource instances
** as currently there is no attempt to merge
** multiple binding objects in the list into a
** combined object (the latter is theoretically
** possible if there is a way to tell two rights
** objects are equal).
** 005 NVS 20050425 @20894 Directory plugin needs special init sequence
** because everything is read from directory
** before contexts and ACLs are created.
** added securityIsUp() call at the end of
** security cache creation.
** 006 NVS 20050502 @20984 binding directory object has changed. It's no
** longer multi-valued. Multiple sortable
** objects are used instead.
** 007 NVS 20050505 @21109 Added serial number private field and method
** to get it. This is required for security
** cache refresh bug fixing. Changed the call to
** SecurityContext constructor for initial
** security context as now it stores the
** reference to this cache.
** 008 NVS 20050525 @21272 Resource plugins now provide scoped variables
** and their libraries are registered in single
** instance of SymbolResolver which has become
** the property of the security cache.
** 009 NVS 20050620 @21528 ACLs are divided into shared and server-
** specific ones. Shared ALCs are read from
** /security/acl node. Server-specific ones are
** read from /security/acls/serverId where the
** serverId portion matches the server ID.
** 010 NVS 20050727 @21867 Initial security context of security cache is
** now permanently bound to DirectoryService
** upon creation. bind()/unbind() calls removed
** from SecurityCache constructor.
** 011 NVS 20050912 @22674 Added constant definition for authentication
** result AUTH_RESULT_FAILURE_RETRIABLE and a
** method getAuthRetries() to get the configured
** number of retries.
** 012 NVS 20060330 @25284 Added getAllAccountIds() method.
** 013 NVS 20060417 @25562 Added password aging support: reading aging
** parameters from /security/config/change and
** storing password change date/time in the user
** accounts. Added new methods: getMaxAge() and
** getPasswordInput().
** 014 NVS 20060418 @25583 Password input hook instantiation had to be
** delayed till the use time.
** 015 NVS 20060426 @25742 Fixed a regression in the mergeAcls() method
** causing ACLs not to be merged properly.
** 016 GES 20061003 @30109 Moved constants to a separate class and made
** many code formatting cleanups.
** 017 NVS 20061011 @30321 Fixed a bug in getAllAccountIds() method that
** returned an empty list of accounts when type
** was specified as ACC_ALL.
** 018 GES 20061215 @31707 Changes in bootstrap config interface.
** 019 NVS 20090415 @41800 Cache generation messages can be routed to
** admin client, server console or both ways.
** 020 NVS 20090504 @42097 Added getCaAliases() and getPeerAliases()
** methods.
** 021 NVS 20090528 @42480 Custom account extension plugins are read
** from the directory and made available through
** getCustomServerExt(), getCustomClientExt().
** 022 NVS 20090602 @42548 Custom account extension plugin jar file name
** is read from the directory and made available
** through getCustomClientJar() method.
** 023 NVS 20090603 @42594 Supporting account enablement status.
** 024 NVS 20090604 @42595 Supporting password protected status.
** 025 NVS 20090615 @42696 Removed #022 due to a utilization of a simpler
** approach.
** 026 NVS 20090624 @42844 Massively changed the ACL creation logic due to
** move to the simplified ACL representation in the
** directory. Added the automatic ACL style detection
** and migration logic, which accepts the old style
** ACL nodes in the directory and converts them into
** their simplified new style equivalents, on the
** fly.
** 027 NVS 20090701 @43005 Creation of rights is done only after calling
** isRightsSetValid() plugin's method successfully.
** 028 NVS 20090824 @43727 Added a package private constructor for use in
** standalone security/directory utilites. It mimics
** the server environment to a degree sufficient to
** implement utility functions: reads accounts and
** loads the specified resource plugin.
** 029 NVS 20090825 @43737 Standalone environment also reads certificates.
** 030 NVS 20090826 @43774 Lowered log level for the "No holidays defined"
** message.
** 031 NVS 20090915 @43914 Fixed a bug in the readUsers() method not clearing
** groups local variable in the reading loop.
** 032 SIY 20100419 @44823 Implemented automatic adding of function into
** directory if it does not exists.
** 033 SVL 20100916 Added support for multiple custom auth plugins
** and auth mode inheritance.
** 034 LMR 20101217 Modified the constructors, readProcesses(),
** readUsers() and verifyServerId() to attempt to
** verify the alias of an account (process, user,
** server) by looking up a certificate for it in
** the trust store only if net/connection/secure
** option is set to true in the bootstrap config
** (defaults to false if it's missing).
** 035 GES 20111109 Added getUniqueInitialSecurityContext() as a
** helper to create and obtain a unique context that
** is used as the initial context for a thread that
** should not share its context with other server
** threads.
** 036 CA 20130529 Added appserver support.
** 037 CA 20130704 Added API to get the accounts for a specified appserver.
** 038 MAG 20131219 Added API to update user cached accounts.
** 039 CA 20140206 Changed H036: appserver agents can be started only from P2J processes.
** Implemented in-directory private keys.
** 111 MAG 20140715 Added broker property to process account.
** 112 ECF 20150715 Replace StringBuffer with StringBuilder.
** 113 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 114 CA 20160106 Log a warning if the loaded certificates use a RSA key length less
** than 2048 bits. Also, if the certificate is signed using a SHA-1
** algorithm, an error is logged.
** 115 SBI 20170225 Removed DataValue and TimeValue fields out of UserDef to be GWT
** serializable.
** 116 SBI 20171116 Changed constructor in order to fill web store from the directory,
** added getWebStore() and getWebServerAlias().
** 117 IAS 20210505 Added check for the certificate uniqueness.
** GES 20210902 Added settings to limit simultaneous interactive user sessions.
** CA 20220405 Added authentication and authorization for web requests. When this is enabled,
** the target API call will be executed under the authenticated FWD context, and not
** the agent's context.
** TJD 20220504 Java 11 compatibility minor changes
** 118 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 119 GBB 20230523 Change the logger level to WARNING if no configs have been set.
** 120 GBB 20230825 Adding support for OS users.
** 121 GBB 20240730 Remove the implicit dependency on DirectoryManager by replacing
** EnvironmentOps.isUnderWindowsFamily with PlatformHelper.
** 122 RAA 20241216 If there are multiple web certificates with the same alias, throw an exception.
** RAA 20241216 Added hint in the multiple web certificates aliases error message.
*/
/*
** 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.math.*;
import java.security.*;
import java.security.cert.*;
import java.security.interfaces.*;
import java.util.*;
import java.util.logging.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.admin.*;
import com.goldencode.p2j.security.SSLCertFactory.CertificateSuite;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.util.*;
/**
* Security cache is a collection of control data that originates in the P2J
* directory and/or the bootstrap configuration and is maintained in memory
* for quick access. Pieces of the cache are:
* <p>
* <ul>
* <li>truststore which holds all certificates;
* <li>authentication related data (contents of the authMode directory
* object);
* <li>process, user and group accounts;
* <li>resource plugins configuration;
* <li>access control lists
* </ul>
* <p>
* Only the first item is common between the client and server sides. The rest
* of the list is for the server side only.
*/
class SecurityCache
implements SecurityConstants
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(SecurityCache.class);
/** logging mode for admin client */
public final static int LM_ADMIN = 0;
/** logging mode for server console */
public final static int LM_CONSOLE = 1;
/** logging mode for admin client and server console */
public final static int LM_DUAL = 2;
/** logging mode for this cache instance */
private final int logMode;
/** Serial (generation) number of this instance of cache. */
private int serialNo = 0;
/** In-memory truststore. */
private KeyStore trustStore = null;
/** The key store used for https, defined by webCertificates */
private KeyStore webStore;
/** The web server's store alias, defined by webCertificates/alias */
private String webServerAlias;
/** CA certificate aliases in truststore */
private String[] caAliases = null;
/** Peer certificate aliases in truststore */
private String[] peerAliases = null;
/** All known accounts. */
private Account[] accs = null;
/** All known OS users. */
private OsUserAccount[] osUsers = null;
/** Server's ID. */
private String serverId = null;
/** Server's process account. */
private ProcessAccount serverAcc = null;
/** Server's truststore alias. */
private String serverAlias = null;
/** Initial security context. */
private SecurityContext initContext = null;
/** Authorization mode. */
private int authMode = 0;
/**
* The number of authorization retries, where:
* -1 unlimited retries
* 0 no retries
* > 0 specific limit
*/
private int authRetries = 0;
/**
* Set of available auth plugins (plugin class -> plugin description
* object).
*/
private Map<String, AuthPlugin> authPlugins = null;
/** Authorization hook class name. */
private String hookName = null;
/** SSO authorization hook. */
private SsoAuthenticator ssoHook = null;
/** Account ID for anonymous processes or <code>null</code> to disallow. */
private String anonymId = null;
/** Registry of abstract resource plugins. */
private ResourceRegistry[] registry = null;
/** Ordinal number of the "system" resource in registry. */
private int systemOrdinal = -1;
/** Set of holidays. */
private Set<String> holidays = null;
/** Audit control. */
private Audit audit = null;
/** Instance of symbol resolver. */
private Resolver resolver = null;
/** Maximum number of days user password is valid; 0 means unlimited. */
private int maxAge = 0;
/** Password input hook. */
private String pwInputHook = null;
/** Maximum number of simultaneous interactive user accounts, -1 means unlimited. */
private int maxInteractiveUsers = -1;
/** Maximum number of simultaneous sessions per each interactive user, -1 means unlimited. */
private int maxSessionsPerUser = -1;
/** instance of security manager */
private SecurityManager sm = null;
/** buffer to receive messages */
List<String[]> msgBuf = null;
/** Custom account extensions server plugin class name. */
private String customServerExt = null;
/** Custom account extensions client plugin class name. */
private String customClientExt = null;
/** Encryption passwords for each SSL private key, per alias. */
private Map<String, String> privateKeyPasswords = new HashMap<>();
/** Encrypted SSL private keys, per alias.*/
private Map<String, byte[]> privateKeys = new HashMap<>();
/** The SSL certificate factory. */
private SSLCertFactory sslFactory = null;
static
{
// sets the default logger level to WARNING, if no configs have set the logger level yet
if (LOG.getExplicitLevel() == null)
{
LOG.setLevel(Level.WARNING);
}
}
/**
* Package private constructor.
*
* @param bc
* An instance of <code>BootstrapConfig</code> class used to get
* the security related configuration information.
* @param newSerial
* Serial number for the generation of the security cache being
* created.
*/
SecurityCache(BootstrapConfig bc, int newSerial)
throws ConfigurationException,
NoSuchMethodException
{
this(bc, newSerial, LM_CONSOLE, null);
}
/**
* Package private constructor.
*
* @param bc
* An instance of <code>BootstrapConfig</code> class used to get
* the security related configuration information.
* @param newSerial
* Serial number for the generation of the security cache being
* created.
* @param logMode
* specifies logging for the admin client, or server console,
* or both
* @param msgBuf
* list to receive messages if logmode enables admin messages
*/
SecurityCache(BootstrapConfig bc, int newSerial, int logMode, List<String[]> msgBuf)
throws ConfigurationException,
NoSuchMethodException
{
this.logMode = logMode;
this.msgBuf = msgBuf;
// security cache is a server-side only initialization
if (!bc.isServer())
return;
sm = SecurityManager.getInstance();
// get this server ID
serverId = bc.getConfigItem("security", "server", "id");
if (serverId == null)
throw new ConfigurationException("no server ID defined");
// access directory
DirectoryService ds = DirectoryService.getInstance();
boolean secure = bc.getBoolean("net", "connection", "secure", false);
if(secure)
{
// create truststore
try
{
trustStore = KeyStore.getInstance("JKS");
trustStore.load(null, null);
}
catch (Exception e)
{
throw new ConfigurationException("can't create empty truststore", e);
}
// read from /security/certificates/CAs into the truststore
readCAs(trustStore, ds);
// verify that all CA certificates are valid and resolvable into a chain
verifyCAs(trustStore);
// read from /security/certificates/peers into the truststore
// these are verified while reading
readPeers(trustStore, ds);
// read the certificates' private keys, their passwords and other configuration
readPrivateKeys(bc, ds);
readRSAConfig(bc, ds);
}
// read from /security/config/auth-mode
readAuthMode(ds);
//
// read all accounts
//
// read from /security/accounts/osusers
osUsers = readOsUsers(ds);
List<Account> accounts = new LinkedList<>();
// read from /security/accounts/groups
readGroups(ds, accounts);
// convert the list into a temporary array
accs = new Account[accounts.size()];
accs = accounts.toArray(accs);
// read from /security/accounts/processes
readProcesses(ds, accounts, secure);
// read from /security/accounts/users
readUsers(ds, accounts, secure);
// convert the list into an array
accs = new Account[0];
accs = accounts.toArray(accs);
accounts = null;
// verify that the server ID in bootstrap configuration matches a server
// account
verifyServerId(secure);
// read list of holidays
holidays = new HashSet<String>();
readHolidays(ds);
// read password aging parameters
readPasswordAging(ds);
// read user session limits
readInteractiveUserLimits(ds);
// verify the anonymous process account
verifyAnonymousId();
// read and register abstract resource plugins
readPlugins(ds, null);
systemOrdinal = getRegistryOrdByTypeName("system");
if (systemOrdinal == -1)
throw new ConfigurationException(
"no plugin defined for system resource");
// read custom server extension name
customServerExt = readCustomExtension(ds,
"/security/config/extensions/server");
if (customServerExt == null)
{
dWarn("No custom server extension defined in P2J directory");
}
else
{
dList("Using custom server extention " + customServerExt);
}
// read custom client extension name
customClientExt = readCustomExtension(ds,
"/security/config/extensions/client");
if (customClientExt == null)
{
dWarn("No custom client extension defined in P2J directory");
}
else
{
dList("Using custom client extention " + customClientExt);
}
// read and organize ACLs
readACLs(ds);
// read and organize audit related configuration
readAudit(ds);
// create the initial security context
initContext = new SecurityContext(this, getOrdinalById(serverId), -1,
null, registry.length, true);
serialNo = newSerial;
try
{
WebCertificates reader = new WebCertificates(getSSLCertFactory());
webServerAlias = reader.getWebAlias(ds, serverId);
if (webServerAlias != null && !webServerAlias.trim().isEmpty())
{
CertificateSuite suite = reader.getWebCertificates(ds, serverId);
String keyPassword = SSLCertGenUtil.createAES256BitKey();
byte[] encrypted = getSSLCertFactory().encryptPrivateKey(suite.privateKey, keyPassword);
if (privateKeys.putIfAbsent(webServerAlias, encrypted) != null)
{
throw new Exception("The web certificate alias " +
webServerAlias +
" already exists. Check both web and non-web certificate aliases. ");
}
privateKeyPasswords.put(webServerAlias, keyPassword);
webStore = SSLCertGenUtil.createEmptyStore();
webStore.setKeyEntry(webServerAlias,
suite.privateKey,
keyPassword.toCharArray(),
suite.getFullChain());
}
}
catch (Exception e)
{
StringWriter writer = new StringWriter();
new ConfigurationException("can't load web certificates", e).printStackTrace(
new PrintWriter(writer));
dWarn(writer.getBuffer().toString());
}
}
/**
* Package private constructor to mimic the server environment for
* standalone utilities.
*
* @param logMode
* specifies logging for the admin client, or server console,
* or both
* @param msgBuf
* list to receive messages if logmode enables admin messages
* @param plugins
* array of resource plugins to be loaded or <code>null</code>
*/
SecurityCache(int logMode, List<String[]> msgBuf, String[] plugins)
throws ConfigurationException,
NoSuchMethodException
{
this.logMode = logMode;
this.msgBuf = msgBuf;
sm = SecurityManager.getInstance();
// create fake server name and account
serverId = "standalone";
serverAcc = new ProcessAccount(serverId, true, "simulation", serverId, true, true, null, null);
DirectoryService ds = DirectoryService.getInstance();
// create truststore
try
{
trustStore = KeyStore.getInstance("JKS");
trustStore.load(null, null);
}
catch (Exception e)
{
throw new ConfigurationException("can't create empty truststore", e);
}
// read from /security/certificates/CAs into the truststore
readCAs(trustStore, ds);
// verify that all CA certificates are valid and resolvable into a chain
verifyCAs(trustStore);
// read from /security/certificates/peers into the truststore
// these are verified while reading
readPeers(trustStore, ds);
// load resource plugins
if (plugins != null)
{
readPlugins(ds, plugins);
}
//
// read all accounts
//
List<Account> accounts = new LinkedList<>();
// read from /security/accounts/groups
readGroups(ds, accounts);
// convert the list into a temporary array
accs = new Account[accounts.size()];
accs = accounts.toArray(accs);
// read from /security/accounts/processes
readProcesses(ds, accounts, false);
// read from /security/accounts/users
readUsers(ds, accounts, false);
// convert the list into an array
accs = new Account[0];
accs = accounts.toArray(accs);
accounts = null;
}
/**
* Checks to see if the given subject ID is unique.
*
* @param id
* Account ID to check.
* @param list
* List of defined accounts.
*
* @return <code>true</code> if subject ID is unique.
*/
private static boolean checkSubjectId(String id, List<Account> list)
{
Iterator<Account> it = list.iterator();
Account acc = null;
while (it.hasNext())
{
acc = it.next();
if (acc.getSubjectId().compareToIgnoreCase(id) == 0)
return false;
}
return true;
}
/**
* Gets this security cache's serial number.
*
* @return Serial number.
*/
int getSerial()
{
return serialNo;
}
/**
* Refresh all plugins.
*
* @param ae
*/
void refreshAll(AdminAccountExtension ae)
{
for (ResourceRegistry resource : registry)
{
resource.getPlugin().refresh(ae);
}
}
/**
* Verifies the validity of certification authority certificates loaded
* into the trust store.
* <p>
* CA certificates have to:
* <ul>
* <li>be within the validity dates;
* <li>have CA:TRUE in X509v3 Basic Constraints;
* <li>be either self-signed or signed by another valid CA
* from the trust store;
* </ul>
* Non-compliant certificates are deleted from the trust store.
*
* @param trustStore
* Truststore loaded with CA certificates to verify.
*
* @throws ConfigurationException
* If the trust store is or becomes empty.
*/
void verifyCAs(KeyStore trustStore)
throws ConfigurationException
{
// TODO: not yet implemented
if (trustStore == null)
throw new ConfigurationException("");
}
/**
* Returns the trust store.
*
* @return Current truststore.
*/
KeyStore getTrustStore()
{
return trustStore;
}
/**
* Returns the web store.
*
* @return Current web store.
*/
KeyStore getWebStore()
{
return webStore;
}
/**
* Gets the web server certificate alias.
*
* @return The web server certificate alias
*/
String getWebServerAlias()
{
return webServerAlias;
}
/**
* Gets authorization mode.
*
* @return Authorization mode.
*/
int getAuthMode()
{
return authMode;
}
/**
* Gets the maximum number of authorization retries.
*
* @return Authorization retry limit.
*/
int getAuthNumRetries()
{
return authRetries;
}
/**
* Gets anonymous account ID.
*
* @return Anonymous account ID.
*/
String getAnonymId()
{
return anonymId;
}
/**
* Gets custom server extension plugin name.
*
* @return custom server extension plugin name
*/
String getCustomServerExt()
{
return customServerExt;
}
/**
* Gets custom client extension plugin name.
*
* @return custom client extension plugin name
*/
String getCustomClientExt()
{
return customClientExt;
}
/**
* Gets authentication hook name.
*
* @return Authentication hook name.
*/
String getHookName()
{
return hookName;
}
/**
* Gets SSO authentication hook.
*
* @return SSO authentication hook.
*/
SsoAuthenticator getSsoHook()
{
return ssoHook;
}
/**
* Gets the initial security context.
*
* @return Initial server's <code>SecurityContext</code>.
*/
SecurityContext getInitialSecurityContext()
{
return initContext;
}
/**
* Gets a new and unique instance of an initial security context.
*
* @return Unique initial <code>SecurityContext</code> which is not
* associated with any account.
*/
SecurityContext getUniqueInitialSecurityContext()
{
return new SecurityContext(this, -1, -1, null, registry.length, true);
}
/**
* Gets an OS user by its ID. Comparison is case-insensitive.
*
* @param id
* OS user ID.
*
* @return <code>OsUserAccount</code></code> object or <code>null</code> if not found.
*/
OsUserAccount getOsUserById(String id)
{
if (id == null)
return null;
if (PlatformHelper.isUnderWindowsFamily())
{
for (int i = 0; i < osUsers.length; i++)
{
if (osUsers[i].getSubjectId().compareTo(id) == 0)
return osUsers[i];
}
}
else
{
for (int i = 0; i < osUsers.length; i++)
{
if (osUsers[i].getSubjectId().compareToIgnoreCase(id) == 0)
return osUsers[i];
}
}
return null;
}
/**
* Gets all OS users.
*
* @return An array of <code>OsUserAccount</code></code> objects.
*/
OsUserAccount[] getAllOsUsers()
{
return osUsers;
}
/**
* Gets an account by its ID.
* <p>
* Comparison is case-insensitive.
*
* @param id
* Account ID.
*
* @return <code>Account</code> object or <code>null</code> if not found.
*/
Account getAccountById(String id)
{
if (id == null)
return null;
for (int i = 0; i < accs.length; i++)
{
if (accs[i].getSubjectId().compareToIgnoreCase(id) == 0)
return accs[i];
}
return null;
}
/**
* Get the process accounts configured to start the given appserver. If the
* <code>appServer</code> is <code>null</code>, this will return an array of length 0.
*
* @param appServer
* The appserver for which the accounts are needed.
*
* @return The process accounts configured for this appServer, if any.
*/
String[] getAccountsForAppserver(String appServer)
{
if (appServer == null)
{
return new String[0];
}
List<String> subjects = new ArrayList<String>();
for (Account a : accs)
{
if (a instanceof ProcessAccount)
{
ProcessAccount pa = (ProcessAccount) a;
if (appServer.equals(pa.getAppServer()))
{
subjects.add(a.getSubjectId());
}
}
}
return subjects.toArray(new String[0]);
}
/**
* Gets IDs of all defined accounts of the specified type.
*
* @param type
* Account type.
*
* @return The array of all defined account IDs of the type.
*/
public String[] getAllAccountIds(int type)
{
List<String> l = new LinkedList<>();
// scan the array of accounts and select only those of the given type
for (int i = 0; i < accs.length; i++)
{
if (accs[i].getAccountType() != type && type != Account.ACC_ALL)
continue;
l.add(accs[i].getSubjectId());
}
// convert the list of IDs into an array
String[] ids = l.toArray(new String[] {});
return ids;
}
/**
* Gets the number of user or process accounts by a trust store alias.
* <p>
* Comparison is case-insensitive.
*
* @param alias
* Trust store alias.
* @param type
* Account type.
*
* @return <code>Account</code> object or <code>null</code> if not
* found.
*/
int getCountByAlias(String alias, int type)
{
if (alias == null)
return 0;
int n = 0;
for (int i = 0; i < accs.length; i++)
{
if (accs[i].getAccountType() != type)
continue;
if (accs[i].getAlias() != null &&
accs[i].getAlias().compareToIgnoreCase(alias) == 0)
n++;
}
return n;
}
/**
* Gets an array of user or process accounts by a trust store alias. In
* general, there may be more than one account sharing a trust store alias.
* <p>
* Comparison is case-insensitive.
*
* @param alias
* Trust store alias.
* @param type
* Account type.
*
* @return Array of accounts.
*/
Account[] getAccountsByAlias(String alias, int type)
{
if (alias == null)
return null;
List<Account> list = new LinkedList<>();
for (int i = 0; i < accs.length; i++)
{
if (accs[i].getAccountType() != type)
continue;
if (accs[i].getAlias() != null &&
accs[i].getAlias().compareToIgnoreCase(alias) == 0)
list.add(accs[i]);
}
return list.toArray(new Account[0]);
}
/**
* Gets account's ordinal number by its ID.
*
* @param id
* Account ID. {@code null} or empty string will not match any account.
*
* @return Ordinal number or -1 if not found.
*/
int getOrdinalById(String id)
{
if (id == null || id.length() == 0)
{
return -1;
}
for (int i = 0; i < accs.length; i++)
{
if (accs[i].getSubjectId().compareToIgnoreCase(id) == 0)
{
return i;
}
}
return -1;
}
/**
* Gets an account by its ordinal number.
*
* @param ordinal
* Account ordinal.
*
* @return <code>Account</code> object or <code>null</code>.
*/
Account getAccountByOrd(int ordinal)
{
if (ordinal < 0 || ordinal > accs.length - 1)
return null;
else
return accs[ordinal];
}
/**
* Gets the number of registered resource plugins.
*
* @return Number of registered resource plugins.
*/
int getNumResources()
{
if (registry == null)
return 0;
else
return registry.length;
}
/**
* Gets a registry ordinal by the resource type name.
* <p>
* Comparison is case-sensitive.
*
* @param typeName
* Abstract resource type name.
*
* @return Index of the plugin which provides the named resource or -1
* for no match.
*/
int getRegistryOrdByTypeName(String typeName)
{
if (typeName == null)
return -1;
for (int i = 0; i < registry.length; i++)
{
if (registry[i].getTypeName().equals(typeName))
return i;
}
return -1;
}
/**
* Gets a registry entry by its ordinal number.
*
* @param ordinal
* Index (ordinal number) of the abstract resource.
*
* @return <code>ResourceRegistry</code> registered under this index or
* <code>null</code>.
*/
ResourceRegistry getRegistryByOrd(int ordinal)
{
if (ordinal < 0 || ordinal > registry.length - 1)
return null;
return registry[ordinal];
}
/**
* Gets the defined set of holidays.
*
* @return <code>Set</code> containing dates of all defined holidays as
* <code>String</code>s formatted to "yyyy-mm-dd".
*/
Set<String> getHolidays()
{
return holidays;
}
/**
* Gets the Audit object from this cache.
*
* @return <code>Audit</code> defined in this cache.
*/
Audit getAudit()
{
return audit;
}
/**
* Gets the system resource ordinal number.
*
* @return System ordinal number.
*/
int getSystem()
{
return systemOrdinal;
}
/**
* Gets this server's account.
*
* @return Server's account.
*/
ProcessAccount getServerAccount()
{
return serverAcc;
}
/**
* Gets the symbol resolver.
*
* @return <code>Resolver</code> object.
*/
Resolver getResolver()
{
return resolver;
}
/**
* Gets the maximum password age in days.
*
* @return Maximum allowed password age where 0 means unlimited age.
*/
int getMaxAge()
{
return maxAge;
}
/**
* Gets the maximum number of simultaneous interactive users.
*
* @return Maximum allowed simultaneous interactive users where -1 means there is no limit.
*/
int getMaxInteractiveUsers()
{
return maxInteractiveUsers;
}
/**
* Gets the maximum number of simultaneous sessions per interactive user.
*
* @return Maximum allowed simultaneous sessions per interactive user where -1 means there is no limit.
*/
int getMaxSessionsPerUser()
{
return maxSessionsPerUser;
}
/**
* Gets the password input hook class name.
*
* @return Class name for the preconfigured password input hook.
*/
String getPasswordInput()
{
return pwInputHook;
}
/**
* Gets the array of CA certificate aliases.
*
* @return array of CA certificate aliases
*/
String[] getCaAliases()
{
return caAliases;
}
/**
* Gets the array of peer certificate aliases.
*
* @return array of peer certificate aliases
*/
String[] getPeerAliases()
{
return peerAliases;
}
/**
* Get the encryption password for the SSL private key associated with the given alias.
* <p>
* Only the server can access this.
*
* @param alias
* The alias.
*
* @return See above.
*/
String getKeyEntryPassword(String alias)
{
if (!(serverAcc.isServer() && serverAcc.isMaster()))
{
return null;
}
return privateKeyPasswords.get(alias);
}
/**
* Get the factory used to create SSL private keys and certificates.
*
* @return The {@link BCCertFactory Bouncy Castle} {@link #sslFactory factory}.
*
* @throws SSLCertGenException
* If an exception occurs during its instantiation.
*/
SSLCertFactory getSSLCertFactory()
throws SSLCertGenException
{
if (sslFactory == null)
{
sslFactory = new BCCertFactory();
}
return sslFactory;
}
/**
* Get the encrypted SSL private key associated with the given alias.
*
* @param alias
* The alias.
*
* @return See above.
*/
byte[] getPrivateKey(String alias)
{
return privateKeys.get(alias);
}
/**
* Get auth plugin description object by its class.
*
* @param pluginClass
* Class of the required plugin.
*
* @return auth plugin description object.
*/
AuthPlugin getAuthPlugin(String pluginClass)
{
return authPlugins.get(pluginClass);
}
/**
* Get the list of available auth plugins.
*
* @return See above.
*/
AuthPlugin[] getAuthPlugins()
{
return authPlugins.values().toArray(new AuthPlugin[authPlugins.size()]);
}
/**
* Reads the P2J directory and loads CA certificates into truststore.
*
* @param trustStore
* The target trust store.
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*/
private void readCAs(KeyStore trustStore, DirectoryService ds)
{
try
{
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = null;
byte[] data = null;
ByteArrayInputStream bis = null;
String fullId = null;
String path = "/security/certificates/CAs";
// enumerate CA certificates
String[] cas = ds.enumerateNodes(path);
int csize = cas == null ? 0 : cas.length;
if (csize == 0)
{
cas = new String[] {};
dWarn("No CA certificates defined in the P2J directory");
}
// keep the list of aliases
caAliases = cas;
int stats = 0;
int statf = 0;
// process existing nodes
for (int i = 0; i < csize; i++)
{
fullId = new String(path + "/" + cas[i]);
if (!ds.getNodeClass(fullId).equals("/meta/class/bytes"))
{
dList("Object " + fullId + " ignored");
statf++;
continue;
}
data = ds.getNodeByteArray(fullId, "value");
bis = new ByteArrayInputStream(data);
cert = (X509Certificate)cf.generateCertificate(bis);
checkCertificate(trustStore, cas[i], cert);
trustStore.setCertificateEntry(cas[i], cert);
dList("Loaded CA cert of " +
sm.certificateSm.getSubjectCommonName(cert));
stats++;
}
dStat("Loaded " + stats + " CA certificates, " + statf +
" objects ignored");
}
catch (Exception e)
{
dErr("Exception while reading CA certificates " + e);
}
}
/**
* Check the RSA key length and encryption algorithm for the given certificate. A warning is
* logged if the RSA key length is less than {@link SSLCertFactory#MIN_RSA_KEY_STRENGTH} bits.
* Also, an error is logged if the encryption algorithm is SHA-1.
*
* @throws CertificateParsingException
* If the certificate's common-name can not be determined.
* @throws KeyStoreException
* If keystore lookup by certificate failed.
*/
private void checkCertificate(KeyStore trustStore, String alias, X509Certificate cert)
throws CertificateParsingException,
KeyStoreException
{
String existing = trustStore.getCertificateAlias(cert);
if (existing != null)
{
if (!existing.equals(alias))
{
LOG.warning(String.format(
"Identical certificates found for aliases [%s] and [%s]. " +
"Certificates MUST NOT be used for more than one alias",
alias, existing
));
}
}
PublicKey pkey = cert.getPublicKey();
if (pkey instanceof RSAPublicKey)
{
RSAPublicKey rsaPk = (RSAPublicKey) pkey;
int keyLength = rsaPk.getModulus().bitLength();
if (keyLength < SSLCertFactory.MIN_RSA_KEY_STRENGTH)
{
String cn = sm.certificateSm.getSubjectCommonName(cert);
String msg = "Certificate alias [%s] for [CN=%s] was signed with a RSA key of " +
"length %d. A minimum key length of %d is recommended.";
dWarn(String.format(msg, alias, cn, keyLength, SSLCertFactory.MIN_RSA_KEY_STRENGTH));
}
}
String algName = cert.getSigAlgName();
if (algName.startsWith("SHA1"))
{
String cn = sm.certificateSm.getSubjectCommonName(cert);
String msg = "Certificate alias [%s] for [CN=%s] was signed using the %s algorithm. " +
"Use a SHA-2 implementation instead.";
dErr(String.format(msg, alias, cn, algName));
}
}
/**
* Reads the P2J directory and loads peers certificates into truststore.
* <p>
* Peer certificates are verified before they are put into the trust store.
* They have to:
* <ul>
* <li>be within the validity dates;
* <li>be signed by a CA, not self-signed;
* </ul>
*
* @param trustStore
* The target trust store.
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*/
private void readPeers(KeyStore trustStore, DirectoryService ds)
{
try
{
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = null;
byte[] data = null;
ByteArrayInputStream bis = null;
String fullId = null;
String path = "/security/certificates/peers";
// enumerate peer certificates
String[] peers = ds.enumerateNodes(path);
int psize = peers == null ? 0 : peers.length;
if (psize == 0)
{
peers = new String[] {};
dWarn("No peer certificates defined in the P2J directory");
}
// keep the list of aliases
peerAliases = peers;
int stats = 0;
int statf = 0;
// process existing nodes
for (int i = 0; i < psize; i++)
{
fullId = new String(path + "/" + peers[i]);
if (!ds.getNodeClass(fullId).equals("/meta/class/bytes"))
{
dList("Object " + fullId + " ignored");
statf++;
continue;
}
// read the certificate data from directory
data = ds.getNodeByteArray(fullId, "value");
bis = new ByteArrayInputStream(data);
cert = (X509Certificate)cf.generateCertificate(bis);
checkCertificate(trustStore, peers[i], cert);
// verify the certificate
// to be implemented
// add the certificate to the trust store
trustStore.setCertificateEntry(peers[i], cert);
dList("Loaded peer cert of " +
sm.certificateSm.getSubjectCommonName(cert));
stats++;
}
dStat("Loaded " + stats + " peer certificates, " + statf +
" objects ignored");
}
catch (Exception e)
{
dErr("Exception while reading peer certificates " + e);
}
}
/**
* Read the RSA configuration.
*
* @param cfg
* An instance of <code>BootstrapConfig</code> class used to get
* the security related configuration information.
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*/
private void readRSAConfig(BootstrapConfig cfg, DirectoryService ds)
{
String root = "/security/certificates/company/";
Integer strength = null;
BigInteger exponent = null;
try
{
strength = ds.getNodeInteger(root + "rsaKeyStrength", "value");
String exp = ds.getNodeString(root + "rsaPublicExponent", "value");
if (exp != null)
{
exponent = new BigInteger(exp);
}
}
catch (Exception e)
{
dErr("Exception while reading RSA configuration " + e);
}
try
{
sslFactory = getSSLCertFactory();
sslFactory.init(strength, exponent);
}
catch (SSLCertGenException e)
{
throw new RuntimeException("Could not instantiate the SSL factory!", e);
}
}
/**
* Read the SSL private keys and their encryption keys.
*
* @param cfg
* An instance of <code>BootstrapConfig</code> class used to get
* the security related configuration information.
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*/
private void readPrivateKeys(BootstrapConfig cfg, DirectoryService ds)
{
try
{
String path = "/security/certificates/private-keys/";
String[] aliases = ds.enumerateNodes(path);
if (aliases == null)
{
dWarn("No private keys defined in the P2J directory");
return;
}
for (String alias : aliases)
{
String aid = path + "/" + alias;
String kpId = aid + "/key-password";
String keId = aid + "/key-entry";
if (!ds.getNodeClass(keId).equals("/meta/class/bytes"))
{
dList("Object " + aid + " ignored");
continue;
}
String kp = null;
if (ds.getNodeClass(kpId) == null)
{
kp = cfg.getString("access", "password", "masterkeyentry", null);
if (kp == null)
{
// falback to per-alias password
kp = cfg.getString("access", "password", "keyentry-" + alias, null);
if (kp == null)
{
dList("Object " + aid + " ignored - no masterkeyentry or " +
"keyentry-" + alias + " password provided");
continue;
}
}
}
else
{
byte[] kpData = ds.getNodeByteArray(kpId, "value");
kp = new String(kpData);
}
byte[] keData = ds.getNodeByteArray(keId, "value");
privateKeyPasswords.put(alias, kp);
privateKeys.put(alias, keData);
dList("Loaded private key for alias " + alias);
}
}
catch (Exception e)
{
dErr("Exception while reading SSL private keys " + e);
}
}
/**
* Reads the P2J directory and loads defined group accounts.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
* @param list
* List to which to add new accounts.
*/
private void readGroups(DirectoryService ds, List<Account> list)
{
String text = null;
String fullId = null;
String path = "/security/accounts/groups";
// enumerate group accounts
String[] groups = ds.enumerateNodes(path);
int gsize = groups == null ? 0 : groups.length;
if (gsize == 0)
{
dWarn("No user groups defined in the P2J directory");
return;
}
int stats = 0;
int statf = 0;
// process existing nodes
for (int i = 0; i < gsize; i++)
{
fullId = new String(path + "/" + groups[i]);
if (!ds.getNodeClass(fullId).equals("/meta/class/group"))
{
dList("Object " + fullId + " ignored");
statf++;
continue;
}
text = ds.getNodeString(fullId, "description");
Integer mode = ds.getNodeInteger(fullId, "mode");
String authPlugin = ds.getNodeString(fullId, "auth-plugin");
list.add(new GroupAccount(groups[i], text, mode != null ? mode : 0, authPlugin));
dList("Loaded group <" + groups[i] + ">");
stats++;
}
dStat("Loaded " + stats + " groups, " + statf + " objects ignored");
}
/**
* Reads the P2J directory and loads defined process accounts.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
* @param list
* List to which to add new accounts.
* @param secure
* Specifies whether secure environment is enabled.
*/
private void readProcesses(DirectoryService ds, List<Account> list, boolean secure)
{
String text = null;
String alias = null;
String broker = null;
Boolean isEnabled = null;
Boolean isServer = null;
Boolean isMaster = null;
String appServer = null;
String fullId = null;
String path = "/security/accounts/processes";
boolean enabled = true;
// enumerate process accounts
String[] procs = ds.enumerateNodes(path);
int psize = procs == null ? 0 : procs.length;
if (psize == 0)
dWarn("No processes defined in the P2J directory");
int stats = 0;
int statf = 0;
// process existing nodes
for (int i = 0; i < psize; i++)
{
fullId = new String(path + "/" + procs[i]);
if (!ds.getNodeClass(fullId).equals("/meta/class/process"))
{
dList("Object " + fullId + " ignored");
statf++;
continue;
}
isEnabled = ds.getNodeBoolean(fullId, "enabled");
text = ds.getNodeString(fullId, "description");
alias = ds.getNodeString(fullId, "alias");
isServer = ds.getNodeBoolean(fullId, "server");
isMaster = ds.getNodeBoolean(fullId, "master");
appServer = ds.getNodeString(fullId, "appserver");
broker = ds.getNodeString(fullId, "broker");
if (isEnabled != null)
{
enabled = isEnabled.booleanValue();
}
else
{
enabled = true;
}
// verify user alias
if (alias != null)
{
if (secure)
{
try
{
java.security.cert.Certificate cert =
trustStore.getCertificate(alias);
if (cert == null)
{
// no certificate with this alias
// don't import this account
dList("Object " + fullId + " has no certificate");
statf++;
continue;
}
}
catch (KeyStoreException e)
{
// problem accessing certificate for this alias
// don't import this account
dList("Object " + fullId +
" has problems accessing certificate " + e);
statf++;
continue;
}
}
}
// check subject ID uniqueness
if (!checkSubjectId(procs[i], list))
{
// ignore non-unique account ID
dList("Object " + fullId + " defines duplicate account ID");
statf++;
continue;
}
list.add(new ProcessAccount(procs[i], enabled, text, alias, isServer, isMaster, appServer, broker));
dList("Loaded process <" + procs[i] + ">");
stats++;
}
dStat("Loaded " + stats + " processes, " + statf +
" objects ignored");
}
/**
* Reads the P2J directory and loads defined OS user accounts.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*
* @return An array of OS user accounts defined in directory.
*/
private OsUserAccount[] readOsUsers(DirectoryService ds)
{
String path = "/security/accounts/osusers";
String[] userIds = ds.enumerateNodes(path);
if (userIds == null || userIds.length == 0)
{
dWarn("No OS users defined in the P2J directory");
return new OsUserAccount[0];
}
int loadedCount = 0;
int ignoredCount = 0;
Map<String, OsUserAccount> osUserIdAccounts = new HashMap<>();
for (int i = 0; i < userIds.length; i++)
{
String subId = userIds[i];
String fullId = path + "/" + subId;
if (!ds.getNodeClass(fullId).equals("/meta/class/osuser"))
{
dList("Object " + fullId + " ignored");
ignoredCount++;
continue;
}
String description = ds.getNodeString(fullId, "description");
Boolean isEnabled = ds.getNodeBoolean(fullId, "enabled");
boolean enabled = isEnabled != null ? isEnabled : true;
byte[] password = ds.getNodeByteArray(fullId, "password");
DateValue passChangeDate = ds.getNodeDate(fullId, "pwsetdate");
TimeValue passChangeTime = ds.getNodeTime(fullId, "pwsettime");
// check subject ID uniqueness
if (osUserIdAccounts.containsKey(subId.toLowerCase()))
{
dList("Object " + fullId + " defines duplicate OS user ID");
ignoredCount++;
continue;
}
OsUserAccount osUser = new OsUserAccount(subId,
description,
enabled,
password,
passChangeDate,
passChangeTime);
osUserIdAccounts.put(subId.toLowerCase(), osUser);
dList("Loaded OS user <" + userIds[i] + ">");
loadedCount++;
}
dStat("Loaded " + loadedCount + " users, " + ignoredCount + " objects ignored");
return osUserIdAccounts.values().toArray(new OsUserAccount[0]);
}
/**
* Reads the P2J directory and loads defined user accounts.
* <p>
* Group accounts must be created by the time of this call as user
* accounts have to be converted to contain ordinal references to groups
* instead of IDs.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
* @param list
* List to which to add new accounts.
* @param secure
* Specifies whether secure environment is enabled.
*/
private void readUsers(DirectoryService ds, List<Account> list, boolean secure)
{
Boolean isEnabled = null;
Boolean isProtected = null;
String person = null;
String alias = null;
String[] groupIds = null;
byte[] password = null;
Integer mode = null;
String authPlugin = null;
String osUser = null;
String webServiceToken = null;
int[] groups = null;
DateValue cdate = null;
TimeValue ctime = null;
int j = 0;
String fullId = null;
String path = "/security/accounts/users";
boolean enabled = true;
boolean protect = true;
// enumerate user accounts
String[] users = ds.enumerateNodes(path);
int usize = users == null ? 0 : users.length;
if (usize == 0)
dWarn("No users defined in the P2J directory");
int stats = 0;
int statf = 0;
// process existing nodes
for (int i = 0; i < usize; i++)
{
fullId = new String(path + "/" + users[i]);
if (!ds.getNodeClass(fullId).equals("/meta/class/user"))
{
dList("Object " + fullId + " ignored");
statf++;
continue;
}
isEnabled = ds.getNodeBoolean(fullId, "enabled");
isProtected = ds.getNodeBoolean(fullId, "protected");
person = ds.getNodeString(fullId, "person");
alias = ds.getNodeString(fullId, "alias");
password = ds.getNodeByteArray(fullId, "password");
cdate = ds.getNodeDate(fullId, "pwsetdate");
ctime = ds.getNodeTime(fullId, "pwsettime");
groupIds = ds.getNodeStrings(fullId, "groups");
mode = ds.getNodeInteger(fullId, "mode");
authPlugin = ds.getNodeString(fullId, "auth-plugin");
osUser = ds.getNodeString(fullId, "osuser");
webServiceToken = ds.getNodeString(fullId, "webServiceToken");
if (isEnabled != null)
{
enabled = isEnabled.booleanValue();
}
else
{
enabled = true;
}
if (isProtected != null)
{
protect = isProtected.booleanValue();
}
else
{
protect = true;
}
int gsize = groupIds == null ? 0 : groupIds.length;
if (gsize > 0)
{
groups = new int[gsize];
// produce ordinals for group IDs
for (j = 0; j < gsize; j++)
groups[j] = getOrdinalById(groupIds[j]);
}
else
{
groups = null;
}
// verify user alias
if (alias != null)
{
if (secure)
{
try
{
java.security.cert.Certificate cert =
trustStore.getCertificate(alias);
if (cert == null)
{
// no certificate with this alias
// don't import this account
dList("Object " + fullId + " has no certificate");
statf++;
continue;
}
}
catch (KeyStoreException e)
{
// problem accessing certificate for this alias
// don't import this account
dList("Object " + fullId +
" has problems accessing certificate " + e);
statf++;
continue;
}
}
}
// check subject ID uniqueness
if (!checkSubjectId(users[i], list))
{
// ignore non-unique account ID
dList("Object " + fullId + " defines duplicate account ID");
statf++;
continue;
}
// get effective user permisions
boolean modeInherited = false;
if (mode == null || mode == SecurityConstants.AUTH_MODE_NONE)
{
if (groups != null)
{
for (int groupOrd : groups)
{
Account acc = getAccountByOrd(groupOrd);
if (acc != null && acc instanceof GroupAccount)
{
GroupAccount groupAccount = (GroupAccount) acc;
if (groupAccount.getAuthMode() !=
SecurityConstants.AUTH_MODE_NONE)
{
if (!modeInherited)
{
mode = groupAccount.getAuthMode();
authPlugin = groupAccount.getAuthPlugin();
modeInherited = true;
}
else if (mode != groupAccount.getAuthMode() ||
(authPlugin != null &&
!authPlugin.equals(groupAccount.getAuthPlugin())))
{
StringBuilder sb = new StringBuilder("User ");
sb.append(users[i]);
sb.append(" consists into multiple groups with");
sb.append(" different auth modes");
LOG.warning(sb.toString());
break;
}
}
}
}
}
if (!modeInherited)
{
// use server default
mode = authMode;
authPlugin = hookName;
modeInherited = true;
}
}
UserAccount user = new UserAccount(users[i], enabled, person, alias, groups,
protect, password, cdate, ctime, mode,
authPlugin, osUser, modeInherited, webServiceToken);
list.add(user);
dList("Loaded user <" + users[i] + ">");
stats++;
}
dStat("Loaded " + stats + " users, " + statf + " objects ignored");
}
/**
* Reads the P2J directory and initializes authentication control data.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*
* @throws ConfigurationException
* If the authentication mode is invalid.
*/
private void readAuthMode(DirectoryService ds)
throws ConfigurationException
{
// reading authorization mode from directory
Integer am = ds.getNodeInteger("/security/config/auth-mode", "mode");
if (am != null)
{
authMode = am.intValue();
if (authMode < AUTH_MODE_LOWEST || authMode > AUTH_MODE_HIGHEST)
throw new ConfigurationException("invalid authentication mode");
}
else
throw new ConfigurationException("no authentication mode");
Integer rm = ds.getNodeInteger("/security/config/auth-mode", "retries");
if (rm != null)
{
authRetries = rm.intValue();
if (authRetries < -1)
throw new ConfigurationException("invalid retries value");
}
dList("Loaded auth-mode " + authMode + ", retries " + authRetries);
// reading default authorization hook class from directory
hookName = ds.getNodeString("/security/config/auth-mode", "plugin");
dList("Loaded auth hook as <" + hookName + ">");
instantiateSsoAuthenticator(ds);
readAuthPlugins(ds);
// reading anonymous account ID
anonymId = ds.getNodeString("/security/config/auth-mode", "anonymous");
if (anonymId != null && anonymId.length() == 0)
anonymId = null;
dList("Loaded anonymous ID <" + anonymId + ">");
dStat("Loaded auth-mode object");
}
/**
* Instantiates the SSO authentication provider.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*/
private void instantiateSsoAuthenticator(DirectoryService ds)
{
// reading default SSO authorization hook class from directory
String ssoHookName = ds.getNodeString("/security/config/auth-mode", "ssoplugin");
String ssoHookOption = ds.getNodeString("/security/config/auth-mode", "ssopluginoption");
dList("Loading sso auth hook as <" + ssoHookName + ">");
if (!StringHelper.hasContent(ssoHookName))
{
return;
}
try
{
Class<?> ssoAuthClass = Class.forName(ssoHookName);
ssoHook = (SsoAuthenticator) ssoAuthClass.getDeclaredConstructor().newInstance();
ssoHook.setOption(ssoHookOption);
}
catch (Exception exc)
{
LOG.warning(
"Can't find or can't instantiate the user-provided implementation for SsoAuthenticator.",
exc);
}
}
/**
* Read the list of available auth plugins form the directory.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*/
private void readAuthPlugins(DirectoryService ds)
{
authPlugins = new LinkedHashMap<String, AuthPlugin>();
// enumerate all plugins
String basePath = "/security/config/auth-plugins";
String[] plugins = ds.enumerateNodes(basePath);
if (plugins != null)
{
basePath = new StringBuilder(basePath).append("/%s").toString();
for (String pluginId : plugins)
{
String pluginNodeId = String.format(basePath, pluginId);
// get plugin class
StringBuilder sb = new StringBuilder(pluginNodeId);
sb.append("/classname");
String pluginClass = ds.getNodeString(sb.toString(), "value");
// get description
sb = new StringBuilder(pluginNodeId);
sb.append("/description");
String description = ds.getNodeString(sb.toString(), "value");
// get option
sb = new StringBuilder(pluginNodeId);
sb.append("/option");
String option = ds.getNodeString(sb.toString(), "value");
AuthPlugin authPlugin = new AuthPlugin(pluginClass,
description,
option);
authPlugins.put(pluginClass, authPlugin);
}
}
}
/**
* Reads the P2J directory and initializes the list of known holidays.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*/
private void readHolidays(DirectoryService ds)
throws ConfigurationException
{
int stats = 0;
int statf = 0;
DateValue[] hdays = ds.getNodeDates("/security/holidays", "values");
if (hdays == null)
dList("No holidays defined in the P2J directory.");
else
{
DateValue today = new DateValue();
for (int i = 0; i < hdays.length; i++)
{
if (hdays[i].compareTo(today) >= 0)
{
holidays.add(hdays[i].toString());
dList("Loaded holiday " + hdays[i].toString());
stats++;
}
else
{
dList("Ignored holiday " + hdays[i].toString());
statf++;
}
}
dStat("Loaded " + stats + " holidays, " + statf + " ignored");
}
}
/**
* Reads the maximum limits for simultaneous user sessions.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*
* @throws ConfigurationException
* If maxAge parameter is out of bounds.
*/
private void readInteractiveUserLimits(DirectoryService ds)
throws ConfigurationException
{
Integer msiu = ds.getNodeInteger("/security/config/maxusers", "value");
if (msiu != null)
{
if (msiu < -1)
{
throw new ConfigurationException("Invalid maximum simultaneous interactive users (maxusers) " +
msiu);
}
maxInteractiveUsers = msiu;
dList("Loaded maximum simultaneous interactive users (maxusers) " + msiu);
}
else
{
maxInteractiveUsers = -1;
}
Integer mus = ds.getNodeInteger("/security/config/maxusersessions", "value");
if (mus != null)
{
if (mus < -1)
{
throw new ConfigurationException("Invalid maximum simultaneous sessions per interactive user " +
"(maxusersessions) " + mus);
}
maxSessionsPerUser = mus;
dList("Loaded maximum simultaneous sessions per interactive user (maxusersessions) " + mus);
}
else
{
maxSessionsPerUser = -1;
}
}
/**
* Reads the password aging control parameters.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*
* @throws ConfigurationException
* If maxAge parameter is out of bounds.
*/
private void readPasswordAging(DirectoryService ds)
throws ConfigurationException
{
// reading maximum password age from directory
Integer mpa = ds.getNodeInteger("/security/config/change/maxage", "value");
if (mpa != null)
{
maxAge = mpa.intValue();
if (maxAge < 0)
throw new ConfigurationException("invalid password age");
dList("Loaded maximum password age " + maxAge);
}
else
{
maxAge = 0;
}
// reading authorization hook class from directory
pwInputHook = ds.getNodeString("/security/config/change/plugin",
"value");
if (pwInputHook == null)
{
if (maxAge > 0)
throw new ConfigurationException("password input hook required");
}
if (maxAge > 0 || pwInputHook != null)
dStat("Loaded password aging object");
}
/**
* Reads the P2J directory and loads defined resource plugins.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*
* @param preload
* array of plugin names to bypass reading the directory
*
* @throws NoSuchMethodException
* Thrown by use of <code>ResourceRegistry</code>.
*/
void readPlugins(DirectoryService ds, String[] preload)
throws NoSuchMethodException
{
String[] plugins = null;
if (preload == null)
{
String nodeId = "/security/config/resource-plugins";
// check the node for existence and proper type
if (ds.getNodeClass(nodeId) == null)
{
dWarn("No resource plugins defined in P2J directory");
return;
}
if (!ds.getNodeClass(nodeId).equals("/meta/class/strings"))
{
dWarn("No resource plugins defined in P2J directory");
return;
}
// read all plugin names
plugins = ds.getNodeStrings(nodeId, "values");
if (plugins == null || plugins.length == 0)
{
dWarn("No resource plugins defined in P2J directory");
return;
}
}
else
{
plugins = preload;
}
// register listed plugins
ResourceRegistry reg[] = new ResourceRegistry[plugins.length];
int rejects = 0;
Class pluginClass = null;
AbstractResource pluginInstance = null;
resolver = new Resolver();
Object library = null;
int stats = 0;
int statf = 0;
// first pass
for (int i = 0; i < plugins.length; i++)
{
reg[i] = null;
// create an instance of the class
try
{
pluginClass = Class.forName(plugins[i]);
pluginInstance = (AbstractResource)pluginClass.getDeclaredConstructor().newInstance();
stats++;
}
catch (Exception e)
{
// something's wrong with the class
rejects++;
dErr("Failed to register plugin " + plugins[i] +
": " + e);
statf++;
continue;
}
// attach the instance to the Security Manager
pluginInstance.attach(sm);
// create the registry entry
reg[i] = new ResourceRegistry(i, pluginInstance);
// check for the resource type name duplication
String thisName = reg[i].getTypeName();
for (int j = 0; j < i - 1; j++)
{
if (reg[j] != null && reg[j].getTypeName().equals(thisName))
{
dErr("Plugin " + plugins[i] + " exports resource type name "
+ thisName + " already claimed by " + plugins[j]);
reg[i] = null;
rejects++;
statf++;
stats--;
break;
}
}
if (reg[i] == null)
continue;
// register exported variables and functions
library = pluginInstance.getLibrary();
if (library != null)
{
resolver.registerLibrary(pluginInstance, thisName, library);
}
}
// second pass - compaction of the array
if (plugins.length == rejects)
{
dWarn("No valid resource plugins defined in P2J directory");
return;
}
registry = new ResourceRegistry[plugins.length - rejects];
int j = 0;
for (int i = 0; i < plugins.length; i++)
{
if (reg[i] != null)
{
registry[j] = reg[i];
pluginInstance = registry[j].getPlugin();
pluginInstance.registeredAs(j);
dList("Loaded resource plugin for <" + reg[i].getTypeName() +
"> as " + j);
j++;
}
}
dStat("Loaded " + stats + " resource plugins, " + statf + " failed");
}
/**
* Reads the P2J directory and returns the custom extension plugin name.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*
* @param nodeId
* where to look for the extension
*
* @return extension name or <code>null</code>
*/
private String readCustomExtension(DirectoryService ds, String nodeId)
{
// check the node for existence and proper type
if (ds.getNodeClass(nodeId) == null)
{
return null;
}
if (!ds.getNodeClass(nodeId).equals("/meta/class/string"))
{
return null;
}
// read the extension name
String ext = ds.getNodeString(nodeId, "value");
if (ext == null)
{
return null;
}
ext = ext.trim();
if (ext.length() == 0)
{
return null;
}
return ext;
}
/**
* Reads the P2J directory and loads the defined ACLs.
* <p>
* ACLs may be defined under the shared branch of the directory, namely,
* <code>/security/acl</code> and/or under the server's private branch
* <code>/security/acls/<i>server-name </i></code>. If both branches
* contain definitions, they will be logically combined.
* <p>The basis for this combination is the ACL sequence numbers, which are
* node names. All definitions are read from both the branches and then
* processed in the ascending order of their sequence numbers.
* If a sequence number is used in both shared and private branches, the
* ACL definition from the private branch is used. This way the private
* branch can simply add definitions to the combinated set or replace some
* shared definitions.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*/
private void readACLs(DirectoryService ds)
throws ConfigurationException
{
String fullId = null;
int ord = -1;
// first, enumerate shared ACL groups by abstract resource name
String pathShared = "/security/acl";
String[] resTypesShared = ds.enumerateNodes(pathShared);
// second, enumerate private ACL groups, if any
String pathPriv = new String("/security/acls/" + serverId);
String[] resTypesPriv = ds.enumerateNodes(pathPriv);
// combine both arrays
String resTypes[] = null;
int numShared = resTypesShared == null ? 0 : resTypesShared.length;
int numPriv = resTypesPriv == null ? 0 : resTypesPriv.length;
// create a union of two sets of names - supported resource types
Set s = new HashSet();
Set ssh = new HashSet();
Set spr = new HashSet();
int i = 0;
for (i = 0; i < numShared; i++)
{
s.add(resTypesShared[i]);
ssh.add(resTypesShared[i]);
}
for (i = 0; i < numPriv; i++)
{
s.add(resTypesPriv[i]);
spr.add(resTypesPriv[i]);
}
int numAcls = s.size();
if (numAcls == 0)
dWarn("No ACL groups defined in the P2J directory");
else
{
resTypes = new String[numAcls];
Iterator iter = s.iterator();
i = 0;
while (iter.hasNext())
{
resTypes[i] = (String)iter.next();
i++;
}
}
// process ACLs by resource type
for (i = 0; i < numAcls; i++)
{
// ACLs being built are collected in this list
List acls = new LinkedList();
// check if this resource type is registered
ord = getRegistryOrdByTypeName(resTypes[i]);
if (ord == -1)
{
dWarn("ACLs branch <" + resTypes[i] + "> is ignored");
continue;
}
dList("ACLs for resource type <" + resTypes[i] + ">:");
SortedMap rmap = new TreeMap();
// enumerate all rights object for this resource type - shared
if (ssh.contains(resTypes[i]))
{
addAclNodes(ds, pathShared + "/" + resTypes[i], rmap, ord);
}
// enumerate all rights object for this resource type - private
if (spr.contains(resTypes[i]))
{
addAclNodes(ds, pathPriv + "/" + resTypes[i], rmap, ord);
}
// processing ordered sequence of ACLs
Set rset = rmap.keySet();
Iterator iter = rset.iterator();
Rights rights = null;
while (iter.hasNext())
{
String path = (String)rmap.get(iter.next());
// instantiate the Rights object
rights = createRights(ds, ord, path + "/rights");
if (rights == null)
{
throw new ConfigurationException("cannot create Rights for " +
path);
}
// read this ACL container and create the ACL
createACL(ds, path, ord, rights, acls);
}
printACLs(acls);
AccessControlList[] arrayACL = new AccessControlList[acls.size()];
registry[ord].setACLs((AccessControlList[])acls.toArray(arrayACL));
dStat("Loaded " + acls.size() + " ACLs for resource type <" +
resTypes[i] + ">");
}
}
/**
* Enumerates a directory branch and gathers all ACLs from there.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*
* @param fullId
* directory branch
*
* @param map
* storage for ACL paths
*
* @param ord
* resource plugin ordinal number
*/
private void addAclNodes(DirectoryService ds, String fullId, Map map,
int ord)
throws ConfigurationException
{
String[] seqno = ds.enumerateNodes(fullId);
// check if ACL conversion is due
if (seqno.length > 0)
{
boolean migrated = migrateACLs(ds, fullId, seqno, ord);
if (migrated)
{
seqno = ds.enumerateNodes(fullId);
}
}
// sorting ACLs in the ascending order of seqno
for (int j = 0; j < seqno.length; j++)
{
String rpath = fullId + "/" + seqno[j];
int k = 0;
try
{
k = Integer.parseInt(seqno[j]);
}
catch (NumberFormatException e)
{
dWarn("ACLs branch " + rpath + " is ignored");
continue;
}
map.put(Integer.valueOf(k), rpath);
}
}
/**
* Checks migration status of the given ACL branch and migrates it fron
* the old tree to the new flat representation when necessary.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*
* @param path
* directory branch
*
* @param nodes
* array of existing nodes in the branch
*
* @param ord
* resource plugin ordinal number
*/
boolean migrateACLs(DirectoryService ds, String path, String[] nodes,
int ord)
throws ConfigurationException
{
// ACLs being built are collected in this list
List acls = new LinkedList();
SortedMap rmap = new TreeMap();
// sorting child nodes in the ascending order of seqno
// they are either rights (old style) or containers (new style)
for (int j = 0; j < nodes.length; j++)
{
String rpath = path + "/" + nodes[j];
int k = 0;
if (ds.getNodeClass(rpath).equals("/meta/class/container"))
{
// found a container; assume new style and ignore everything else
return false;
}
try
{
k = Integer.parseInt(nodes[j]);
}
catch (NumberFormatException e)
{
dWarn("ACLs branch " + rpath + " is ignored");
continue;
}
rmap.put(Integer.valueOf(k), rpath);
}
// conversion is due
if (!serverAcc.isMaster())
{
throw new ConfigurationException(
"ACL conversion requires running as master server");
}
dWarn("*** converting ACLs for " + path + " ***");
// processing ordered sequence of rights
Set rset = rmap.keySet();
Iterator iter = rset.iterator();
Rights rights = null;
int step = 100;
int seqn = step;
boolean rc = false;
while (iter.hasNext())
{
String rpath = (String)rmap.get(iter.next());
dWarn("Converting ACLs branch " + rpath);
acls.clear();
// instantiate this Rights object
rights = createRights(ds, ord, rpath);
if (rights == null)
throw new ConfigurationException("cannot create Rights for " +
rpath);
// enumerate all bindings for this Rights
String[] bseqno = ds.enumerateNodes(rpath);
// sorting bindings in the ascending order of seqno
SortedMap bmap = new TreeMap();
for (int j = 0; j < bseqno.length; j++)
{
String bpath = rpath + "/" + bseqno[j];
int l = 0;
try
{
l = Integer.parseInt(bseqno[j]);
}
catch (NumberFormatException e)
{
dWarn("ACLs branch " + bpath + " is ignored");
continue;
}
bmap.put(Integer.valueOf(l), bseqno[j]);
}
// processing ordered sequence of bindings
Set bset = bmap.keySet();
Iterator biter = bset.iterator();
String bisn = null;
while (biter.hasNext())
{
bisn = (String)bmap.get(biter.next());
String bpath = rpath + "/" + bisn;
// read this binding directory object and create as many
// separate ACLs as there are defined instances
// ACLs are put into acls list.
createOldACLs(ds, rpath, rights, bpath, acls);
}
// now read the list of ACLs and create new style ACL containers
// open editing batch for the branch
rc = ds.openBatch(path);
if (!rc)
{
dErr("Can't lock directory branch '" + path +
"'. Migration aborted.");
return false;
};
// creating new ACL containers
Iterator ai = acls.iterator();
newAcl:
while (ai.hasNext())
{
AccessControlList acl = (AccessControlList)ai.next();
// prepare resource instance name
String instanceName = acl.getInstanceName();
// prepare resource isntance name mode
boolean instanceNameMode = acl.isExact();
// prepare the reference to the original rights node
String rightsRef = acl.getOrigin();
// get all bindings for this resource instance
List binds = acl.getBindings();
// treat each binding separately
Iterator bi = binds.iterator();
while (bi.hasNext())
{
Binding bind = (Binding)bi.next();
// get the array of subjects
int[] nsubs = bind.getSubjects();
// prepare the subjects as names
String[] subjects = new String[nsubs.length];
for (int i = 0; i < nsubs.length; i++)
{
if (nsubs[i] == -2)
{
subjects[i] = "all_others";
}
else
{
subjects[i] = getAccountByOrd(nsubs[i]).getSubjectId();
}
}
// create the container
String seq = String.format("%06d", seqn);
seqn += step;
String cid = path + '/' + seq;
rc = ds.addNode(cid, "container", null);
if (!rc)
{
dErr("can't add container " + cid);
break newAcl;
}
// copy the rights node
rc = ds.copyNode(rightsRef, cid + "/rights", false);
if (!rc)
{
dErr("can't copy rights " + rightsRef + " to " + cid);
break newAcl;
}
// create resource instance node
Attribute[] rdata = new Attribute[]
{
// resource instance name
new Attribute(ds.getClassNodeAttribute("resource",
"reference"),
new Object[] {instanceName}),
// resource instance mode
new Attribute(ds.getClassNodeAttribute("resource",
"reftype"),
new Object[] {Boolean.valueOf(instanceNameMode)}),
};
rc = ds.addNode(cid + "/resource-instance", "resource", rdata);
if (!rc)
{
dErr("can't add resource-instance to " + cid);
break newAcl;
}
// create subjects node
Attribute[] sdata = new Attribute[]
{
// subjects
new Attribute(ds.getClassNodeAttribute("strings", "values"),
subjects),
};
rc = ds.addNode(cid + "/subjects", "strings", sdata);
if (!rc)
{
dErr("can't add subjects to " + cid);
break newAcl;
}
}
}
// remove the old style ACL node
if (rc)
{
rc = Utils.removeDirectoryNode(ds, rpath);
if (!rc)
{
dErr("can't remove the old style ACLs " + rpath);
}
}
// close batch with commit or rollback and refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
dErr("Error unlocking directory branch '" + path + "'.");
}
else
{
dList(String.format("*** converted %d ACLs ***",
(seqn - step)/step));
}
}
// indicate the migration was performed
return rc;
}
/**
* Reads the P2J directory and loads audit related definitions.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
*/
private void readAudit(DirectoryService ds)
throws ConfigurationException
{
// the very first check is to see if auditing is enabled
Boolean enabled = ds.getNodeBoolean("/security/audit/enabled", "value");
if (enabled == null || !enabled.booleanValue())
{
// instantiate disabled audit object
dWarn("Security audit log is disabled");
audit = new Audit();
return;
}
// the second check is to see if the audit log file is defined
String logFile = ds.getNodeString("/security/audit/logfile", "value");
if (logFile == null)
throw new ConfigurationException(
"audit log file not defined while auditing is enabled");
// read various audit definitions
Integer logSizeC = ds.getNodeInteger("/security/audit/logsize",
"value");
int logSize = logSizeC == null ? 8096 : logSizeC.intValue();
if (logSize < 0)
throw new ConfigurationException(
"audit log size is invalid: " + logSize);
Integer logCountC = ds.getNodeInteger("/security/audit/logcount",
"value");
int logCount = logCountC == null ? 3 : logCountC.intValue();
if (logCount < 1)
throw new ConfigurationException(
"audit log count is invalid: " + logCount);
dList("Security audit log is <" + logFile + ">, " + logCount + ", " +
logSize + "K");
int statd = 0;
int stats = 0;
int statr = 0;
Boolean filterModeC = ds.getNodeBoolean("/security/audit/filterMode",
"value");
boolean filterMode = filterModeC == null ?
false : filterModeC.booleanValue();
Boolean decSuccessC = ds.getNodeBoolean("/security/audit/decisions",
"success");
boolean decSuccess = decSuccessC == null ?
false : decSuccessC.booleanValue();
Boolean decFailureC = ds.getNodeBoolean("/security/audit/decisions",
"failure");
boolean decFailure = decFailureC == null ?
false : decFailureC.booleanValue();
if (decSuccess)
statd++;
if (decFailure)
statd++;
dList("Audit filtering mode is " + (filterMode ? "AND" : "OR"));
dList("Audit decisions: success: " + (decSuccess ? "YES " : "NO ") +
"failure: " + (decFailure ? "YES " : "NO "));
// read subject IDs and convert them into numeric form
String[] auditSubjs = ds.getNodeStrings("/security/audit/subjects",
"values");
int numSubjs = auditSubjs == null ? 0 : auditSubjs.length;
int ord = 0;
int[] auditIds = null;
if (numSubjs > 0)
auditIds = new int[numSubjs];
StringBuilder sb = new StringBuilder();
for (int i = 0; i < numSubjs; i++)
{
ord = getOrdinalById(auditSubjs[i]);
if (ord == -1)
dWarn("Audit definition refers to unknown account "
+ auditSubjs[i]);
else
sb.append("<" + auditSubjs[i] + ">, ");
auditIds[i] = ord;
}
if (numSubjs > 0)
{
dList("Audit subjects " + new String(sb));
stats = numSubjs;
}
// enumerate auditable resources
String path = "/security/audit/resources";
String[] res = ds.enumerateNodes(path);
int rsize = res == null ? 0 : res.length;
// process auditable resources
String fullId = null;
String resType = null;
String[] instances = null;
Integer[] reqRights = null;
AuditTarget target = null;
for (int i = 0; i < rsize; i++)
{
fullId = new String(path + "/" + res[i]);
if (!ds.getNodeClass(fullId).equals("/meta/class/auditResource"))
continue;
// read resource type and identify the plugin
resType = ds.getNodeString(fullId, "type");
if (resType == null)
{
dWarn("cannot read resource type for " + fullId);
continue;
}
ord = getRegistryOrdByTypeName(resType);
if (ord == -1)
{
dWarn("cannot identify resource plugin for " + fullId + ", " +
resType);
continue;
}
ResourceRegistry rr = getRegistryByOrd(ord);
AbstractResource ar = rr.getPlugin();
target = rr.getTarget();
// read instances and requested rights
instances = ds.getNodeStrings(fullId, "instances");
reqRights = ds.getNodeIntegers(fullId, "requests");
// create a registration list
String[] reg = null;
if (instances == null)
{
reg = new String[1];
reg[0] = null;
}
else
{
reg = instances;
statr += instances.length;
}
sb.setLength(0);
// register audit target
for (int j = 0; j < reg.length; j++)
{
if (reg[j] != null)
if (!ar.isInstanceNameValid(reg[j]))
{
dWarn("invalid resource instance name " + reg[j]);
continue;
}
target.addTarget(reg[j], reqRights);
sb.append("<" + reg[j] + ">, ");
}
sb.append("access modes ");
if (reqRights == null)
sb.append("all");
else
for (int j = 0; j < reqRights.length; j++)
sb.append(reqRights[j] + ", ");
dList("Audit resource <" + resType + "> instances " + sb);
}
// instantiate and set up the audit object
try
{
audit = new Audit(this, logFile, logSize, logCount);
}
catch (IOException ioe)
{
throw new ConfigurationException("audit log file cannot be created");
}
audit.setFilterMode(filterMode);
audit.setAuditSuccess(decSuccess);
audit.setAuditFailure(decFailure);
audit.setAuditId(auditIds);
dStat("Audit decisions: " + statd + ", subjects: " + stats +
", resources " + statr);
}
/**
* Verifies that the server ID is valid.
* <p>
* Server ID is valid if:
* <ul>
* <li>it has a matching process account;
* <li>that account is for a server;
* <li>that account's alias points to a matching trust store certificate
* </ul>
*
* @param secure
* Specifies whether secure environment is enabled.
*
* @throws ConfigurationException
* If the server ID is invalid.
*/
private void verifyServerId(boolean secure)
throws ConfigurationException
{
Account acc = getAccountById(serverId);
if (acc == null)
throw new ConfigurationException("server ID <" + serverId +
"> has no account");
if (acc.getAccountType() != Account.ACC_PROCESS)
throw new ConfigurationException("wrong account type for server ID <"
+ serverId + ">");
serverAcc = (ProcessAccount)acc;
if (!serverAcc.isServer())
throw new ConfigurationException("server ID <" + serverId +
"> is for an application");
serverAlias = serverAcc.getAlias();
if (serverAlias == null)
throw new ConfigurationException(
"server ID <" + serverId + "> has no certificate alias");
if (secure)
{
try
{
java.security.cert.Certificate cert = trustStore.getCertificate(
serverAlias);
if (cert == null)
throw new ConfigurationException("server's alias <"
+ serverAlias + "> has no certificate");
}
catch (KeyStoreException e)
{
throw new ConfigurationException("server's alias <" + serverAlias +
"> has no certificate");
}
}
}
/**
* Verifies that the anonymous process account ID is valid.
* <p>
* Anonymous process account ID is valid if:
* <ul>
* <li>it has a matching process account;
* <li>that account is for an application;
* <li>that account has no truststore alias;
* </ul>
*
* @throws ConfigurationException
* If the anonymous ID is invalid.
*/
private void verifyAnonymousId()
throws ConfigurationException
{
if (anonymId == null)
return;
Account acc = getAccountById(anonymId);
if (acc == null)
throw new ConfigurationException("anonymous ID <" + anonymId +
"> has no account");
if (acc.getAccountType() != Account.ACC_PROCESS)
throw new ConfigurationException(
"wrong account type for anonymous ID <" + anonymId + ">");
ProcessAccount proc = (ProcessAccount)acc;
if (proc.isServer())
throw new ConfigurationException("anonymous ID <" + anonymId +
"> is for a server");
if (proc.getAlias() != null)
throw new ConfigurationException(
"anonymous ID <" + anonymId + "> has a certificate alias");
}
/**
* Creates an instance of Rights based on information in the P2J directory.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
* @param ord
* Ordinal number of the resource type.
* @param path
* P2J directory path to the instance of <code>Rights</code>.
*
* @return New instance of <code>Rights</code> or <code>null</code>.
*/
Rights createRights(DirectoryService ds, int ord, String path)
{
// locate the plugin responsible for this resource type
AbstractResource plugin = getRegistryByOrd(ord).getPlugin();
if (plugin == null)
return null;
// read all attributes of this instance of Rights
Attribute[] attr = ds.getNodeAttributes(path);
if (attr == null)
return null;
// get fields description
Description[] descr = getRegistryByOrd(ord).getDescription();
if (descr == null)
return null;
// instantiate field objects
Object[] fields = new Object[descr.length];
String label = null;
for (int i = 0; i < descr.length; i++)
{
label = descr[i].getLabel();
int j = 0;
// find a matching attribute
for (j = 0; j < attr.length; j++)
{
if (attr[j].getName().equals(label))
break;
}
if (j < attr.length)
fields[i] = attr[j].getValue(0);
else
{
if (!descr[i].isOptional())
return null;
else
fields[i] = null;
}
}
// create Rights instance from fields
if (!plugin.isRightsSetValid(fields))
{
return null;
}
return plugin.getRightsInstance(fields);
}
/**
* Reads the P2J directory and loads ACLs from the specified binding
* directory object into a list.
* <p>
* A binding object is made of:
* <ul>
* <li>a list of one or more subject IDs and, possibly, all_others
* keyword;
* <li>a list of resource instance names;
* <li>a list of resource instance reference types: <code>true</code>
* meaning exact name and <code>false</code> meaning regexp match
* </ul>
* The number of values for instance names and for reference types must be
* the same. That does not depend on the number of subject IDs.
* <p>
* The result of this method is a number of <code>AccessControlList</code>
* entries added to the list "list", one entry per instance name. These
* <code>AccessControlList</code>s will have exactly one
* <code>Binding</code> object put into the embedded list of bindings:
* {parameter rights, list of subjects from oid}.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
* @param roid
* Rights directory object ID
* @param rights
* Object to be kept in triplets.
* @param boid
* Binding directory object ID to read.
* @param list
* Place to put the instances of ACLs.
*/
private void createOldACLs(DirectoryService ds, String roid, Rights rights,
String boid, List list)
throws ConfigurationException
{
// read the set of subjects from this parent object (strings)
String[] subjects = ds.getNodeStrings(boid, "values");
if (subjects == null)
throw new ConfigurationException("error 1 reading directory object "
+ boid);
// enumerate all bindings for this parent object
String[] bseqno = ds.enumerateNodes(boid);
if (bseqno == null)
throw new ConfigurationException("error 2 reading directory object "
+ boid);
// sorting bindings in the ascending order of seqno
SortedMap bmap = new TreeMap();
for (int j = 0; j < bseqno.length; j++)
{
String bpath = boid + "/" + bseqno[j];
int l = 0;
try
{
l = Integer.parseInt(bseqno[j]);
}
catch (NumberFormatException e)
{
dWarn("ACLs branch " + bpath + " is ignored");
continue;
}
bmap.put(Integer.valueOf(l), bseqno[j]);
}
// processing ordered sequence of bindings
Set bset = bmap.keySet();
if (bset.size() == 0)
throw new ConfigurationException("error 3 reading directory object "
+ boid);
Iterator biter = bset.iterator();
String bisn = null;
String[] references = new String[bset.size()];
Boolean[] reftypes = new Boolean[bset.size()];
String[] refpaths = new String[bset.size()];
int i = 0;
while (biter.hasNext())
{
bisn = (String)bmap.get(biter.next());
String bpath = boid + "/" + bisn;
refpaths[i] = bpath;
// read this binding directory object
references[i] = ds.getNodeString(bpath, "reference");
if (references[i] == null)
throw new ConfigurationException(
"error 4 reading directory object " + bpath);
reftypes[i] = ds.getNodeBoolean(bpath, "reftype");
if (reftypes[i] == null)
throw new ConfigurationException(
"error 5 reading directory object " + bpath);
i++;
}
// resolve subject IDs into ordinals
int[] ordinals = new int[subjects.length];
for (i = 0; i < subjects.length; i++)
{
ordinals[i] = getOrdinalById(subjects[i]);
if (ordinals[i] == -1)
{
if (subjects[i].compareToIgnoreCase("all_others") == 0)
ordinals[i] = -2;
else
throw new ConfigurationException("directory object " + boid +
" refers to non-existing account " + subjects[i]);
}
}
// can't share a single binding list between all resource instances
// have to create an instance for every AccessControlList
// but can share the single binding
Binding bind = new Binding(rights, ordinals);
List bindList = null;
// create ACLs, one per resource instance
AccessControlList acl = null;
for (i = 0; i < references.length; i++)
{
bindList = new LinkedList();
bindList.add(bind);
// use roid (rights object id) as a reference to the original node
acl = new AccessControlList(roid,
references[i],
reftypes[i].booleanValue(),
bindList);
list.add(acl);
}
}
/**
* Reads the P2J directory and loads ACLs from the specified
* directory object into a list.
* <p>
* <p>
* The result of this method is a number of <code>AccessControlList</code>
* entries added to the list "list", one entry per instance name. These
* <code>AccessControlList</code>s will have exactly one
* <code>Binding</code> object put into the embedded list of bindings:
* {parameter rights, list of subjects from oid}.
*
* @param ds
* Instance of <code>DirectoryService</code> from which to read.
* @param path
* directory object ID of the container of this ACL
* @param rights
* Object to be kept in triplets.
* @param list
* Place to put the instances of ACLs.
*/
private void createACL(DirectoryService ds, String path, int ord,
Rights rights, List list)
throws ConfigurationException
{
// locate the plugin responsible for this resource type
AbstractResource plugin = getRegistryByOrd(ord).getPlugin();
// read the set of subjects from this ACL container (strings)
String[] subjects = ds.getNodeStrings(path + "/subjects", "values");
if (subjects == null)
{
throw new ConfigurationException("no subjects list in ACL " + path);
}
// resolve subject IDs into ordinals
int[] ordinals = new int[subjects.length];
for (int i = 0; i < subjects.length; i++)
{
ordinals[i] = getOrdinalById(subjects[i]);
if (ordinals[i] == -1)
{
if (subjects[i].compareToIgnoreCase("all_others") == 0)
ordinals[i] = -2;
else
throw new ConfigurationException(
"directory object " + path + "/subjects" +
" refers to non-existing account " + subjects[i]);
}
}
// read the resource instance information
String bpath = path + "/resource-instance";
String reference = ds.getNodeString(bpath, "reference");
if (reference == null)
{
throw new ConfigurationException("no resource name in ACL " + path);
}
Boolean reftype = ds.getNodeBoolean(bpath, "reftype");
if (reftype == null)
{
throw new ConfigurationException("no match type in ACL " + path);
}
// for exact match instances, verify the instance name
if (reftype.booleanValue())
{
if (!plugin.isInstanceNameValid(reference))
{
throw new ConfigurationException("directory object " + bpath +
" refers to invalid resource instance name <" +
reference + ">");
}
}
// create the ACL
Binding bind = new Binding(rights, ordinals);
List bindList = new LinkedList();
bindList.add(bind);
AccessControlList acl = new AccessControlList(path,
reference,
reftype.booleanValue(),
bindList);
list.add(acl);
}
/**
* Prints all ACLs from a list.
*
* @param list
* List of ACLs.
*/
private void printACLs(List list)
{
AccessControlList acl = null;
int[] ids = null;
List bindList = null;
String reforig = null;
Binding bind = null;
Iterator iter = list.iterator();
StringBuilder sb = new StringBuilder();
while (iter.hasNext())
{
acl = (AccessControlList)iter.next();
reforig = acl.getOrigin();
dTrace(" origin " + reforig);
sb.setLength(0);
sb.append(" instance name <" + acl.getInstanceName() + "> ");
if (acl.isExact())
sb.append("(exact)");
else
sb.append("(regexp)");
dList(sb.toString());
bindList = acl.getBindings();
Iterator biter = bindList.iterator();
while (biter.hasNext())
{
bind = (Binding)biter.next();
dList(" rights " + bind.getRights());
ids = bind.getSubjects();
sb.setLength(0);
sb.append(" subjects ");
for (int i = 0; i < ids.length; i++)
{
if (ids[i] == -2)
sb.append("*all_others* ");
else
if (ids[i] == -1)
sb.append("*undefined* ");
else
sb.append(getAccountByOrd(ids[i]).getSubjectId() + " ");
}
dList(sb.toString());
}
}
}
/**
* Logs error messages.
*
* @param message
* text to be displayed
*/
public void dErr(String message)
{
if (logMode == LM_ADMIN)
{
AdminServerImpl.message(new String[] {message});
}
if (logMode == LM_DUAL)
{
msgBuf.add(new String[] {message});
}
if (logMode == LM_CONSOLE || logMode == LM_DUAL)
{
LOG.severe(message);
}
}
/**
* Logs warnings.
*
* @param message
* text to be displayed
*/
public void dWarn(String message)
{
if (logMode == LM_ADMIN)
{
AdminServerImpl.message(new String[] {message});
}
if (logMode == LM_DUAL)
{
msgBuf.add(new String[] {message});
}
if (logMode == LM_CONSOLE || logMode == LM_DUAL)
{
LOG.warning(message);
}
}
/**
* Logs statistics.
*
* @param message
* text to be displayed
*/
public void dStat(String message)
{
if (logMode == LM_ADMIN)
{
AdminServerImpl.message(new String[] {message});
}
if (logMode == LM_DUAL)
{
msgBuf.add(new String[] {message});
}
if (logMode == LM_CONSOLE || logMode == LM_DUAL)
{
LOG.info(message);
}
}
/**
* Logs detailed lists.
*
* @param message
* text to be displayed
*/
public void dList(String message)
{
if (logMode == LM_ADMIN)
{
AdminServerImpl.message(new String[] {message});
}
if (logMode == LM_DUAL)
{
msgBuf.add(new String[] {message});
}
if (logMode == LM_CONSOLE || logMode == LM_DUAL)
{
LOG.fine(message);
}
}
/**
* Logs detailed traces.
*
* @param message
* text to be displayed
*/
public void dTrace(String message)
{
if (logMode == LM_ADMIN)
{
AdminServerImpl.message(new String[] {message});
}
if (logMode == LM_DUAL)
{
msgBuf.add(new String[] {message});
}
if (logMode == LM_CONSOLE || logMode == LM_DUAL)
{
LOG.finer(message);
}
}
/**
* Logs detailed data, possibly including sensitive information.
*
* @param message
* text to be displayed
*/
public void dData(String message)
{
if (logMode == LM_ADMIN)
{
AdminServerImpl.message(new String[] {message});
}
if (logMode == LM_DUAL)
{
msgBuf.add(new String[] {message});
}
if (logMode == LM_CONSOLE || logMode == LM_DUAL)
{
LOG.finest(message);
}
}
/**
* Find and update user account stored in cache.
*
* @param user
* User account parameters.
*
* @return <code>true</code> if account is found and updated.
* <code>false</code> if account not found.
*/
public boolean updateCachedUserAccount(UserDef user)
{
boolean result = false;
// iterate over cached accounts.
for (Account acc : accs)
{
// search for account.
if (acc instanceof UserAccount && user.subjectId.equals(acc.getSubjectId()))
{
// if found update acccount.
acc.setEnabled(user.enabled);
((UserAccount) acc).setPassword(user.password, new DateValue(user.dateTime),
new TimeValue(user.dateTime));
result = true;
}
}
return result;
}
}