SecurityAdmin.java
/*
** Module : SecurityAdmin.java
** Abstract : Collection of security administration workers and session state
**
** Copyright (c) 2009-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- --------------------------------Description-------------------------------------
** 001 NVS 20090325 @41682 Created the initial version since security
** workers are not visible to the admin package
** 002 NVS 20090429 @42083 Added isMaster() method.
** Implemented listProcesses(), getProcess(),
** setProcess(), addProcess(), deleteProcess(),
** 003 NVS 20090505 @42096 Implemented certificate related methods.
** 004 NVS 20090511 @42139 Implemented user account related methods.
** 005 NVS 20090518 @42243 Implemented moveCert() method.
** 006 NVS 20090519 @42334 Implemented searchCert() method. Improved
** checkCertificate() worker to block the wrong
** domain requests (adding peer cert to ca and
** ca cert to peer domains).
** 007 NVS 20090520 @42418 Added listUserGroups() API implementation.
** 008 NVS 20090525 @42451 Added temporary code to support custom account
** extensions. To be reimplemented.
** 009 NVS 20090528 @42479 Implemented interfacing with custom account
** extension plugin permanently.
** 010 CA 20090603 @42556 Avoid possible NPE's in getUser and getProcess.
** 011 CA 20090604 @42558 If adding or saving the extension fails with an
** exception, then rollback and perform a fast exit.
** 012 NVS 20090604 @42605 cloneUser() optionally replaces the password.
** 013 NVS 20090608 @42612 Full support for enabled/disabled account state.
** 014 NVS 20090609 @42618 Automatically create password aging timestamp if
** user account is password protected and the
** password is provided but not the timestamp.
** 015 NVS 20090609 @42622 Added consReloadDirectory() implementation.
** 016 CA 20090615 @42693 Added getAdminDef(). Now also populates a map
** with the description for each resource name.
** 017 GES 20090610 @42761 Added getSessionList().
** 018 GES 20090619 @42783 Added killSession().
** 019 NVS 20090625 @42958 Implemented cloning ACLs and proper removal of
** ACLs on account removals.
** 020 NVS 20090626 @42967 Implemented the first group of ACL related APIs -
** listAclInstances() and the like
** 021 NVS 20090701 @43006 Implemented the ACL getters group of the ACL
** related APIs.
** 022 NVS 20090703 @43045 Implemented the addAcl() method.
** 023 NVS 20090703 @43050 Implemented the setAcl() method.
** 024 NVS 20090706 @43067 Implemented APIs moveAcl(), deleteAcl(),
** cloneAcl() and resequenceAcl().
** 025 NVS 20090707 @43082 Rights editor names are passed to the admin client
** 026 NVS 20090717 @43231 Fixed ALL_OTHERS subject handling in ACLs.
** 027 NVS 20090723 @43367 Added protection against various directory issues.
** 028 NVS 20090814 @43619 Added a command line, offline ACL editing utility
** modifySubjectsInACLs() which can conditionally
** select ACLs for editing by a subject ID and apply
** specified deletions and/or additions of other
** subject IDs.
** 029 NVS 20090824 @43728 Extended the set of command line, offline directory
** utilities with these new utilities:
** - migrate - migrates an ACL section style
** - johndoe - validates accounts referenced by ACLs
** - resource - validates resources referenced by ACLs
** - encode - encodes passwords stored in another
** attribute as a plain text.
** 039 NVS 20090825 @43738 Fixed the recognition of all_others in johndoe
** utility.
** 040 NVS 20090826 @43773 Javadoc fix
** 041 NVS 20090827 @43778 Detailed messages from the security cache creation
** are logged in standalone utility environment.
** 042 NVS 20090910 @43858 Fixed sorting of ACLs in resequenceAcls() method.
** 043 NVS 20090918 @43953 Tells the transaction manager the session runs
** without the client side UI by setting the headless
** token in the session context.
** 044 NVS 20090924 @44024 Protected all account extension plugin calls with
** try/catch throwable to guarantee normal return
** from the admin APIs, thus avoiding deadlocks due
** non released open batched etc.
** 045 NVS 20090928 @44049 Fixed NPE in listGroupUsers() method. Changed
** closeBatch() related error message to a more
** informative one.
** 046 NVS 20090929 @44054 Fixed cloneUser() and cloneProcess() trying to
** clone ACLs even when the batch for the account
** creation failed.
** 047 NVS 20090930 @44058 Added API protection against null parameters to
** multiple workers.
** 048 NVS 20091007 @44108 Implemented granular access rights checks for all
** user account related APIs.
** 049 SIY 20091027 @44261 Replaced DirectoryServices.refresh() with
** DirectoryServices.atomicRefresh().
** 050 NVS 20091109 @44324 Fixed a NPE in getProfile() which was possible if
** the AdminResource plugin was not installed.
** 051 SIY 20091116 @44334 Refactoring of things related to TaggedName.
** Minor cleanups.
** 052 SIY 20091216 @44512 Added support for "refresh" node.
** 053 SIY 20100419 @44824 Implemented automatic adding of function into
** directory if it does not exists.
** 054 SVL 20100729 Update auth mode while updating user account
** definitions.
** 055 SVL 20100916 Added support for group.mode, group.authPlugin
** and user.authPlugin fields.
** 056 SVL 20100920 Added bulkAuthModeUpdate function. Some
** refactoring was performed.
** 057 GES 20101118 Eliminated use of dead bootstrap config value for
** specification of the directory schema filename.
** 058 CA 20111209 Added method to list active sessions which have
** invoked APIs in the given jar or interface.
** 059 OM 20121002 Fallback to SystemResource sr if AdminResource ar
** is null for checking admin access in getAdminDef()
** 060 CS 20121121 Removed client specific references.
** 061 ECF 20150828 Enable Java 8.
** 062 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 062 IAS 20160408 Use synthetic key as context key.
** 063 SBI 20170225 Moved administration profile type, access control list type and
** record lock type to their own modules respectively, removed DataValue
** and TimeValue fields out of UserDef.
** 064 GES 20210917 Minor code cleanup.
** TJD 20220504 Java 11 compatibility minor changes
** 065 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 006 GBB 20230825 OS users methods added.
** SecurityManager context & session methods calls updated.
** 067 GBB 20240215 Handling RestrictedUseException in killSession().
** 068 TJD 20240123 Java 17 compatibility updates
** 069 SBI 20240307 Added missed resource logging for checkResourceType(String).
** 070 RNC 20240109 Added the getSessionById() method.
*/
/*
** 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.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateEncodingException;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import com.goldencode.p2j.admin.server.reports.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.directory.Base64;
import com.goldencode.p2j.admin.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.util.*;
/**
* This is a collection of workers that have to be in the security package
* but used from the admin package. This class also maintains the session
* related security cache which reflects the current state of the system being
* edited in the persistent storage.
*/
public class SecurityAdmin
implements SecurityConstants
{
/** certificate encoding I/O chunk (as in *.PEM files) */
private static final int SPLIT_CHUNK = 64;
/** certificate encoding start mark */
private static final String CERT_BEG = "-----BEGIN CERTIFICATE-----";
/** certificate encoding end mark */
private static final String CERT_END = "-----END CERTIFICATE-----";
/** list of admin resource instance names maing up a profile */
private static final String[] prof =
{
"/console",
"/accounts",
"/access_control",
"/extension",
"/runtime",
"/configuration",
"/target",
};
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(SecurityAdmin.class);
/** instance of security manager */
private SecurityManager sm = null;
/** instance of system resource plugin */
private SystemResource sr = null;
/** instance of admin resource plugin */
private AdminResource ar = null;
/** instance of current offline security cache */
private SecurityCache sc = null;
/** instance of directory service */
private DirectoryService ds = null;
/** instance of account extension plugin */
private AdminAccountExtension ae = null;
/** maps ACL instance name into map of resource types */
private Map<String, Map<String, Map<Integer, String>>> mapInst = new
HashMap<String, Map<String, Map<Integer, String>>>(3, (float)0.9);
/** current map from mapInst */
private Map<String, Map<Integer, String>> mapTypes = null;
/** current map from mapTypes */
private Map<Integer, String> mapId = null;
/**
* Constructor.
*
* @param ds
* instance of DirectoryService to use for directory access
*/
public SecurityAdmin(DirectoryService ds)
{
// store frequently used references
sm = SecurityManager.getInstance();
sr = (SystemResource)sm.getPluginInstance("system");
ar = (AdminResource)sm.getPluginInstance("admin");
this.ds = ds;
ae = getExtension();
// set the context to the headless mode to suppress UI
sm.contextSm.addToken(ContextKey.HEADLESS_KEY, Boolean.TRUE);
// get the offline security cache for this session (serial == -1)
currentCache();
}
/**
* Checks the rights of the caller to perform admin operations.
*
* @return <code>true</code> if the caller has sufficient rights
*/
public boolean adminAccess()
{
// check the access rights
return checkPermission();
}
/**
* Tells whether the server can modify the directory. Only the master
* server can. The others have read-only access to the directory.
*
* @return <code>true</code> if it can
*/
public boolean isMaster()
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get the server account
ProcessAccount srv = sc.getServerAccount();
return srv.isMaster();
}
/**
* Check if there user can perform refresh.
*
* @return <code>true</code> if current user has enough rights to perform
* security cache refresh.
*/
public boolean canRefresh()
{
// check the access rights
if (!checkPermission("/target/refresh",
AdminRights.ADLU_WRITE_ACCESS))
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
return true;
}
/**
* Get the list of all active sessions which have executed API(s)
* associated to this jar.
*
* @param jar
* The name of the target jar file.
* @param iface
* The interface for which we need the sessions;
* if <code>null</code>, search through the entire jar.
*
* @return the list of all active sessions which have executed API(s)
* associated to this jar.
*/
public SessionInfo[] getActiveSessions(String jar, String iface)
{
// check the access rights
if (!checkPermission())
{
return null;
}
return sm.sessionSm.getActiveSessions(jar, iface);
}
/**
* Terminate all active sessions which have invoked the given APIs in the
* given jar. If the jar name is <code>null</code>, terminate all active
* tracked sessions.
* This method guarantees that all session termination listeners have been
* executed.
*
* @param jar The jar name; if <code>null</code>, terminate all active
* tracked sessions.
* @param iface The interface for which we need to terminate the sessions;
* if <code>null</code>, terminate all sessions for the given
* jar.
*
* @return <code>true</code> if operation was successful,
* <code>false</code> if you do not have admin rights.
*/
public boolean terminateSessions(String jar, String iface)
{
// check the access rights
if (!checkPermission())
{
return false;
}
sm.sessionSm.terminateSessions(jar, iface);
return true;
}
/**
* Returns a tagged array of defined group names. Tags are group
* descriptions.
*
* @return All defined group accounts or <code>null</code>
*/
public TaggedName[] listGroups()
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// get the list of accounts
String[] gl = sc.getAllAccountIds(Account.ACC_GROUP);
if (gl == null)
{
return null;
}
// create tagged list
TaggedName[] tg = new TaggedName[gl.length];
for (int i = 0; i < gl.length; i++)
{
tg[i] = new TaggedName(new String(gl[i]),
new String(((GroupAccount)sc.getAccountById(gl[i])).
getDescription()));
}
return tg;
}
/**
* Returns the group definition for the specified group name.
*
* @param name
* Group name.
*
* @return group definition or <code>null</code>
*/
public GroupDef getGroup(String name)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// sanity check
if (name == null)
{
message("Account name can't be omitted.");
return null;
}
// get the group account
Account acc = sc.getAccountById(name);
if (acc == null)
{
// no such account
message("There is no account named '" + name + "'.");
return null;
}
if (acc.getAccountType() != Account.ACC_GROUP)
{
// not a group account
message("Account '" + name + "' is not a group.");
return null;
}
// return the group definition
GroupDef gd = new GroupDef();
GroupAccount ga = (GroupAccount) acc;
gd.subjectId = new String(ga.getSubjectId());
gd.description = new String(ga.getDescription());
gd.mode = ga.getAuthMode();
gd.authPlugin = ga.getAuthPlugin() != null ?
new String(ga.getAuthPlugin()) :
null;
return gd;
}
/**
* Enumerates the users of a named group.
*
* @param name
* group name
*
* @return array of user names, that may be empty, or <code>null</code> if
* there is no such group
*/
@SuppressWarnings("null")
public TaggedName[] listGroupUsers(String name)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// sanity check
if (name == null)
{
message("Account name can't be omitted.");
return null;
}
// get and check the group account's ordinal number
int go = sc.getOrdinalById(name);
if (go == -1)
{
// no such account
message("There is no account named '" + name + "'.");
return null;
}
Account acc = sc.getAccountByOrd(go);
if (acc.getAccountType() != Account.ACC_GROUP)
{
// not a group account
message("Account '" + name + "' is not a group.");
return null;
}
// scan all users and select those ones who belong to the group
String[] users = sc. getAllAccountIds(Account.ACC_USER);
if (users.length == 0)
{
// no defined users
return new TaggedName[] {};
}
List<TaggedName> lu = new LinkedList<TaggedName>();
for (int i = 0; i < users.length; i++)
{
UserAccount ua = (UserAccount)sc.getAccountById(users[i]);
int[] groups = ua.getGroups();
int gsn = groups == null ? 0 : groups.length;
for (int j = 0; j < gsn; j++)
{
if (groups[j] == go)
{
TaggedName tu = new TaggedName(new String(users[i]),
new String(ua.getPerson()));
lu.add(tu);
}
}
}
return lu.toArray(new TaggedName[0]);
}
/**
* Changes the group definition for the named group.
*
* @param group
* group definition
*
* @return <code>true</code> if successfully changed the group
*/
public boolean setGroup(GroupDef group)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get and check the group account's ordinal number
if (group.subjectId == null)
{
message("Account name can't be omitted.");
return false;
}
int go = sc.getOrdinalById(group.subjectId);
if (go == -1)
{
// no such account
message("There is no account named '" + group.subjectId + "'.");
return false;
}
Account acc = sc.getAccountByOrd(go);
if (acc.getAccountType() != Account.ACC_GROUP)
{
// not a group account
message("Account '" + group.subjectId + "' is not a group.");
return false;
}
// validate auth mode
if (group.mode < AUTH_MODE_LOWEST || group.mode > AUTH_MODE_HIGHEST)
{
// invalid data
message("Authentication mode is invalid.");
return false;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/groups");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = new String("/security/accounts/groups/" +
group.subjectId);
// sync the description attribute
rc = syncString(node, "description", group.description);
// sync auth settings
if (rc)
{
rc = syncAuthSettings(node, group.mode, group.authPlugin);
}
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Adds new group account definition.
*
* @param group
* group definition for a new account
*
* @return <code>true</code> if successfully added the group
*/
public boolean addGroup(GroupDef group)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get and check the existing accounts
if (group.subjectId == null)
{
message("Account name can't be omitted.");
return false;
}
int go = sc.getOrdinalById(group.subjectId);
if (go != -1)
{
// account exists
message("Name '" + group.subjectId +
"' duplicates existing account name.");
return false;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/groups");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = new String("/security/accounts/groups/" + group.subjectId);
// create group
rc = createGroupNode(node, group.description, group.mode,
group.authPlugin);
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Deletes a group account definition.
* The account is deleted. All references to this group are deleted from
* the user accounts and from ACLs.
* <p>
* The directory modification is done in multiple separate transactions:
* <ul>
* <li>unassigning users from the group -
* <code>/security/accounts/users</code>
* <li>removing the group from the ACLs -
* <code>/security/acl</code> and <code>/security/acls</code>
* <li>removing the group account -
* <code>/security/accounts/groups</code>
* </ul>
* This approach may leave the directory partially modified.
*
* @param name
* group name for the group being deleted
*
* @return <code>true</code> if successfully deleted the group
*/
public boolean deleteGroup(String name)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get the existing users of the group
TaggedName[] tusers = listGroupUsers(name);
if (tusers == null)
{
// no group etc
return false;
}
String[] users = new String[tusers.length];
for (int i = 0; i < tusers.length; i++)
{
users[i] = tusers[i].getName();
}
// unassign users from the group
if (!changeGroupUsersWorker(name, new String[] {}, users))
{
// errors during unassign
return false;
}
// remove the group from ACLs
if (!removeSubjectFromACLs(name))
{
// errors during ACLs modification
return false;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/groups");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// remove the group node
rc = ds.deleteNode("/security/accounts/groups/" + name);
if (!rc)
{
message("Directory modification failed for " +
"/security/accounts/groups/" + name);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Clones an existing group account into a new one.
* Users of the source group become also the users of the clone.
* ACLs are modified to give the source group's rights to the clone, if
* this option is requested.
*
* @param nameFrom
* group name for the source group
*
* @param nameTo
* group name for the clone
*
* @param acls
* <code>true</code> tells to clone ACLs as well
*
* @return <code>true</code> if successfully cloned the group
*/
public boolean cloneGroup(String nameFrom, String nameTo, boolean acls)
{
// get the source group
GroupDef sg = getGroup(nameFrom);
if (sg == null)
{
// no source
return false;
}
// add the target group
GroupDef tg = new GroupDef();
tg.subjectId = nameTo;
tg.description = sg.description;
tg.mode = sg.mode;
tg.authPlugin = sg.authPlugin;
boolean rc = addGroup(tg);
if (!rc)
{
// problem creating group
return false;
}
// get the existing users of the source group
TaggedName[] tusers = listGroupUsers(nameFrom);
if (tusers == null)
{
// inconsistency found
message("Directory inconsistency found when listing users of group '"
+ nameFrom + "'.");
return false;
}
String[] users = new String[tusers.length];
for (int i = 0; i < tusers.length; i++)
{
users[i] = tusers[i].getName();
}
// assign the users to the target group
rc = changeGroupUsersWorker(nameTo, users, new String[] {});
if (!rc)
{
return false;
}
// clone ACLs
if (acls)
{
rc = cloneSubjectInACLs(nameFrom, nameTo);
if (!rc)
{
return false;
}
}
// recreate offline cache
return currentCache();
}
/**
* Changes the group by adding and/or removing users.
* <p>
* This API requires ADLU_GROUP_ACCESS permission.
*
* @param name
* group name
*
* @param additions
* array of user names to be added to the group; may be empty
*
* @param deletions
* array of user names to be excluded from the group;
* may be empty
*
* @return <code>true</code> if successfully changed the group
*/
public boolean changeGroupUsers(String name, String[] additions,
String[] deletions)
{
// check the access rights
if (!checkPermission("/accounts/users", AdminRights.ADLU_GROUP_ACCESS))
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get and check the group account's ordinal number
int go = sc.getOrdinalById(name);
if (go == -1)
{
// no such account
message("There is no account named '" + name + "'.");
return false;
}
Account acc = sc.getAccountByOrd(go);
if (acc.getAccountType() != Account.ACC_GROUP)
{
// not a group account
message("Account '" + name + "' is not a group.");
return false;
}
// trivial case of nothing to do
if (additions.length == 0 && deletions.length == 0)
{
return true;
}
// do the essential part
boolean rc = changeGroupUsersWorker(name, additions, deletions);
// recreate offline cache
if (rc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Returns an array of defined user account names and their owners.
* <p>
* This API requires ADLU_ENUMERATE_ACCESS permission.
*
* @return All defined user accounts or <code>null</code>
*/
public TaggedName[] listUsers()
{
// check the access rights
if (!checkPermission("/accounts/users",
AdminRights.ADLU_ENUMERATE_ACCESS))
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// get the list of user accounts
String[] usernames = sc.getAllAccountIds(Account.ACC_USER);
if (usernames == null)
{
return null;
}
// create tagged list
TaggedName[] tu = new TaggedName[usernames.length];
for (int i = 0; i < usernames.length; i++)
{
String username = usernames[i];
UserAccount account = (UserAccount) sc.getAccountById(username);
tu[i] = new TaggedName(username,
account.getPerson() == null ? "" : account.getPerson(),
account.getOsUser() == null ? "" : account.getOsUser());
}
return tu;
}
/**
* Returns the configuration information about the administration interface
* itself. This information should be used to get access to the custom
* admin extensions
*
* @return container with admin related configuration information
*/
public AdminDef getAdminDef()
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
AdminDef def = new AdminDef();
// set the custom client extension plugin
def.customClientExt = sm.getCustomClientExt();
// get the rights editor and the description for each resource
def.customAclEditors = new HashSet<>();
def.resourceDescription = new HashMap<String, Description[]>();
int nrRes = sc.getNumResources();
// lookup the plugins description
for (int i = 0; i < nrRes; i++)
{
AbstractResource plugin = sc.getRegistryByOrd(i).getPlugin();
String type = plugin.getTypeName();
def.customAclEditors.add(type);
Description[] descr = plugin.describeRights();
def.resourceDescription.put(type, descr);
}
// create the admin profile for this session
def.profile = new AdminProfile[prof.length];
for (int i = 0; i < prof.length; i++)
{
def.profile[i] = new AdminProfile(prof[i],
ar == null ? sr.checkAdminAccess() : ar.checkAnyAdminAccess(prof[i]));
}
return def;
}
/**
* Returns the admin permissions profile for an arbitrary set of resource
* instance names.
*
* @param name
* array of admin resource instance names to query
*
* @return permissions for the given names or <code>null</code>
*/
public AdminProfile[] getProfile(String[] name)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the parameter
if (name == null)
{
message("Resource instance names can't be omitted");
return null;
}
// create the admin profile for this set
AdminProfile[] profile = new AdminProfile[name.length];
for (int i = 0; i < name.length; i++)
{
profile[i] = new AdminProfile(name[i],
ar == null ? true :
ar.checkAnyAdminAccess(name[i]));
}
return profile;
}
/**
* Returns the admin permissions profile for an arbitrary set of
* permissions for a resource instance name.
*
* @param name
* admin leaf resource instance name to query
*
* @param perm
* array of permissions to query
*
* @return permissions for the given name or <code>null</code>
*/
public AdminProfile[] getProfile(String name, int[] perm)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the parameter
if (name == null)
{
message("Resource instance names can't be omitted");
return null;
}
// create the admin profile for this set
AdminProfile[] profile = new AdminProfile[perm.length];
for (int i = 0; i < perm.length; i++)
{
profile[i] = new AdminProfile(name,
ar == null ? true :
ar.checkAdminAccess(name, perm[i]) > 0);
}
return profile;
}
/**
* Returns all OS user account definitions. This API requires one of the following permission
* ADLU_READ_ACCESS.
*
* @return An array of all OS user account definitions.
*/
public TaggedName[] listOsUsers()
{
// check the access rights
if (!checkPermissions("/accounts/osusers", new int[]{ AdminRights.ADLU_READ_ACCESS }))
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
OsUserAccount[] osUserAccounts = sc.getAllOsUsers();
TaggedName[] tn = new TaggedName[osUserAccounts.length];
for (int i = 0; i < osUserAccounts.length; i++)
{
OsUserAccount osUserAccount = osUserAccounts[i];
String desc = osUserAccount.getDescription();
String hasPasswordSign = osUserAccount.getPassword() != null ? "✓" : "";
tn[i] = new TaggedName(osUserAccount.getSubjectId(), desc == null ? "" : desc, hasPasswordSign);
}
return tn;
}
/**
* Returns the OS user account definition for the specified account name. This API requires one of the
* following permission ADLU_READ_ACCESS.
*
* @param name
* OS user account name
*
* @return OS user account definition or <code>null</code> if no such OS user
*/
public OsUserDef getOsUser(String name)
{
// check the access rights
if (!checkPermissions("/accounts/osusers", new int[]{ AdminRights.ADLU_READ_ACCESS}))
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// sanity check
if (name == null)
{
message("OS user name can't be omitted.");
return null;
}
// get the OS user account
OsUserAccount ua = sc.getOsUserById(name);
if (ua == null)
{
// no such account
message("There is no OS user named '" + name + "'.");
return null;
}
// create the user account definition
OsUserDef ud = new OsUserDef();
// fill it out
ud.enabled = ua.isEnabled();
ud.description = ua.getDescription();
ud.protect = ua.isProtected();
ud.subjectId = ua.getSubjectId();
ud.password = ua.getPassword();
Calendar caDate = Calendar.getInstance();
if (ua.getPasswordDate() != null)
{
caDate.setTime(ua.getPasswordDate().getDate());
}
Calendar caTime = Calendar.getInstance();
if (ua.getPasswordTime() != null)
{
caTime.setTime(ua.getPasswordTime().getDate());
}
caDate.set(Calendar.HOUR_OF_DAY, caTime.get(Calendar.HOUR_OF_DAY));
caDate.set(Calendar.MINUTE, caTime.get(Calendar.MINUTE));
caDate.set(Calendar.SECOND, caTime.get(Calendar.SECOND));
caDate.set(Calendar.MILLISECOND, caTime.get(Calendar.MILLISECOND));
ud.dateTime = caDate.getTime();
return ud;
}
/**
* Adds new OS user account definition.
* <p>
* The mandatory fields of the OS user definition container are validated.
* Optional fields are added, depending on their presence in the container.
* <p>
* This API requires ADLU_CREATE_ACCESS permission.
*
* @param user
* user account definition
*
* @return <code>true</code> if successfully added the OS user
*/
public boolean addOsUser(OsUserDef user)
{
String containerNode = "/accounts/osusers";
// check the access rights
if (!checkPermission(containerNode, AdminRights.ADLU_CREATE_ACCESS))
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get the existing account
String name = user.subjectId;
if (name == null)
{
message("Account name can't be omitted.");
return false;
}
OsUserAccount acc = sc.getOsUserById(name);
if (acc != null)
{
// name duplication
message("OS user named '" + name + "' already exists.");
return false;
}
String fullContainerNode = "/security/accounts/osusers";
String fullContainerParentNode = "/security/accounts";
if (ds.getNodeClass(fullContainerNode) == null)
{
if (!ds.openBatch(fullContainerParentNode) ||
!ds.addNode(fullContainerNode, "container", null))
{
message("Can't create container " + fullContainerNode + " in directory.");
return false;
}
ds.closeBatch(true);
}
// open editing batch for the branch
boolean rc = ds.openBatch(fullContainerNode);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = fullContainerNode + "/" + user.subjectId;
if (user.password == null)
{
user.dateTime = null;
}
else
{
user.dateTime = new Date();
}
DateValue dateValue = user.dateTime == null ? null : new DateValue(user.dateTime);
TimeValue timeValue = user.dateTime == null ? null : new TimeValue(user.dateTime);
// create user node
rc = createOsUserNode(node, user.description, user.enabled, user.password, dateValue, timeValue);
// check directory operations
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Changes the OS user account definition. Optional fields are synchronized with the directory. Password
* aging information is set only if a password is set.
* <p>
* This API requires one of the following permissions:
* <ul>
* <li>ADLU_WRITE_ACCESS
* <li>ADLU_PASSWORD_ACCESS
* </ul>
* The ADLU_PASSWORD_ACCESS permission allows changing the password and its
* timestamp only. No validation of the rest of the user definition is done
* in this case.
*
* @param user
* user account definition
*
* @return <code>true</code> if successfully changed the OS user
*/
public boolean setOsUser(OsUserDef user)
{
// check the access rights
String path = "/accounts/osusers";
boolean permWr = queryPermission(path, AdminRights.ADLU_WRITE_ACCESS);
boolean permPw = queryPermission(path, AdminRights.ADLU_PASSWORD_ACCESS);
if (!permWr && !permPw)
{
message(String.format("insufficient access rights for %s (%d, %d)",
path,
AdminRights.ADLU_WRITE_ACCESS,
AdminRights.ADLU_PASSWORD_ACCESS));
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get the existing account
String name = user.subjectId;
if (name == null)
{
message("OS user name can't be omitted.");
return false;
}
OsUserAccount foundAcc = sc.getOsUserById(name);
if (foundAcc == null)
{
// no such account
message("There is no OS user named '" + name + "'.");
return false;
}
// open editing batch for the branch
String securityPath = "/security" + path;
boolean rc = ds.openBatch(securityPath);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = securityPath + "/" + user.subjectId;
// sync the enabled attribute
if (permWr && rc)
{
rc = syncBoolean(node, "enabled", user.enabled);
}
// sync the description attribute
if (rc)
{
rc = syncString(node, "description", user.description);
}
if ((user.password != null && !Arrays.equals(user.password, foundAcc.getPassword())) ||
(user.password == null && !user.protect))
{
// delete password timestamp if no password given
if (user.password == null)
{
user.dateTime = null;
}
else
{
user.dateTime = new Date();
}
// sync the password attribute
if (rc)
{
rc = syncBytes(node, "password", user.password);
}
// sync the password set date attribute
if (rc)
{
rc = syncDate(node, "pwsetdate", user.dateTime == null ? null : new DateValue(user.dateTime));
}
// sync the password set time attribute
if (rc)
{
rc = syncTime(node, "pwsettime", user.dateTime == null ? null : new TimeValue(user.dateTime));
}
}
// check directory operations
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Deletes an OS user definition.
* <p>
* This API requires ADLU_DELETE_ACCESS permission.
*
* @param name
* OS user name being deleted
*
* @return <code>true</code> if successfully deleted the account
*/
public boolean deleteOsUser(String name)
{
String path = "/accounts/osusers";
// check the access rights
if (!checkPermission(path, AdminRights.ADLU_DELETE_ACCESS))
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// remove the user from ACLs
if (!removeSubjectFromACLs(name))
{
// errors during ACLs modification
return false;
}
String securityPath = "/security" + path;
// open editing batch for the branch
boolean rc = ds.openBatch(securityPath);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build the node name
String node = securityPath + "/" + name;
// remove the user node
if (rc)
{
rc = ds.deleteNode(node);
}
// check directory operations
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Returns an array of defined FWD user names, associated with the OS user.
*
* @param osUser
* The OS user name
*
* @return The associated FWD users or <code>null</code>
*/
public TaggedName[] listAssociatedFwdUsers(String osUser)
{
// check the access rights
if (!checkPermissions("/accounts/users", new int[]
{ AdminRights.ADLU_READ_ACCESS }))
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// sanity check
if (osUser == null)
{
message("OS user can't be omitted.");
return null;
}
String[] allFwdUserIds = sc.getAllAccountIds(Account.ACC_USER);
if (allFwdUserIds == null)
{
return null;
}
return Arrays.stream(allFwdUserIds)
.parallel()
.map(id -> (UserAccount) sc.getAccountById(id))
.filter(fwdUser -> osUser.equals(fwdUser.getOsUser()))
.map(fwdUser -> new TaggedName(fwdUser.getSubjectId()))
.toArray(TaggedName[]::new);
}
/**
* Returns the user account definition for the specified account name.
* <p>
* This API requires one of the following permissions:
* <ul>
* <li>ADLU_READ_ACCESS
* <li>ADLU_WRITE_ACCESS
* <li>ADLU_PASSWORD_ACCESS
* </ul>
*
* @param name
* user account name
*
* @return user account definition or <code>null</code> if no such user
*/
@SuppressWarnings("null")
public UserDef getUser(String name)
{
// check the access rights
if (!checkPermissions("/accounts/users", new int[]
{ AdminRights.ADLU_PASSWORD_ACCESS,
AdminRights.ADLU_READ_ACCESS,
AdminRights.ADLU_WRITE_ACCESS }))
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// sanity check
if (name == null)
{
message("Account name can't be omitted.");
return null;
}
// get the user account
Account acc = sc.getAccountById(name);
if (acc == null)
{
// no such account
message("There is no account named '" + name + "'.");
return null;
}
if (acc.getAccountType() != Account.ACC_USER)
{
// not a group account
message("Account '" + name + "' is not a user.");
return null;
}
// create the user account definition
UserDef ud = new UserDef();
UserAccount ua = (UserAccount)acc;
// fill it out
ud.enabled = ua.isEnabled();
ud.subjectId = ua.getSubjectId();
ud.person = ua.getPerson();
ud.alias = ua.getAlias();
ud.protect = ua.isProtected();
ud.password = ua.getPassword();
ud.osUser = ua.getOsUser();
Calendar caDate = Calendar.getInstance();
if (ua.getPasswordDate() != null)
{
caDate.setTime(ua.getPasswordDate().getDate());
}
Calendar caTime = Calendar.getInstance();
if (ua.getPasswordTime() != null)
{
caTime.setTime(ua.getPasswordTime().getDate());
}
caDate.set(Calendar.HOUR_OF_DAY, caTime.get(Calendar.HOUR_OF_DAY));
caDate.set(Calendar.MINUTE, caTime.get(Calendar.MINUTE));
caDate.set(Calendar.SECOND, caTime.get(Calendar.SECOND));
caDate.set(Calendar.MILLISECOND, caTime.get(Calendar.MILLISECOND));
ud.dateTime = caDate.getTime();
if (ua.isModeInherited())
{
ud.mode = SecurityConstants.AUTH_MODE_NONE;
ud.authPlugin = null;
}
else
{
ud.mode = ua.getAuthMode();
ud.authPlugin = ua.getAuthPlugin();
}
// gather groups information
int[] gids = ua.getGroups();
int gs = gids == null ? 0 : gids.length;
String[] gr = new String[gs];
for (int i = 0; i < gs; i++)
{
gr[i] = sc.getAccountByOrd(gids[i]).getSubjectId();
}
ud.groups = gr;
return ud;
}
/**
* Changes the user account definition for the named account.
* Optional fields are synchronized with the directory. Password aging
* information is set only if a password is set.
* If the account is password protected and the password is present,
* but both the date and time fields are NOT present, then they are
* automatically set to the current date and time.
* <p>
* This API requires one of the following permissions:
* <ul>
* <li>ADLU_WRITE_ACCESS
* <li>ADLU_PASSWORD_ACCESS
* </ul>
* The ADLU_PASSWORD_ACCESS permission allows changing the password and its
* timestamp only. No validation of the rest of the user definition is done
* in this case.
*
* @param user
* user account definition
*
* @param ext
* optional extension container for custom account extensions
*
* @param syncExt
* if <code>true</code>, the state of the account should sync'ed
* with the state of the extension container;
* otherwise the state of the account extensions does not change
*
* @return <code>true</code> if successfully changed the account
*/
public boolean setUser(UserDef user, Serializable ext, boolean syncExt)
{
// check the access rights
boolean permWr = queryPermission("/accounts/users",
AdminRights.ADLU_WRITE_ACCESS);
boolean permPw = queryPermission("/accounts/users",
AdminRights.ADLU_PASSWORD_ACCESS);
if (!permWr && !permPw)
{
message(String.format("insufficient access rights for /accounts/users "
+ "(%d, %d)", AdminRights.ADLU_WRITE_ACCESS,
AdminRights.ADLU_PASSWORD_ACCESS));
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get the existing account
String name = user.subjectId;
if (name == null)
{
message("Account name can't be omitted.");
return false;
}
Account acc = sc.getAccountById(name);
if (acc == null)
{
// no such account
message("There is no account named '" + name + "'.");
return false;
}
// ensure it's a user
if (acc.getAccountType() != Account.ACC_USER)
{
// not a group account
message("Account '" + name + "' is not a user.");
return false;
}
// validate auth mode
if (permWr &&
(user.mode < AUTH_MODE_LOWEST || user.mode > AUTH_MODE_HIGHEST))
{
// invalid data
message("Authentication mode is invalid.");
return false;
}
// validate groups
if (permWr && (user.groups != null))
{
for (int i = 0; i < user.groups.length; i++)
{
if (getGroup(user.groups[i]) == null)
{
message("User account references an invalid group '" +
user.groups[i] + "'.");
return false;
}
}
}
// assign password timestamp if omitted
if (user.protect && user.password != null && user.dateTime == null)
{
user.dateTime = new Date();
}
// delete password timestamp if no password given
if (user.password == null)
{
user.dateTime = null;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/users");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = new String("/security/accounts/users/" +
user.subjectId);
// manage account extensions
if (permWr && ae != null && syncExt)
{
try
{
rc = ae.setExtension(ds, node, ext);
}
catch (AccountExtValidationException e)
{
// fast exit, validation failed
ds.closeBatch(false, false);
return false;
}
catch (Throwable t)
{
// unexpected failure
message("can't complete operation. " + t);
ds.closeBatch(false, false);
return false;
}
}
// sync the enabled attribute
if (permWr && rc)
{
rc = syncBoolean(node, "enabled", user.enabled);
}
// sync the person attribute
if (permWr && rc)
{
rc = syncString(node, "person", user.person);
}
// change the osuser attribute
if (permWr && rc)
{
rc = syncString(node, "osuser", user.osUser);
}
// sync the alias attribute
if (permWr && rc)
{
rc = syncString(node, "alias", user.alias);
}
// change the groups attribute
if (permWr && rc)
{
rc = syncStrings(node, "groups", user.groups);
}
// change auth mode settings
if (permWr && rc)
{
rc = syncAuthSettings(node, user.mode, user.authPlugin);
}
// sync the password-protected attribute
if (rc)
{
rc = syncBoolean(node, "protected", user.protect);
}
// sync the password attribute
if (rc)
{
rc = syncBytes(node, "password", user.password);
}
// sync the password set date attribute
// the null check is needed in case password has not been set for the account
if (rc && user.dateTime != null)
{
rc = syncDate(node, "pwsetdate", new DateValue(user.dateTime));
}
// sync the password set time attribute
// the null check is needed in case password has not been set for the account
if (rc && user.dateTime != null)
{
rc = syncTime(node, "pwsettime", new TimeValue(user.dateTime));
}
// check directory operations
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Adds new user account definition.
* <p>
* The mandatory fields of the user definition container are validated.
* Optional fields are added, depending on their presence in the container.
* One exception is the password timestamp: if the account is password
* protected and the password is present, but both the date and time fields
* are NOT present, then they are automatically set to the current date and
* time.
* <p>
* This API requires ADLU_CREATE_ACCESS permission.
*
* @param user
* user account definition
*
* @param ext
* optional extension container for custom account extensions
*
* @return <code>true</code> if successfully added the account
*/
public boolean addUser(UserDef user, Serializable ext)
{
// check the access rights
if (!checkPermission("/accounts/users", AdminRights.ADLU_CREATE_ACCESS))
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get the existing account
String name = user.subjectId;
if (name == null)
{
message("Account name can't be omitted.");
return false;
}
Account acc = sc.getAccountById(name);
if (acc != null)
{
// name duplication
message("Account named '" + name + "' already exists.");
return false;
}
// validate auth mode
if (user.mode < AUTH_MODE_LOWEST || user.mode > AUTH_MODE_HIGHEST)
{
// invalid data
message("Authentication mode is invalid.");
return false;
}
// validate groups
if (user.groups != null)
{
for (int i = 0; i < user.groups.length; i++)
{
if (getGroup(user.groups[i]) == null)
{
message("User account references an invalid group '" +
user.groups[i] + "'.");
return false;
}
}
}
// assign password timestamp if omitted
if (user.protect && user.password != null && user.dateTime == null)
{
user.dateTime = new Date();
}
// delete password timestamp if no password given
if (user.password == null)
{
user.dateTime = null;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/users");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = new String("/security/accounts/users/" + user.subjectId);
DateValue dateValue = user.dateTime == null ? null : new DateValue(user.dateTime);
TimeValue timeValue = user.dateTime == null ? null : new TimeValue(user.dateTime);
// create user node
rc = createUserNode(node, user.enabled, user.person, user.osUser, user.mode,
user.authPlugin, user.alias, user.groups, user.protect,
user.password, dateValue, timeValue);
// manage account extensions
if (rc && ae != null)
{
try
{
rc = ae.addExtension(ds, node, ext);
}
catch (AccountExtValidationException e)
{
// fast exit, validation failed
ds.closeBatch(false, false);
return false;
}
catch (Throwable t)
{
// unexpected failure
message("can't complete operation. " + t);
ds.closeBatch(false, false);
return false;
}
}
// check directory operations
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Deletes a user account definition.
* The account is deleted. All references to this account are deleted from
* the ACLs, but the associated user certificate is left untouched.
* <p>
* The directory modification is done in multiple separate transactions:
* <ul>
* <li>removing the user from the ACLs -
* <code>/security/acl</code> and <code>/security/acls</code>
* <li>removing the user account -
* <code>/security/accounts/users</code>
* </ul>
* This approach may leave the directory partially modified.
* <p>
* This API requires ADLU_DELETE_ACCESS permission.
*
* @param name
* user account name being deleted
*
* @return <code>true</code> if successfully deleted the account
*/
public boolean deleteUser(String name)
{
// check the access rights
if (!checkPermission("/accounts/users", AdminRights.ADLU_DELETE_ACCESS))
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// remove the user from ACLs
if (!removeSubjectFromACLs(name))
{
// errors during ACLs modification
return false;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/users");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build the node name
String node = new String("/security/accounts/users/" + name);
// manage account extensions
if (ae != null && ae.isExtended(ds, node))
{
try
{
rc = ae.deleteExtension(ds, node);
}
catch (Throwable t)
{
// unexpected failure
message("can't complete operation. " + t);
ds.closeBatch(false, false);
return false;
}
}
// remove the user node
if (rc)
{
rc = ds.deleteNode(node);
}
// check directory operations
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Clones an existing user account into a new one.
* ACLs are modified to give the source user rights to the clone, if
* this option is requested.
* <p>
* This API requires ADLU_CREATE_ACCESS permission.
*
* @param nameFrom
* user account name for the source
*
* @param nameTo
* user account name for the clone
*
* @param acls
* <code>true</code> tells to clone ACLs as well
*
* @param replacePassword
* <code>true</code> tells to assign the provided password
* instead of cloning it
*
* @param newPassword
* a replacement password for the clone
*
* @return <code>true</code> if successfully cloned the user account
*/
public boolean cloneUser(String nameFrom, String nameTo, boolean acls,
boolean replacePassword, byte[] newPassword)
{
// check the access rights
if (!checkPermission("/accounts/users", AdminRights.ADLU_CREATE_ACCESS))
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get the source user
UserDef su = getUser(nameFrom);
if (su == null)
{
// no source
return false;
}
if (replacePassword)
{
su.password = newPassword;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/users");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String nodeFrom = new String("/security/accounts/users/" + nameFrom);
String nodeTo = new String("/security/accounts/users/" + nameTo);
// create the target user node
rc = createUserNode(nodeTo, su.enabled, su.person, su.osUser, su.mode, su.authPlugin,
su.alias, su.groups, su.protect, su.password,
new DateValue(su.dateTime), new TimeValue(su.dateTime));
// manage account extensions
if (rc && ae != null && ae.isExtended(ds, nodeFrom))
{
try
{
rc = ae.cloneExtension(ds, nodeFrom, nodeTo);
}
catch (AccountExtValidationException e)
{
// fast exit, validation failed
ds.closeBatch(false, false);
return false;
}
catch (Throwable t)
{
// unexpected failure
message("can't complete operation. " + t);
ds.closeBatch(false, false);
return false;
}
}
// check directory operations
if (!rc)
{
message("Directory modification failed for " + nodeTo);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
return false;
}
if (!rc)
{
return false;
}
// clone ACLs
if (acls)
{
rc = cloneSubjectInACLs(nameFrom, nameTo);
if (!rc)
{
return false;
}
}
// recreate offline cache
rc = currentCache();
return rc;
}
/**
* Enumerates the groups the named user is part of.
* <p>
* This API requires one of the following permissions:
* <ul>
* <li>ADLU_READ_ACCESS
* <li>ADLU_WRITE_ACCESS
* <li>ADLU_PASSWORD_ACCESS
* </ul>
*
* @param name
* user account name
*
* @return array of tagged group names, that may be empty,
* or <code>null</code> if there is no such user etc
*/
@SuppressWarnings("null")
public TaggedName[] listUserGroups(String name)
{
// check the access rights
if (!checkPermissions("/accounts/users", new int[]
{ AdminRights.ADLU_PASSWORD_ACCESS,
AdminRights.ADLU_READ_ACCESS,
AdminRights.ADLU_WRITE_ACCESS }))
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// sanity check
if (name == null)
{
message("Account name can't be omitted.");
return null;
}
// get the user account
Account acc = sc.getAccountById(name);
if (acc == null)
{
// no such account
message("There is no account named '" + name + "'.");
return null;
}
if (acc.getAccountType() != Account.ACC_USER)
{
// not a group account
message("Account '" + name + "' is not a user.");
return null;
}
// gather groups information
UserAccount ua = (UserAccount)acc;
int[] gids = ua.getGroups();
int gs = gids == null ? 0 : gids.length;
TaggedName[] gr = new TaggedName[gs];
for (int i = 0; i < gs; i++)
{
gr[i] = new TaggedName(sc.getAccountByOrd(gids[i]).getSubjectId(),
sc.getAccountByOrd(gids[i]).getDescription());
}
return gr;
}
/**
* Returns an array of resource plugin names.
*
* @return All defined resource plugin names
*/
public String[] listResourceNames()
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// get the size of the array first
int nr = sc.getNumResources();
if (nr == 0)
{
return new String[] {};
}
// get the plugin names
String[] res = new String[nr];
AbstractResource plugin = null;
for (int i = 0; i < nr; i++)
{
plugin = sc.getRegistryByOrd(i).getPlugin();
res[i] = new String(plugin.getClass().getName());
}
return res;
}
/**
* Returns the registered resource type name for the given resource plugin.
*
* @param name
* plugin name
*
* @return registered resource type name or <code>null</code> if no match
*/
public String getResourceType(String name)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// get the size of the array first
int nr = sc.getNumResources();
if (nr == 0)
{
return null;
}
// lookup the plugin name
AbstractResource plugin = null;
for (int i = 0; i < nr; i++)
{
plugin = sc.getRegistryByOrd(i).getPlugin();
if (plugin.getClass().getName().equals(name))
{
return plugin.getTypeName();
}
}
return null;
}
/**
* Returns a tagged array of defined process names.
* Tags are process descriptions.
*
* @return All defined group accounts or <code>null</code>
*/
public TaggedName[] listProcesses()
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// get the list of accounts
String[] pl = sc.getAllAccountIds(Account.ACC_PROCESS);
if (pl == null)
{
return null;
}
// create tagged list
TaggedName[] tp = new TaggedName[pl.length];
for (int i = 0; i < pl.length; i++)
{
tp[i] = new TaggedName(new String(pl[i]),
new String(sc.getAccountById(pl[i]).getDescription()));
}
return tp;
}
/**
* Returns the process definition for the specified process name.
*
* @param name
* Process name.
*
* @return process definition or <code>null</code>
*/
public ProcessDef getProcess(String name)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// sanity check
if (name == null)
{
message("Account name can't be omitted.");
return null;
}
// get the process account
Account acc = sc.getAccountById(name);
if (acc == null)
{
// no such account
message("There is no account named '" + name + "'.");
return null;
}
if (acc.getAccountType() != Account.ACC_PROCESS)
{
// not a group account
message("Account '" + name + "' is not a process.");
return null;
}
// return the process definition
ProcessAccount pa = (ProcessAccount)acc;
ProcessDef pd = new ProcessDef();
pd.enabled = pa.isEnabled();
pd.subjectId = acc.getSubjectId();
pd.description = acc.getDescription();
pd.alias = acc.getAlias();
pd.server = pa.isServer();
pd.master = pa.isMaster();
return pd;
}
/**
* Changes the process definition for the named process.
*
* @param process
* process definition
*
* @param ext
* optional extension container for custom account extensions
*
* @param syncExt
* if <code>true</code>, the state of the account should sync'ed
* with the state of the extension container;
* otherwise the state of the account extensions does not change
*
* @return <code>true</code> if successfully changed the process
*/
public boolean setProcess(ProcessDef process, Serializable ext,
boolean syncExt)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get and check the process account's ordinal number
if (process.subjectId == null)
{
message("Account name can't be omitted.");
return false;
}
int po = sc.getOrdinalById(process.subjectId);
if (po == -1)
{
// no such account
message("There is no account named '" + process.subjectId + "'.");
return false;
}
Account acc = sc.getAccountByOrd(po);
if (acc.getAccountType() != Account.ACC_PROCESS)
{
// not a process account
message("Account '" + process.subjectId + "' is not a process.");
return false;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/processes");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = new String("/security/accounts/processes/" +
process.subjectId);
// manage account extensions
if (ae != null)
{
try
{
rc = ae.setExtension(ds, node, ext);
}
catch (AccountExtValidationException e)
{
// fast exit, validation failed
ds.closeBatch(false, false);
return false;
}
catch (Throwable t)
{
// unexpected failure
message("can't complete operation. " + t);
ds.closeBatch(false, false);
return false;
}
}
// sync the enabled attribute
if (rc)
{
rc = syncBoolean(node, "enabled", process.enabled);
}
// sync the description attribute
if (rc)
{
rc = syncString(node, "description", process.description);
}
// sync the alias attribute
if (rc)
{
rc = syncString(node, "alias", process.alias);
}
// sync the server attribute
if (rc)
{
rc = syncBoolean(node, "server", process.server);
}
// sync the master attribute
if (rc)
{
rc = syncBoolean(node, "master", process.server ?
Boolean.valueOf(process.master) : null);
}
// check directory operations
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Adds new process account definition.
*
* @param process
* process definition for a new account
*
* @param ext
* optional extension container for custom account extensions
*
* @return <code>true</code> if successfully added the process
*/
public boolean addProcess(ProcessDef process, Serializable ext)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get and check the existing accounts
if (process.subjectId == null)
{
message("Account name can't be omitted.");
return false;
}
int ao = sc.getOrdinalById(process.subjectId);
if (ao != -1)
{
// account exists
message("Name '" + process.subjectId +
"' duplicates existing account name.");
return false;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/processes");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = new String("/security/accounts/processes/" +
process.subjectId);
// create process node
rc = createProcessNode(node, process.enabled, process.description,
process.alias, process.server, process.master);
if (!rc)
{
message("Directory modification failed for " + node);
}
// manage account extensions
if (rc && ae != null)
{
try
{
rc = ae.addExtension(ds, node, ext);
}
catch (AccountExtValidationException e)
{
// fast exit, validation failed
ds.closeBatch(false, false);
return false;
}
catch (Throwable t)
{
// unexpected failure
message("can't complete operation. " + t);
ds.closeBatch(false, false);
return false;
}
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Deletes a process account definition.
* The account is deleted. All references to this process are deleted from
* the ACLs, but the associated process certificate is left untouched.
* <p>
* The directory modification is done in multiple separate transactions:
* <ul>
* <li>removing the process from the ACLs -
* <code>/security/acl</code> and <code>/security/acls</code>
* <li>removing the process account -
* <code>/security/accounts/processes</code>
* </ul>
* This approach may leave the directory partially modified.
*
* @param name
* process account name being deleted
*
* @return <code>true</code> if successfully deleted the process
*/
public boolean deleteProcess(String name)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// remove the process from ACLs
if (!removeSubjectFromACLs(name))
{
// errors during ACLs modification
return false;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/processes");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build the node name
String node = new String("/security/accounts/processes/" + name);
// manage account extensions
if (ae != null && ae.isExtended(ds, node))
{
try
{
rc = ae.deleteExtension(ds, node);
}
catch (Throwable t)
{
// unexpected failure
message("can't complete operation. " + t);
ds.closeBatch(false, false);
return false;
}
}
// remove the process node
rc = ds.deleteNode(node);
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Clones an existing process account into a new one.
* Master server flag is always false for the clone.
* ACLs are modified to give the source process' rights to the clone, if
* this option is requested.
*
* @param nameFrom
* process name for the source
*
* @param nameTo
* process name for the clone
*
* @param acls
* <code>true</code> tells to clone ACLs as well
*
* @return <code>true</code> if successfully cloned the process
*/
public boolean cloneProcess(String nameFrom, String nameTo, boolean acls)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// get the source process
ProcessDef sp = getProcess(nameFrom);
if (sp == null)
{
// no source
return false;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/processes");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String nodeFrom = new String("/security/accounts/processes/" + nameFrom);
String nodeTo = new String("/security/accounts/processes/" + nameTo);
// create process node
rc = createProcessNode(nodeTo, sp.enabled, sp.description, sp.alias,
sp.server, sp.master);
if (!rc)
{
message("Directory modification failed for " + nodeTo);
}
// manage account extensions
if (rc && ae != null && ae.isExtended(ds, nodeFrom))
{
try
{
rc = ae.cloneExtension(ds, nodeFrom, nodeTo);
}
catch (AccountExtValidationException e)
{
// fast exit, validation failed
ds.closeBatch(false, false);
return false;
}
catch (Throwable t)
{
// unexpected failure
message("can't complete operation. " + t);
ds.closeBatch(false, false);
return false;
}
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
return false;
}
if (!rc)
{
return false;
}
// clone ACLs
if (acls)
{
rc = cloneSubjectInACLs(nameFrom, nameTo);
if (!rc)
{
return false;
}
}
// recreate offline cache
rc = currentCache();
return rc;
}
/**
* Returns an array of defined CA or peer certificates with their
* distinctive names.
*
* @param auth
* if <code>true</code>, list CA certificates. otherwise peer
* certificates
*
* @param unusedOnly
* if <code>true</code>, lists unused certificates only,
* otherwise all certificates
*
* @return All defined certificates of a given type or <code>null</code>
*/
public TaggedName[] listCerts(boolean auth, boolean unusedOnly)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// quick return
if (auth && unusedOnly)
{
return new TaggedName[]{};
}
// get the list of aliases of the type
String als[] = auth ? sc.getCaAliases() : sc.getPeerAliases();
// create the tagged list
TaggedName[] ctn = new TaggedName[als.length];
KeyStore ts = sc.getTrustStore();
for (int i = 0; i < als.length; i++)
{
// access the certificate
try
{
X509Certificate cert = (X509Certificate)ts.getCertificate(als[i]);
// copy the distinctive name
ctn[i] = new TaggedName(als[i], cert.getSubjectX500Principal().getName());
}
catch (KeyStoreException kse)
{
// never happens here
message("Unexpected KeyStoreException.");
}
}
if (auth || !unusedOnly)
{
return ctn;
}
// exclude assigned certificates from the collection
List<TaggedName> lu = new LinkedList<TaggedName>();
for (int i = 0; i < als.length; i ++)
{
TaggedName[] tn = listCertAccounts(als[i], Account.ACC_ALL);
if (tn != null && tn.length > 0)
{
continue;
}
lu.add(ctn[i]);
}
return lu.toArray(new TaggedName[]{});
}
/**
* Returns an array of user account names with their description for users
* sharing this certificate.
*
* @param alias
* certificate alias name
*
* @return all user accounts referring to this certificate or
* <code>null</code>
*/
public TaggedName[] getCertUsers(String alias)
{
return listCertAccounts(alias, Account.ACC_USER);
}
/**
* Returns an array of process account names with their description for
* processes sharing this certificate.
*
* @param alias
* certificate alias name
*
* @return all process accounts referring to this certificate or
* <code>null</code>
*/
public TaggedName[] getCertProcesses(String alias)
{
return listCertAccounts(alias, Account.ACC_PROCESS);
}
/**
* Returns an array of all account names with their description for
* accounts sharing this certificate.
*
* @param alias
* certificate alias name
*
* @return all accounts referring to this certificate or <code>null</code>
*/
public TaggedName[] getCertAccounts(String alias)
{
return listCertAccounts(alias, Account.ACC_ALL);
}
/**
* Returns a DER-encoded certificate like those found in *.PEM files.
*
* @param alias
* the alias of the certificate to get
*
* @return the encoded certificate or <code>null</code>
*/
public CertDef getCert(String alias)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// access the truststore
KeyStore ts = sc.getTrustStore();
X509Certificate cert = null;
// get the certificate
try
{
cert = (X509Certificate)ts.getCertificate(alias);
}
catch (KeyStoreException kse)
{
message("Unexpected KeyStoreException.");
return null;
}
if (cert == null)
{
// no such certificate
message("No certificate with alias '" + alias + "'.");
return null;
}
// encode certificate and create definition
CertDef cd = new CertDef();
cd.alias = alias;
cd.authority = false;
// identify the type
String als[] = sc.getCaAliases();
for (int i = 0; i < als.length; i++)
{
if (als[i].equals(alias))
{
cd.authority = true;
break;
}
}
// get the encoded certificate
String b64 = null;
try
{
byte[] enc = cert.getEncoded();
b64 = Base64.byteArrayToBase64(enc);
}
catch (CertificateEncodingException see)
{
message("Unexpected CertificateEncodingException.");
return null;
}
// split the encoding into SPLIT_CHUNK sized chunks
List<String> ls = new LinkedList<String>();
ls.add(CERT_BEG);
for (int i = 0; i < b64.length(); i += SPLIT_CHUNK)
{
int len = b64.length() - i >= SPLIT_CHUNK ? SPLIT_CHUNK :
b64.length() - i;
ls.add(b64.substring(i, i + len));
}
ls.add(CERT_END);
cd.certificate = ls.toArray(new String[]{});
return cd;
}
/**
* Replaces an existing certificate with the given one.
*
* @param cert
* encoded certificate
*
* @return <code>true</code> if replaced, <code>false</code> if errors
*/
public boolean setCert(CertDef cert)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// check and reassemble the certificate encoding
if (cert.alias == null)
{
message("Certificate alias can't be omitted.");
return false;
}
byte[] enc = checkCertificate(cert);
if (enc == null)
{
return false;
}
// open editing batch for the branch
String branch = cert.authority ? "/security/certificates/cas" :
"/security/certificates/peers";
boolean rc = ds.openBatch(branch);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = new String(branch + "/" + cert.alias);
// modify the directory
rc = ds.setNodeByteArray(node, "value", enc);
// check directory operations
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Adds a new certificate according to its definition.
*
* @param cert
* encoded certificate
*
* @return <code>true</code> if added, <code>false</code> if errors
*/
public boolean addCert(CertDef cert)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// check the alias for duplication
if (cert.alias == null)
{
message("Certificate alias can't be omitted.");
return false;
}
if (!checkCAAlias(cert.alias))
{
message("Alias duplication in CA domain.");
return false;
}
if (!checkPeerAlias(cert.alias))
{
message("Alias duplication in peer domain.");
return false;
}
// check and reassemble the certificate encoding
byte[] enc = checkCertificate(cert);
if (enc == null)
{
// messages are generated in utility method
return false;
}
// open editing batch for the branch
String branch = cert.authority ? "/security/certificates/cas" :
"/security/certificates/peers";
boolean rc = ds.openBatch(branch);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = new String(branch + "/" + cert.alias);
// modify the directory
rc = createCertNode(node, enc);
// check directory operations
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Deletes an existing certificate conditionally or unconditionally.
* Conditional deletion means the certificate belongs to the peer domain
* and is not used by any account.
*
* @param alias
* the alias of the certificate being deleted
*
* @param force
* if <code>true</code>, the certificate is deleted even if in
* use
*
* @return <code>true</code> if deleted, <code>false</code> if errors
*
*/
public boolean deleteCert(String alias, boolean force)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// find the certificate's domain
boolean auth = false;
if (!checkCAAlias(alias))
{
// found in CA
if (!force)
{
message("CA certificate with alias '" + alias +
"' can't be deleted.");
return false;
}
auth = true;
}
if (checkPeerAlias(alias))
{
// not found in peers
if (!auth)
{
message("No certificate with alias '" + alias + "'.");
return false;
}
}
else
{
// found in peers
if (!force)
{
TaggedName[] tn = listCertAccounts(alias, Account.ACC_ALL);
if (tn != null && tn.length > 0)
{
message("Peer certificate with alias '" + alias +
"' can't be deleted.");
return false;
}
}
}
// open editing batch for the branch
String branch = auth ? "/security/certificates/cas" :
"/security/certificates/peers";
boolean rc = ds.openBatch(branch);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String node = new String(branch + "/" + alias);
// modify the directory
rc = ds.deleteNode(node);
// check directory operations
if (!rc)
{
message("Directory modification failed for " + node);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Changes the certificate's alias.
*
* @param aliasFrom
* current certificate's alias
*
* @param aliasTo
* new certificate's alias
*
* @return <code>true</code> if changed, <code>false</code> if errors
*/
public boolean moveCert(String aliasFrom, String aliasTo)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// both names are required
if (aliasFrom == null || aliasTo == null)
{
message("Aliases can't be omitted.");
return false;
}
// names must be different
if (aliasFrom.equals(aliasTo))
{
message("Can't move alias to itself.");
return false;
}
// check the source alias for existence
if (checkCAAlias(aliasFrom) && checkPeerAlias(aliasFrom))
{
message("Source alias not found.");
return false;
}
// check the target alias for duplication
if (!checkCAAlias(aliasTo))
{
message("Alias duplication in CA domain.");
return false;
}
if (!checkPeerAlias(aliasTo))
{
message("Alias duplication in peer domain.");
return false;
}
// determine the alias' domain
boolean peer = false;
if (checkCAAlias(aliasFrom))
{
peer = true;
}
// open editing batch for the branch
String branch = !peer ? "/security/certificates/cas" :
"/security/certificates/peers";
boolean rc = ds.openBatch(branch);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// build node ID
String nodeFrom = new String(branch + "/" + aliasFrom);
// modify the directory
rc = ds.moveNode(nodeFrom, branch + "/" + aliasTo);
// check directory operations
if (!rc)
{
message("Directory modification failed for " + nodeFrom);
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Searches the CA directory for an alias of the certificate by the
* distinctive name of the subject.
*
* @param dn
* distinctive name
*
* @return alias of the certificate if exists, or <code>null</code>
*/
public String searchCert(String dn)
{
// get the list of all certificates in CA domain
String[] als = sc.getCaAliases();
// get the truststore
KeyStore ts = sc.getTrustStore();
// do the direct search
for (int i = 0; i < als.length; i++)
{
try
{
X509Certificate cert = (X509Certificate)ts.getCertificate(als[i]);
String dna = cert.getSubjectX500Principal().getName();
if (CertificateUtils.compareNames(dn, dna))
{
return als[i];
}
}
catch (KeyStoreException kse)
{
return null;
}
}
// no duplicate
return null;
}
/**
* Causes the live system to reload the P2J directory from the back
* storage.
*
* @return <code>true</code> if successful
*/
public boolean consReloadDirectory()
{
// check the access rights
if (!checkPermission())
{
return false;
}
boolean rc = ds.atomicRefresh();
// recreate offline cache
if (rc)
{
rc = currentCache();
}
return rc;
}
/**
* Creates a report of all current user and process sessions.
*
* @return Session descriptors suitable for reporting purposes or
* <code>null</code> if insufficient rights exist to obtain the
* list.
*/
public SessionInfo[] getSessionList()
{
// check the access rights
if (!checkPermission())
{
return null;
}
return sm.sessionSm.getSessionReport();
}
/**
* Creates a report of a session matching the provided id.
*
* @param id
* The context's session id of the {@link SecuritySession}.
* It can be retrieved through {@link SessionToken#getSessionID()}.
*
* @return The session descriptor suitable for reporting purposes.
* <code>null</code> if insufficient rights exist to obtain it
* or the session was not found.
*/
public SessionInfo getSessionById(int id)
{
// check the access rights
if (!checkPermission())
{
return null;
}
return sm.sessionSm.getSessionReportById(id);
}
/**
* Forcibly terminates (closes) the socket in use for the given session.
*
* @param sid
* Session ID to terminate.
*/
public void killSession(int sid)
{
// check the access rights
if (!checkPermission())
{
throw new SecurityException("Insufficient rights to kill a session.");
}
try
{
sm.sessionSm.killSession(sid);
}
catch (RestrictedUseException e)
{
throw new SecurityException("Unable to kill a session due to restricted use.");
}
}
/**
* Locates all instances of ACLs in the directory and returns their names.
* The shared instance has an empty name.
* <p>
* ACL instances are the directory branches that store ACLs. The shared
* instance is the one found under <code>/security/acl</code> branch.
* Private instances are found under
* <code>/security/acls/<i>name</i></code> branches.
*
* @return array of ACL instance names; may be empty
*/
public String[] listAclInstances()
{
// check the access rights
if (!checkPermission())
{
return null;
}
return enumerateACLInstances();
}
/**
* Locates all instances of ACLs in the directory and returns their names
* for those instances which have ACLs defined for the specified resource
* type. The shared instance has an empty name.
* <p>
* ACL instances are the directory branches that store ACLs. The shared
* instance is the one found under <code>/security/acl</code> branch.
* Private instances are found under
* <code>/security/acls/<i>name</i></code> branches.
*
* @param resourceType
* name of the resource of interest
*
* @return array of ACL instance names; may be empty
*/
public String[] listAclInstances(String resourceType)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// get the filtered list
return enumerateACLInstances(resourceType);
}
/**
* Lists resource names for which the specified ACL instance has ACLs
* defined.
*
* @param instanceName
* name of the ACL instance; empty string for the shared one
*
* @return array of resource type names; may be empty
*/
public String[] listInstanceAcls(String instanceName)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// calculate the directory branch
String node = instanceName.equals("") ? "/security/acl" :
"/security/acls/" + instanceName;
// enumerate resource types
String[] rtypes = ds.enumerateNodes(node);
if (rtypes == null)
{
return new String[0];
}
return rtypes;
}
/**
* Gets the full ACL definition information from the directory for the
* specified ACL instance and resource type.
*
* @param resourceType
* name of the resource of interest; can't be <code>null</code>
*
* @param instanceName
* name of the ACL instance; empty string for the shared one, or
* <code>null</code> for the effective list of this server
*
* @return ACL definition or <code>null</code>
*/
public AclDef getAcl(String resourceType, String instanceName)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// check arguments
if (resourceType == null)
{
message("Resource type can't me omitted");
return null;
}
if (checkResourceType(resourceType) == -1)
{
return null;
}
// get the specified or shared instance first
String path1 = instanceName == null || instanceName.length() == 0 ?
"/security/acl/" + resourceType :
"/security/acls/" + instanceName + "/" + resourceType;
// conditionally get the additional path
String path2 = instanceName == null ?
"/security/acls/" + sc.getServerAccount().getSubjectId() +
"/" + resourceType : null;
// get the ACL definition, possibly combined
AclDef ad = getAclWorker(resourceType, instanceName, path1, path2);
return ad;
}
/**
* Gets the full ACL definition information from the directory for the
* specified ACL instance or all of them.
*
* @param instanceName
* name of the ACL instance; empty string for the shared one; if
* <code>null</code>, all ACLs are returned and some may be
* combined
*
* @return an array of ACL definitions, one per resource type and instance,
* or <code>null</code>
*/
public AclDef[] getAcls(String instanceName)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// create the scan list
String[] inst = null;
if (instanceName != null)
{
inst = new String[] {instanceName};
}
else
{
inst = enumerateACLInstances();
}
// process the list
List<AclDef> ad = new LinkedList<AclDef>();
String serverInstance = sc.getServerAccount().getSubjectId();
for (int i = 0; i < inst.length; i++)
{
// if combining, do not return the private instance separately
if (instanceName == null && inst[i].equals(serverInstance))
{
continue;
}
// base path
String path = inst[i].length() == 0 ? "/security/acl"
: "/security/acls/" + inst[i];
// resource part
String[] rtypes = listInstanceAcls(inst[i]);
if (rtypes == null)
{
return null;
}
for (int j = 0; j < rtypes.length; j++)
{
// conditionally get the additional path
String path2 = null;
String instance = inst[i];
if (instanceName == null && instance.equals(""))
{
// combining and this is the shared instance
instance = null;
path2 = "/security/acls/" + serverInstance + "/" + rtypes[j];
}
AclDef acd = getAclWorker(rtypes[j],
instance,
path + "/" + rtypes[j],
path2);
if (acd == null)
{
return null;
}
ad.add(acd);
}
}
return ad.toArray(new AclDef[]{});
}
/**
* Adds new set of ACLs based on the contents of the given ACL definition.
* Both the target ACL instance and resource part will be created if they
* don't exist.
*
* @param resourceType
* name of the resource to add the ACL for
*
* @param acl
* ACL definition, that should come fully defined, including the
* instance name.
*
* @param subjMap
* a map to translate numeric subject IDs back to names
*
* @return <code>true</code> if added, <code>false</code> if errors
*/
@SuppressWarnings("null")
public boolean addAcl(String resourceType, Acl acl, Map<Integer, String> subjMap)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// check arguments
if (resourceType == null)
{
message("Resource type can't be omitted");
return false;
}
// resource type should be known
int ord = checkResourceType(resourceType);
if (ord == -1)
{
return false;
}
// check the ACL in depth
String[] subjects = null;
if (acl != null && acl.subjects != null)
{
subjects = new String[acl.subjects.length];
}
if (!checkAcl(resourceType, acl, subjMap, subjects,
true, true, true, true))
{
return false;
}
// calculate the directory branch to lock
String branch = acl.origin.equals("") ?
"/security/acl" : "/security/acls/" + acl.origin;
String save = null;
boolean createInstance = false;
boolean createResource = false;
if (ds.getNodeClass(branch) == null)
{
createInstance = true;
createResource = true;
// what to create
save = branch;
// what to lock
branch = branch.substring(0, branch.lastIndexOf("/"));
}
// open editing batch for the branch
boolean rc = ds.openBatch(branch);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// create instance if it doesn't exist
if (createInstance)
{
rc = ds.addNode(save, "container", null);
if (!rc)
{
message(String.format("Can't create %s node", save));
}
branch = save;
}
// check if the resource exists
branch = branch + "/" + resourceType;
if (rc && !createResource)
{
if (ds.getNodeClass(branch) == null)
{
createResource = true;
}
}
// create resource if it doesn't exist
if (rc && createResource)
{
rc = ds.addNode(branch, "container", null);
if (!rc)
{
message(String.format("Can't create %s node", branch));
}
}
// build the ACL node ID and create the ACL container node
String soid = null;
String node = null;
if (rc)
{
soid = String.format("%06d", acl.oid);
node = String.format("%s/%s", branch, soid);
if (ds.getNodeClass(node) != null)
{
message(String.format("Node %s already exists", node));
rc = false;
}
else
{
rc = ds.addNode(node, "container", null);
if (!rc)
{
message(String.format("Can't create %s node", node));
}
}
}
// create rights node
if (rc)
{
rc = ds.addNodeAttributes(node + "/rights", acl.rights.getRightsName(),
acl.rights.getAttributes());
if (!rc)
{
message(String.format("Can't create rights node for %s", node));
}
}
// create resource instance node
if (rc)
{
Attribute[] rdata = new Attribute[]
{
// resource instance name
new Attribute(ds.getClassNodeAttribute("resource",
"reference"),
new Object[] {acl.name}),
// resource instance mode
new Attribute(ds.getClassNodeAttribute("resource",
"reftype"),
new Object[] {acl.exact}),
};
rc = ds.addNode(node + "/resource-instance", "resource", rdata);
if (!rc)
{
message("can't add resource-instance to " + node);
}
}
// create subjects node
if (rc)
{
Attribute[] sdata = new Attribute[]
{
// subjects
new Attribute(ds.getClassNodeAttribute("strings", "values"),
subjects),
};
rc = ds.addNode(node + "/subjects", "strings", sdata);
if (!rc)
{
message("can't add subjects to " + node);
}
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// keep track of the added node; recreate offline cache
if (rc && rcc)
{
mapTypes = mapInst.get(acl.origin);
if (mapTypes == null)
{
mapTypes = new HashMap<String, Map<Integer, String>>();
mapInst.put(acl.origin, mapTypes);
}
mapId = mapTypes.get(resourceType);
if (mapId == null)
{
mapId = new HashMap<Integer, String>();
mapTypes.put(resourceType, mapId);
}
mapId.put(acl.oid, soid);
rc = currentCache();
return rc;
}
return false;
}
/**
* Get the list of available auth plugins.
*
* @return the list of available auth plugins encoded into tagged names or
* <code>null</code> if no plugins are available.
*/
public TaggedName[] listAuthPlugins()
{
// check the cache
if (!checkCache())
{
return null;
}
AuthPlugin[] plugins = sc.getAuthPlugins();
if (plugins == null || plugins.length == 0)
{
return null;
}
TaggedName[] taggedNames = new TaggedName[plugins.length];
for (int i = 0; i < plugins.length; i++)
{
taggedNames[i] = plugins[i].toTaggedName();
}
return taggedNames;
}
/**
* Preforms bulk update of authentication settings of the accounts of the
* specified type.
*
* @param targetAccounts
* Type of accounts to update. <code>Account.ACC_USER</code> and
* <code>Account.ACC_GROUP</code> are supported.
* @param excludedAccounts
* The list of IDs of the accounts that should NOT be updated. Can
* be <code>null</code>.
* @param authMode
* Authentication mode to set.
* @param authPlugin
* Authentication plugin to set.
*
* @return the number of updated accounts or <code>-1</code> if an error
* has occured.
*/
public int bulkAuthModeUpdate(int targetAccounts,
String[] excludedAccounts,
int authMode,
String authPlugin)
{
// validate auth mode
if (authMode < AUTH_MODE_LOWEST || authMode > AUTH_MODE_HIGHEST)
{
message("Authentication mode is invalid.");
return -1;
}
if (authMode != AUTH_MODE_CUSTOM)
authPlugin = null;
// check the cache
if (!checkCache())
{
return -1;
}
int updatedNum = 0;
Set<String> excludedSet = new HashSet<String>();
if (excludedAccounts != null)
{
excludedSet.addAll(Arrays.asList(excludedAccounts));
}
if (targetAccounts == Account.ACC_USER)
{
String[] userList = sc.getAllAccountIds(Account.ACC_USER);
if (userList == null || userList.length == 0)
return 0;
if (!queryPermission("/accounts/users",
AdminRights.ADLU_WRITE_ACCESS))
{
// insufficient access rights
return -1;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/users");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return -1;
}
for (String userId : userList)
{
if (excludedSet.contains(userId))
continue;
UserDef userDef = getUser(userId);
if (userDef != null &&
(userDef.mode != authMode ||
(userDef.authPlugin == null ? authPlugin != null :
!userDef.authPlugin.equals(authPlugin))))
{
// build node ID
String node =
new StringBuilder("/security/accounts/users/").
append(userId).toString();
// update auth settings
rc = syncAuthSettings(node, authMode, authPlugin);
if (rc)
updatedNum++;
}
}
}
else if (targetAccounts == Account.ACC_GROUP)
{
String[] groupList = sc.getAllAccountIds(Account.ACC_GROUP);
if (groupList == null || groupList.length == 0)
return 0;
// check the access rights
if (!checkPermission())
{
return -1;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/groups");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return -1;
}
for (String groupId : groupList)
{
if (excludedSet.contains(groupId))
continue;
GroupDef groupDef = getGroup(groupId);
if (groupDef != null &&
(groupDef.mode != authMode ||
(groupDef.authPlugin == null ? authPlugin != null :
!groupDef.authPlugin.equals(authPlugin))))
{
// build node ID
String node =
new StringBuilder("/security/accounts/groups/").
append(groupDef.subjectId).toString();
// update auth settings
rc = syncAuthSettings(node, authMode, authPlugin);
if (rc)
updatedNum++;
}
}
}
else
{
return -1;
}
// close batch with commit or rollback and no refresh
boolean rc = ds.closeBatch(updatedNum > 0, false);
if (!rc)
{
message("Error committing directory changes.");
return -1;
}
else
{
// recreate offline cache
if (updatedNum > 0)
currentCache();
return updatedNum;
}
}
/**
* Send a refresh request to specified plugin.
*
* @param resourceType
*/
public void refreshPlugin(String resourceType)
{
int ord = checkResourceType(resourceType);
AbstractResource plugin = sc.getRegistryByOrd(ord).getPlugin();
if (plugin != null)
plugin.refresh(this.ae);
}
/**
* Conditionally edits the existing ACL based on the contents of the given
* ACL and the selected conditions.
*
* @param resourceType
* name of the resource to set the ACL for
*
* @param acl
* ACL definition
*
* @param subjMap
* a map to translate numeric subject IDs back to names; needed
* only when setSubjects is <code>true</code> and can be
* <code>null</code> all other times
*
* @param setInstance
* <code>true</code> if setting the resource instance name
*
* @param setInstanceMode
* <code>true</code> if setting the resource instance name mode
*
* @param setSubjects
* <code>true</code> if setting the subjects IDs
*
* @param setRights
* <code>true</code> if setting the rights for this ACL
*
* @return <code>true</code> if added, <code>false</code> if errors
*/
@SuppressWarnings("null")
public boolean setAcl(String resourceType, Acl acl,
Map<Integer, String> subjMap,
boolean setInstance, boolean setInstanceMode,
boolean setSubjects, boolean setRights)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// check arguments
if (resourceType == null)
{
message("Resource type can't be omitted");
return false;
}
// resource type should be known
int ord = checkResourceType(resourceType);
if (ord == -1)
{
return false;
}
// check the ACL in depth
String[] subjects = null;
if (acl != null && acl.subjects != null)
{
subjects = new String[acl.subjects.length];
}
if (!checkAcl(resourceType, acl, subjMap, subjects,
setInstance, setSubjects, setRights, false))
{
return false;
}
// find the target ACL container's parent
String branch = acl.origin.equals("") ?
"/security/acl" : "/security/acls/" + acl.origin;
branch = branch + "/" + resourceType;
// lookup the ACL container's oid (it has been checked for existence)
mapTypes = mapInst.get(acl.origin);
if (mapTypes == null)
{
mapTypes = new HashMap<String, Map<Integer, String>>();
mapInst.put(acl.origin, mapTypes);
}
mapId = mapTypes.get(resourceType);
if (mapId == null)
{
mapId = new HashMap<Integer, String>();
mapTypes.put(resourceType, mapId);
}
String soid = mapId.get(acl.oid);
String node = branch + "/" + soid;
// open editing batch for the branch
boolean rc = ds.openBatch(branch);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// set the resource instance name
if (setInstance)
{
String rnode = node + "/resource-instance";
// make sure the node exists
if (ds.getNodeClass(rnode) == null)
{
// create directory object
Attribute[] data = new Attribute[]
{
// dummy reference
new Attribute(ds.getClassNodeAttribute("resource", "reference"),
new Object[] {""}),
// reftype
new Attribute(ds.getClassNodeAttribute("resource", "reftype"),
new Object[] {acl.exact}),
};
rc = ds.addNode(rnode, "resource", data);
}
if (rc)
{
rc = syncString(rnode, "reference", acl.name);
}
if (!rc)
{
message("Can't set reference attribute of " + rnode);
}
}
// set the resource instance mode
if (rc && setInstanceMode)
{
String rnode = node + "/resource-instance";
// make sure the node exists
if (ds.getNodeClass(rnode) == null)
{
// create directory object
Attribute[] data = new Attribute[]
{
// reference
new Attribute(ds.getClassNodeAttribute("resource", "reference"),
new Object[] {acl.name}),
// reftype
new Attribute(ds.getClassNodeAttribute("resource", "reftype"),
new Object[] {acl.exact}),
};
rc = ds.addNode(rnode, "resource", data);
}
if (rc)
{
rc = syncBoolean(rnode, "reftype", acl.exact);
}
if (!rc)
{
message("Can't set reftype attribute of " + rnode);
}
}
// set the subjects
if (rc && setSubjects)
{
String snode = node + "/subjects";
// make sure the node exists
if (ds.getNodeClass(snode) == null)
{
// create directory object
Attribute[] data = new Attribute[]
{
};
rc = ds.addNode(snode, "strings", data);
}
if (rc)
{
rc = syncStrings(snode, "values", subjects);
}
if (!rc)
{
message("Can't set subjects as " + snode);
}
}
// set the rights
if (rc && setRights)
{
String rnode = node + "/rights";
// remove the old node first
if (ds.getNodeClass(rnode) != null)
{
rc = Utils.removeDirectoryNode(ds, rnode);
if (!rc)
{
message("Can't remove the existing node " + rnode);
}
}
if (rc)
{
rc = ds.addNodeAttributes(rnode, acl.rights.getRightsName(),
acl.rights.getAttributes());
if (!rc)
{
message(String.format("Can't create rights node for %s", rnode));
}
}
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
rc = currentCache();
return rc;
}
return false;
}
/**
* Moves the specified ACL to another position within the same ACL
* instance.
*
* @param resourceType
* name of the resource of interest
*
* @param instanceName
* name of the source ACL instance; empty string for the shared
* one
*
* @param oid
* ACL object ID that selects the ACL to move
*
* @param newOid
* new ACL object ID
*
* @return <code>true</code> if moved, <code>false</code> if errors
*/
public boolean moveAcl(String resourceType, String instanceName, int oid,
int newOid)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// check arguments
if (resourceType == null)
{
message("Resource type can't be omitted");
return false;
}
// resource type should be known
int ord = checkResourceType(resourceType);
if (ord == -1)
{
return false;
}
// check the existence of the instance and the source ACL
if (instanceName == null)
{
message("instance name can't be omitted");
return false;
}
// check the existence of the instance, the source and target ACLs
mapTypes = mapInst.get(instanceName);
if (mapTypes == null)
{
message(String.format("Instance '%s' was not queried first",
instanceName));
return false;
}
mapId = mapTypes.get(resourceType);
if (mapId == null)
{
message(String.format(
"Resource '%s' for instance '%s' was not queried first",
resourceType, instanceName));
return false;
}
String soid = mapId.get((oid));
if (soid == null)
{
message("Source ACL does not exist or was not queried first");
return false;
}
String toid = mapId.get((newOid));
if (toid != null)
{
message(String.format(
"Target ACL should not exist, but '%s' was found", toid));
return false;
}
// calculate directory path
String path = instanceName.length() == 0 ?
"/security/acl/" + resourceType :
"/security/acls/" + instanceName + "/" + resourceType;
// check the source node
String snode = path + "/" + soid;
String sclass = ds.getNodeClass(snode);
if (sclass == null)
{
message(String.format("Source node '%s' was not found", snode));
return false;
};
if (!sclass.equals("/meta/class/container"))
{
message(String.format(
"Found source node '%s' of unexpected class '%s'",
snode, sclass));
return false;
};
// check the target node
toid = String.format("%06d", newOid);
String tnode = path + "/" + toid;
String tclass = ds.getNodeClass(tnode);
if (tclass != null)
{
message(String.format("Target node '%s' already exists", tnode));
return false;
};
// open editing batch for the branch
boolean rc = ds.openBatch(path);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// move the ACL
rc = ds.moveNode(snode, tnode);
if (!rc)
{
message(String.format("Move '%s' to '%s' operation failed", snode,
tnode));
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
// refresh the map: delete oid, add newOid
mapId.remove(Integer.valueOf(oid));
mapId.put(Integer.valueOf(newOid), toid);
rc = currentCache();
return rc;
}
return false;
}
/**
* Moves the specified ACL to another ACL instance, private or shared. The
* target instance will be created, if it doesn't exist.
*
* @param resourceType
* name of the resource of interest
*
* @param instanceName
* name of the source ACL instance; empty string for the shared
* one
*
* @param oid
* ACL object ID that selects the ACL to move
*
* @param newInstanceName
* name of the target ACL instance; empty string for the shared
* one
*
* @return <code>true</code> if moved, <code>false</code> if errors
*/
public boolean moveAcl(String resourceType, String instanceName, int oid,
String newInstanceName)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// check arguments
if (resourceType == null)
{
message("Resource type can't be omitted");
return false;
}
// resource type should be known
int ord = checkResourceType(resourceType);
if (ord == -1)
{
return false;
}
// check the existence of the instance and the source ACL
if (instanceName == null || newInstanceName == null)
{
message("instance names can't be omitted");
return false;
}
if (instanceName.equals(newInstanceName))
{
message("instance names should be different");
return false;
}
mapTypes = mapInst.get(instanceName);
if (mapTypes == null)
{
message(String.format("Instance '%s' was not queried first",
instanceName));
return false;
}
mapId = mapTypes.get(resourceType);
if (mapId == null)
{
message(String.format(
"Resource '%s' for instance '%s' was not queried first",
resourceType, instanceName));
return false;
}
String soid = mapId.get((oid));
if (soid == null)
{
message("Source ACL does not exist or was not queried first");
return false;
}
// calculate directory paths
String sipath = instanceName.length() == 0 ?
"/security/acl" : "/security/acls/" + instanceName;
String spath = sipath + "/" + resourceType;
String tipath = newInstanceName.length() == 0 ?
"/security/acl" : "/security/acls/" + newInstanceName;
String tpath = tipath + "/" + resourceType;
// check the source node
String snode = spath + "/" + soid;
String sclass = ds.getNodeClass(snode);
if (sclass == null)
{
message(String.format("Source node '%s' was not found", snode));
return false;
};
if (!sclass.equals("/meta/class/container"))
{
message(String.format(
"Found source node '%s' of unexpected class '%s'",
snode, sclass));
return false;
};
// check the target node
String tnode = tpath + "/" + soid;
String tclass = ds.getNodeClass(tnode);
if (tclass != null)
{
message(String.format("Target node '%s' already exists", tnode));
return false;
};
// calculate the directory branch to lock:
// it is the common parent of the source resource node and
// the parent of either the target instance or target resource node
String branch = tpath;
boolean createInstance = false;
boolean createResource = false;
if (ds.getNodeClass(branch) == null)
{
createResource = true;
branch = tipath;
}
if (ds.getNodeClass(branch) == null)
{
createInstance = true;
branch = tipath.substring(0, tipath.lastIndexOf("/"));
}
branch = commonParent(spath, branch);
// open editing batch for the branch
boolean rc = ds.openBatch(branch);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// create instance if it doesn't exist
if (createInstance)
{
rc = ds.addNode(tipath, "container", null);
if (!rc)
{
message(String.format("Can't create %s node", tipath));
}
}
// create resource if it doesn't exist
if (rc && createResource)
{
rc = ds.addNode(tpath, "container", null);
if (!rc)
{
message(String.format("Can't create %s node", tpath));
}
}
// move the ACL
if (rc)
{
rc = ds.moveNode(snode, tnode);
if (!rc)
{
message(String.format("Move '%s' to '%s' operation failed", snode,
tnode));
}
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
// refresh the source map: delete oid
mapId.remove(Integer.valueOf(oid));
// refresh the target map" add newOid
mapTypes = mapInst.get(newInstanceName);
if (mapTypes == null)
{
mapTypes = new HashMap<String, Map<Integer, String>>();
mapInst.put(newInstanceName, mapTypes);
}
mapId = mapTypes.get(resourceType);
if (mapId == null)
{
mapId = new HashMap<Integer, String>();
mapTypes.put(resourceType, mapId);
}
mapId.put(Integer.valueOf(oid), soid);
rc = currentCache();
return rc;
}
return false;
}
/**
* Deletes the specified ACL from the specified ACL instance.
*
* @param resourceType
* name of the resource of interest
*
* @param instanceName
* name of the source ACL instance; empty string for the shared
* one
*
* @param oid
* ACL object ID that selects the ACL to delete
*
* @return <code>true</code> if deleted, <code>false</code> if errors
*/
public boolean deleteAcl(String resourceType, String instanceName, int oid)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// check arguments
if (resourceType == null)
{
message("Resource type can't be omitted");
return false;
}
// resource type should be known
int ord = checkResourceType(resourceType);
if (ord == -1)
{
return false;
}
// check the existence of the instance
if (instanceName == null)
{
message("instance name can't be omitted");
return false;
}
mapTypes = mapInst.get(instanceName);
if (mapTypes == null)
{
message(String.format("Instance '%s' was not queried first",
instanceName));
return false;
}
mapId = mapTypes.get(resourceType);
if (mapId == null)
{
message(String.format(
"Resource '%s' for instance '%s' was not queried first",
resourceType, instanceName));
return false;
}
String soid = mapId.get((oid));
if (soid == null)
{
message("Source ACL does not exist or was not queried first");
return false;
}
// calculate directory path
String path = instanceName.length() == 0 ?
"/security/acl/" + resourceType :
"/security/acls/" + instanceName + "/" + resourceType;
// check the node
String snode = path + "/" + soid;
String sclass = ds.getNodeClass(snode);
if (sclass == null)
{
message(String.format("Source node '%s' was not found", snode));
return false;
};
if (!sclass.equals("/meta/class/container"))
{
message(String.format(
"Found source node '%s' of unexpected class '%s'",
snode, sclass));
return false;
};
// open editing batch for the branch
boolean rc = ds.openBatch(path);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// delete the ACL
rc = Utils.removeDirectoryNode(ds, snode);
if (!rc)
{
message(String.format("Deleting '%s' failed", snode));
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
// refresh the map: delete oid
mapId.remove(Integer.valueOf(oid));
rc = currentCache();
return rc;
}
return false;
}
/**
* Clones the specified ACL to another position within the same ACL
* instance.
*
* @param resourceType
* name of the resource of interest
*
* @param instanceName
* name of the source ACL instance; empty string for the shared
* one
*
* @param oid
* ACL object ID that selects the ACL to clone
*
* @param cloneOid
* ACL object ID for the clone
*
* @return <code>true</code> if cloned, <code>false</code> if errors
*/
public boolean cloneAcl(String resourceType, String instanceName, int oid,
int cloneOid)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// check arguments
if (resourceType == null)
{
message("Resource type can't be omitted");
return false;
}
// resource type should be known
int ord = checkResourceType(resourceType);
if (ord == -1)
{
return false;
}
// check the existence of the instance, the source and target ACLs
// check the existence of the instance
if (instanceName == null)
{
message("instance name can't be omitted");
return false;
}
mapTypes = mapInst.get(instanceName);
if (mapTypes == null)
{
message(String.format("Instance '%s' was not queried first",
instanceName));
return false;
}
mapId = mapTypes.get(resourceType);
if (mapId == null)
{
message(String.format(
"Resource '%s' for instance '%s' was not queried first",
resourceType, instanceName));
return false;
}
String soid = mapId.get((oid));
if (soid == null)
{
message("Source ACL does not exist or was not queried first");
return false;
}
String toid = mapId.get((cloneOid));
if (toid != null)
{
message(String.format(
"Target ACL should not exist, but '%s' was found", toid));
return false;
}
// calculate directory path
String path = instanceName.length() == 0 ?
"/security/acl/" + resourceType :
"/security/acls/" + instanceName + "/" + resourceType;
// check the source node
String snode = path + "/" + soid;
String sclass = ds.getNodeClass(snode);
if (sclass == null)
{
message(String.format("Source node '%s' was not found", snode));
return false;
};
if (!sclass.equals("/meta/class/container"))
{
message(String.format(
"Found source node '%s' of unexpected class '%s'",
snode, sclass));
return false;
};
// check the target node
toid = String.format("%06d", cloneOid);
String tnode = path + "/" + toid;
String tclass = ds.getNodeClass(tnode);
if (tclass != null)
{
message(String.format("Target node '%s' already exists", tnode));
return false;
};
// open editing batch for the branch
boolean rc = ds.openBatch(path);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// copy the ACL
rc = ds.copyNode(snode, tnode, true);
if (!rc)
{
message(String.format("Copy '%s' to '%s' operation failed", snode,
tnode));
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
// refresh the map: add cloneOid
mapId.put(Integer.valueOf(cloneOid), toid);
rc = currentCache();
return rc;
}
return false;
}
/**
* Assigns new object IDs sequentially for all ACLs in the given ACL
* instance and resource type.
*
* @param resourceType
* name of the resource of interest
*
* @param instanceName
* name of the source ACL instance; empty string for the shared
* one
*
* @param start
* the starting number
*
* @param step
* the increment
*
* @return <code>true</code> if success, <code>false</code> if errors
*/
@SuppressWarnings("null")
public boolean resequenceAcls(String resourceType, String instanceName,
int start, int step)
{
// check the access rights
if (!checkPermission())
{
return false;
}
// check the cache
if (!checkCache())
{
return false;
}
// check arguments
if (resourceType == null)
{
message("Resource type can't be omitted");
return false;
}
// resource type should be known
int ord = checkResourceType(resourceType);
if (ord == -1)
{
return false;
}
// check the existence of the instance, the source and target ACLs
// check the existence of the instance
if (instanceName == null)
{
message("instance name can't be omitted");
return false;
}
// calculate directory path
String path = instanceName.length() == 0 ?
"/security/acl/" + resourceType :
"/security/acls/" + instanceName + "/" + resourceType;
// open editing batch for the branch
boolean rc = ds.openBatch(path);
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// enumerate and sort existing ACLs
// create a combination map: Integer oid to ACL path
SortedMap<Integer, String> amap = new TreeMap<Integer, String>();
// enumerate ACLs under the mandatory path
int badIds = enumerateACLs(path, amap, 0);
String[] acls = null;
if (badIds > 0)
{
message(String.format("fix %d bad ACL IDs before resequencing",
badIds));
rc = false;
}
else
{
// create sorted array of ACL names
acls = new String[amap.size()];
Set<Integer> set = amap.keySet();
Iterator<Integer> iter = set.iterator();
int i = 0;
while (iter.hasNext())
{
Integer k = iter.next();
String base = amap.get(k);
String oid = base.substring(base.lastIndexOf("/") + 1);
acls[i++] = oid;
}
}
// rename existing nodes to temporary names
if (rc)
{
for (int i = 0; i < acls.length; i++)
{
rc = ds.moveNode(path + "/" + acls[i], path + "/z" + acls[i]);
if (!rc)
{
message("temporary ACL rename failed for " +
path + "/" + acls[i]);
break;
}
}
}
// resequence existing nodes permanently
Map<Integer, String> tempMap = new HashMap<Integer, String>();
if (rc)
{
int nseq = start;
for (int i = 0; i < acls.length; i++)
{
String seq = String.format("%06d", nseq);
tempMap.put(Integer.valueOf(nseq), seq);
rc = ds.moveNode(path + "/z" + acls[i], path + "/" + seq);
if (!rc)
{
message("ACL rename failed.");
break;
}
nseq += step;
}
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
}
// recreate offline cache
if (rc && rcc)
{
// refresh the map
mapTypes = mapInst.get(instanceName);
if (mapTypes == null)
{
mapTypes = new HashMap<String, Map<Integer, String>>();
mapInst.put(instanceName, mapTypes);
}
mapId = mapTypes.get(resourceType);
if (mapId == null)
{
mapId = new HashMap<Integer, String>();
mapTypes.put(resourceType, mapId);
}
mapId.clear();
mapId.putAll(tempMap);
rc = currentCache();
return rc;
}
return false;
}
/**
* Account reports extension point.
*
* @return An instance of {@link AccountReportsExtension} or <code>null</code> if no
* extension is present.
*/
public AccountReportsExtension getAccountReportsExtension()
{
return ae != null ? ae.getAccountReportsExtension() : null;
}
/**
* Checks an ACL for validity.
*
* @param resourceType
* name of the resource to add the ACL for
*
* @param acl
* ACL definition
*
* @param subjMap
* a map to translate numeric subject IDs back to names
*
* @param subjects
* array to store the subject names
*
* @param setInstance
* <code>true</code> if setting the resource instance name
*
* @param setSubjects
* <code>true</code> if setting the subjects IDs
*
* @param setRights
* <code>true</code> if setting the rights for this ACL
*
* @param addOrSet
* <code>true</code> if adding new ACL, else setting existing ACL
*
* @return <code>true</code> if valid
*/
private boolean checkAcl(String resourceType, Acl acl,
Map<Integer, String> subjMap, String[] subjects,
boolean setInstance, boolean setSubjects,
boolean setRights, boolean addOrSet)
{
// ACL should be present
if (acl == null)
{
message("ACL can't me omitted");
return false;
}
// subject map should be present
if (setSubjects && subjMap == null)
{
message("Subject map can't be omitted");
return false;
}
// subject map should be present
if (setSubjects)
{
if (subjMap == null)
{
message("Subject map can't be omitted");
return false;
}
if (subjMap.isEmpty())
{
message("Subject map must not be empty");
return false;
}
}
// check ACL details
// origin should be present
if (acl.origin == null)
{
message("Target ACL instance can't be omitted");
return false;
}
// resource name should be present
if (setInstance && acl.name == null)
{
message("Target ACL resource name can't be omitted");
return false;
}
// subjects should be present
if (setSubjects && acl.subjects == null)
{
message("Target ACL subjects can't be omitted");
return false;
}
// rights should be present
if (setRights && acl.rights == null)
{
message("Target ACL rights can't be omitted");
return false;
}
// check the oid - should be valid and new (add) or valid and existing
if (acl.oid < 0)
{
message("Target ACL ID can't be negative");
return false;
}
boolean exists = false;
mapTypes = mapInst.get(acl.origin);
if (mapTypes == null)
{
mapTypes = new HashMap<String, Map<Integer, String>>();
mapInst.put(acl.origin, mapTypes);
}
mapId = mapTypes.get(resourceType);
if (mapId == null)
{
mapId = new HashMap<Integer, String>();
mapTypes.put(resourceType, mapId);
}
if (mapId.containsKey(Integer.valueOf(acl.oid)))
{
exists = true;
}
if (addOrSet && exists)
{
message("Target ACL ID should not exist");
return false;
}
else if (!addOrSet && !exists)
{
message("Target ACL ID should have been queried first");
return false;
}
// check the resource name with the resource plugin, if exact
if (setInstance && acl.exact)
{
// locate the plugin responsible for this resource type
int ord = checkResourceType(resourceType);
AbstractResource plugin = sc.getRegistryByOrd(ord).getPlugin();
if (!plugin.isInstanceNameValid(acl.name))
{
message("Target ACL resource name is not acceptable");
return false;
}
}
// check subject names for validity and duplication
if (setSubjects)
{
for (int i = 0; i < acl.subjects.length; i++)
{
// special case of ALL_OTHERS
if (acl.subjects[i] == AclDef.ALL_OTHERS)
{
if (acl.subjects.length != 1)
{
message("ALL_OTHERS must be the only subject");
return false;
}
subjects[i] = "all_others";
continue;
}
// map numeric ID to name
String subj = subjMap.get(Integer.valueOf(acl.subjects[i]));
if (subj == null)
{
message(String.format(
"Target ACL subject %d is either unmapped or duplicate",
acl.subjects[i]));
return false;
}
// check the name being a valid account
Account acc = sc.getAccountById(subj);
if (acc == null)
{
message(String.format("Target ACL subject %s does not exist",
subj));
return false;
}
// add name to the array
subjects[i] = subj;
}
}
// everything is perfect
return true;
}
/**
* Checks the resource type name for validity.
*
* @param resourceType
* resource type name
*
* @return registered resource type ordinal or <code>-1</code> if no match
*/
private int checkResourceType(String resourceType)
{
// get the size of the array first
int nr = sc.getNumResources();
if (nr == 0)
{
message("No resource types defined at all");
return -1;
}
// lookup the resource plugin table
AbstractResource plugin = null;
for (int i = 0; i < nr; i++)
{
plugin = sc.getRegistryByOrd(i).getPlugin();
if (plugin.getTypeName().equals(resourceType))
{
return i;
}
}
message("Unknown resource type '" + resourceType + "'");
return -1;
}
/**
* Checks certificate alias for duplication in CA domain.
*
* @param alias
* certificate alias
*
* @return <code>true</code> if there is no duplicate
*/
private boolean checkCAAlias(String alias)
{
// get the list in CA domain
String[] als = sc.getCaAliases();
// check for duplication
for (int i = 0; i < als.length; i++)
{
if (als[i].equals(alias))
{
// found duplicate
return false;
}
}
// no duplicate
return true;
}
/**
* Checks certificate alias for duplication in peer domain.
*
* @param alias
* certificate alias
*
* @return <code>true</code> if there is no duplicate
*/
private boolean checkPeerAlias(String alias)
{
// get the list in peer domain
String[] als = sc.getPeerAliases();
// check for duplication
for (int i = 0; i < als.length; i++)
{
if (als[i].equals(alias))
{
// found duplicate
return false;
}
}
// no duplicate
return true;
}
/**
* Reassembles certificate encoding from its definition. Verifies the
* certificate's domain and its validity.
* <p>
* The domain verification is based on the presence and contents of the
* BasicConstraints X.509 extension, object ID 2.5.29.19. To avoid using a
* DER decoder, this particular case takes a shortcut, based on the
* knowledge of the expected result.
* <p>
* First, the result of the getExtensionValue() is always an OCTET STRING.
* Second, the BasicConstraints value is
*
* <pre>
* BasicConstraints ::= SEQUENCE {
* cA BOOLEAN DEFAULT FALSE,
* pathLenConstraint INTEGER (0..MAX) OPTIONAL }
* </pre>
*
* Thus, the DER encoding should look like this:
* <ul>
* <li>- 4 or more bytes
* <li>- byte 0 is 0x04 typically for OCTET STRING
* <li>- byte 1 encodes the length of the rest
* <li>- byte 2, if present, is 0x30 for SEQUENCE
* <li>- byte 3, if present, is a BOOLEAN cA
* </ul>
*
* @param cert
* certificate definition
*
* @return array of bytes of the encoded certificate or <code>null</code>
*/
private byte[] checkCertificate(CertDef cert)
{
// check and reassemble the certificate encoding
int len = cert.certificate.length - 2;
if (len < 1 ||
!cert.certificate[0].equals(CERT_BEG) ||
!cert.certificate[len + 1].equals(CERT_END))
{
message("Certificate definition format is bad.");
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 1; i < len + 1; i++)
{
if ((i != len && cert.certificate[i].length() != SPLIT_CHUNK) ||
(i == len && cert.certificate[i].length() > SPLIT_CHUNK))
{
message("Certificate definition format is bad.");
return null;
}
sb.append(cert.certificate[i]);
}
// get the binary encoding
byte[] bin = Base64.base64ToByteArray(sb.toString());
ByteArrayInputStream bis = new ByteArrayInputStream(bin);
// decode the certificate
X509Certificate xcert = null;
try
{
CertificateFactory cf = CertificateFactory.getInstance("X.509");
xcert = (X509Certificate)cf.generateCertificate(bis);
bis.close();
}
catch (CertificateException ce)
{
return null;
}
catch (IOException ioe)
{
}
finally
{
try
{
bis.close();
}
catch (IOException ioe)
{
}
}
// verify certificate's domain inspecting the BasicConstraints extension
byte[] bc = xcert.getExtensionValue("2.5.29.19");
boolean cA = false;
// DER-decoding shortcut:
if (bc.length >= 4 && bc[3] != 0)
{
cA = true;
}
if (cert.authority)
{
// for CA certificate, the BasicConstraints extension is required
// and should indicate CA:TRUE
if (!cA)
{
message("Certificate is not of a Certificate Authority");
return null;
}
}
else
{
// for peer certificate, the BasicConstraints extension is not
// required, but if present, should indicate CA:FALSE
if (cA)
{
message("Certificate is of a Certificate Authority");
return null;
}
}
// certificate is acceptable
return bin;
}
/**
* Returns an array of account names with their description for accounts
* sharing this certificate.
*
* @param alias
* certificate alias name
*
* @param type
* account type
*
* @return all accounts referring to this certificate or <code>null</code>
*/
private TaggedName[] listCertAccounts(String alias, int type)
{
// check the access rights
if (!checkPermission())
{
return null;
}
// check the cache
if (!checkCache())
{
return null;
}
// list all accounts of the type
String[] al = sc.getAllAccountIds(type);
if (al == null)
{
return null;
}
// select by alias
List<TaggedName> ltn = new LinkedList<TaggedName>();
for (int i = 0; i < al.length; i++)
{
Account acc = sc.getAccountById(al[i]);
String aalias = acc.getAlias();
if (aalias != null && aalias.equals(alias))
{
TaggedName tn = new TaggedName(al[i], acc.getDescription());
ltn.add(tn);
}
}
return ltn.toArray(new TaggedName[]{});
}
/**
* Creates new security cache generation offline.
*
* @return <code>true</code> if cache is OK
*/
private boolean currentCache()
{
try
{
sc = new SecurityCache(sm.getConfig(), -1, SecurityCache.LM_ADMIN, null);
}
catch (ConfigurationException cfgx)
{
sc = null;
message(new String[] {cfgx.getMessage()});
return false;
}
catch (NoSuchMethodException nsmx)
{
sc = null;
message(new String[] {nsmx.getMessage()});
return false;
}
sc.refreshAll(this.ae);
return true;
}
/**
* Checks to see if the cache is available and stores a message if not.
*
* @return <code>true</code> if cache is OK
*/
private boolean checkCache()
{
if (sc == null)
{
message("Last security cache reconstruction attempt has failed.");
return false;
}
return true;
}
/**
* Changes the group by adding and/or removing users.
*
* @param name
* group name
*
* @param additions
* array of user names to be added to the group; may be empty
*
* @param deletions
* array of user names to be excluded from the group;
* may be empty
*
* @return <code>true</code> if successfully changed the group
*/
private boolean changeGroupUsersWorker(String name, String[] additions,
String[] deletions)
{
// get the existing users of the group
TaggedName[] tusers = listGroupUsers(name);
if (tusers == null)
{
// inconsistency found
message("Directory inconsistency found when listing users of group '"
+ name + "'.");
return false;
}
// open editing batch for the branch
boolean rc = ds.openBatch("/security/accounts/users");
if (!rc)
{
message("Can't lock directory branch. Try later.");
return false;
};
// process additions
for (int i = 0; i < additions.length; i++)
{
String newUser = additions[i];
// check for valid user name
Account ua = sc.getAccountById(newUser);
if (ua == null || ua.getAccountType() != Account.ACC_USER)
{
message("There is no user account named '" + newUser + "'.");
ds.closeBatch(false, false);
return false;
}
// check for duplication
for (int n = 0; n < tusers.length; n++)
{
if (tusers[n].getName().compareToIgnoreCase(newUser) == 0)
{
message("User '" + newUser + "' is in the group yet.");
ds.closeBatch(false, false);
return false;
}
}
// OK to add this user to the group
if (!ds.addNodeString("/security/accounts/users/" + newUser,
"groups", name))
{
message("Directory modification failed for " +
"/security/accounts/users/" + newUser);
ds.closeBatch(false, false);
return false;
}
}
// process deletions
for (int i = 0; i < deletions.length; i++)
{
String delUser = deletions[i];
// check for valid user name
Account ua = sc.getAccountById(delUser);
if (ua == null || ua.getAccountType() != Account.ACC_USER)
{
message("There is no user account named '" + delUser + "'.");
ds.closeBatch(false, false);
return false;
}
// check for presence in the group
int n = 0;
for (n = 0; n < tusers.length; n++)
{
if (tusers[n].getName().compareToIgnoreCase(delUser) == 0)
{
break;
}
}
if (n == tusers.length)
{
// not in the group; ignore it
message("User '" + tusers[n].getName() + "' not in the group '" + name +
"'");
continue;
}
// OK to delete this user from the group
// 1. get all groups for the user
String node = new String("/security/accounts/users/" + delUser);
String[] groups = ds.getNodeStrings(node, "groups");
if (groups == null)
{
// inconsistency found
message("Directory access failed for " +
"/security/accounts/users/" + delUser);
ds.closeBatch(false, false);
return false;
}
// 2. find the group being serviced
int j = 0;
for (j = 0; j < groups.length; j++)
{
if (groups[j].compareToIgnoreCase(name) == 0)
{
break;
}
}
if (j == groups.length)
{
// inconsistency found
message("Directory inconsistency. User '" + delUser +
"' should be in group '" + name + "'.");
ds.closeBatch(false, false);
return false;
}
// 3. delete group by index
if (!ds.deleteNodeAttributeValue(node, "groups", j))
{
message("Directory modification failed for " + node);
ds.closeBatch(false, false);
return false;
}
}
// close batch with commit and no refresh
rc = ds.closeBatch(true, false);
if (!rc)
{
message("Error committing directory changes.");
}
return rc;
}
/**
* Synchronizes a directory node attribute with the given value. It means
* setting or removing the attribute value.
*
* @param id
* object ID for a directory node
*
* @param attr
* node attribute name
*
* @param value
* attribute value to sync with; may be <code>null</code>
*
* @return <code>true</code> if successful
*/
private boolean syncString(String id, String attr, String value)
{
boolean rc = true;
if (value != null)
{
// check the attribute existence
String old = ds.getNodeString(id, attr);
if (old != null)
{
// sync by setting the value
rc = ds.setNodeString(id, attr, value);
}
else
{
// sync by adding the value
rc = ds.addNodeString(id, attr, value);
}
}
else
{
// sync by removing the attribute
if (ds.getNodeString(id, attr) != null)
{
rc = ds.deleteNodeAttribute(id, attr);
}
}
return rc;
}
/**
* Synchronizes a directory node attribute with the given value. It means
* setting or removing the attribute value.
*
* @param id
* object ID for a directory node
*
* @param attr
* node attribute name
*
* @param value
* attribute value to sync with; may be <code>null</code>
*
* @return <code>true</code> if successful
*/
private boolean syncStrings(String id, String attr, String[] value)
{
boolean rc = true;
// remove the attribute first
if (ds.getNodeStrings(id, attr) != null)
{
rc = ds.deleteNodeAttribute(id, attr);
}
if (value != null && value.length > 0)
{
// recreate the values
for (int i = 0; i < value.length; i++)
{
rc = ds.addNodeString(id, attr, value[i]);
}
}
return rc;
}
/**
* Synchronizes a directory node attribute with the given value. It means
* setting or removing the attribute value.
*
* @param id
* object ID for a directory node
*
* @param attr
* node attribute name
*
* @param value
* attribute value to sync with; may be <code>null</code>
*
* @return <code>true</code> if successful
*/
private boolean syncBoolean(String id, String attr, Boolean value)
{
boolean rc = true;
if (value != null)
{
// check the attribute existence
Boolean old = ds.getNodeBoolean(id, attr);
if (old != null)
{
// sync by setting the value
rc = ds.setNodeBoolean(id, attr, value);
}
else
{
// sync by adding the value
rc = ds.addNodeBoolean(id, attr, value);
}
}
else
{
// sync by removing the attribute
if (ds.getNodeBoolean(id, attr) != null)
{
rc = ds.deleteNodeAttribute(id, attr);
}
}
return rc;
}
/**
* Synchronizes a directory node attribute with the given value. It means
* setting or removing the attribute value.
*
* @param id
* object ID for a directory node
*
* @param attr
* node attribute name
*
* @param value
* attribute value to sync with; may be <code>null</code>
*
* @return <code>true</code> if successful
*/
private boolean syncInteger(String id, String attr, Integer value)
{
boolean rc = true;
if (value != null)
{
// check the attribute existence
Integer old = ds.getNodeInteger(id, attr);
if (old != null)
{
// sync by setting the value
rc = ds.setNodeInteger(id, attr, value);
}
else
{
// sync by adding the value
rc = ds.addNodeInteger(id, attr, value);
}
}
else
{
// sync by removing the attribute
if (ds.getNodeInteger(id, attr) != null)
{
rc = ds.deleteNodeAttribute(id, attr);
}
}
return rc;
}
/**
* Synchronizes a directory node attribute with the given value. It means
* setting or removing the attribute value.
*
* @param id
* object ID for a directory node
*
* @param attr
* node attribute name
*
* @param value
* attribute value to sync with; may be <code>null</code>
*
* @return <code>true</code> if successful
*/
private boolean syncBytes(String id, String attr, byte[] value)
{
boolean rc = true;
if (value != null)
{
// check the attribute existence
byte[] old = ds.getNodeByteArray(id, attr);
if (old != null)
{
// sync by setting the value
rc = ds.setNodeByteArray(id, attr, value);
}
else
{
// sync by adding the value
rc = ds.addNodeByteArray(id, attr, value);
}
}
else
{
// sync by removing the attribute
if (ds.getNodeByteArray(id, attr) != null)
{
rc = ds.deleteNodeAttribute(id, attr);
}
}
return rc;
}
/**
* Synchronizes a directory node attribute with the given value. It means
* setting or removing the attribute value.
*
* @param id
* object ID for a directory node
*
* @param attr
* node attribute name
*
* @param value
* attribute value to sync with; may be <code>null</code>
*
* @return <code>true</code> if successful
*/
private boolean syncDate(String id, String attr, DateValue value)
{
boolean rc = true;
if (value != null)
{
// check the attribute existence
DateValue old = ds.getNodeDate(id, attr);
if (old != null)
{
// sync by setting the value
rc = ds.setNodeDate(id, attr, value);
}
else
{
// sync by adding the value
rc = ds.addNodeDate(id, attr, value);
}
}
else
{
// sync by removing the attribute
if (ds.getNodeDate(id, attr) != null)
{
rc = ds.deleteNodeAttribute(id, attr);
}
}
return rc;
}
/**
* Synchronizes a directory node attribute with the given value. It means
* setting or removing the attribute value.
*
* @param id
* object ID for a directory node
*
* @param attr
* node attribute name
*
* @param value
* attribute value to sync with; may be <code>null</code>
*
* @return <code>true</code> if successful
*/
private boolean syncTime(String id, String attr, TimeValue value)
{
boolean rc = true;
if (value != null)
{
// check the attribute existence
TimeValue old = ds.getNodeTime(id, attr);
if (old != null)
{
// sync by setting the value
rc = ds.setNodeTime(id, attr, value);
}
else
{
// sync by adding the value
rc = ds.addNodeTime(id, attr, value);
}
}
else
{
// sync by removing the attribute
if (ds.getNodeTime(id, attr) != null)
{
rc = ds.deleteNodeAttribute(id, attr);
}
}
return rc;
}
/**
* Creates a group account directory object.
*
* @param gid
* object ID for the new directory object
*
* @param text
* group description
*
* @param mode
* authorization mode
*
* @param authPlugin
* custom authentication plugin class
*
* @return <code>true</code> if successful
*/
private boolean createGroupNode(String gid, String text,
int mode, String authPlugin)
{
// create directory object
List<Attribute> data = new ArrayList<Attribute>(4);
// group data: description (string)
data.add(new Attribute(ds.getClassNodeAttribute("group", "description"),
new Object[] { text }));
if (mode != SecurityConstants.AUTH_MODE_NONE)
{
data.add(new Attribute(ds.getClassNodeAttribute("group", "mode"),
new Object[] { Integer.valueOf(mode) }));
}
if (authPlugin != null)
{
data.add(new Attribute(ds.getClassNodeAttribute("group",
"auth-plugin"),
new Object[] { authPlugin }));
}
return ds.addNode(gid, "group", data.toArray(new Attribute[0]));
}
/**
* Creates a user account directory object.
*
* @param uid
* object ID for the new directory object
*
* @param enabled
* enabled/disabled status of the account
*
* @param person
* account owner's name; may be <code>null</code>
*
* @param osuser
* the OS user associated with the account
*
* @param mode
* account authentication mode
*
* @param alias
* related certificate's alias; may be <code>null</code>
*
* @param groups
* array of group names this account is a part of; may be
* <code>null</code>
*
* @param protect
* password-protected status of the account
*
* @param password
* password hash; may be <code>null</code>
*
* @param date
* date of the last password change; may be <code>null</code>
*
* @param time
* time of the last password change; may be <code>null</code>
*
* @param authPlugin
* custom authentication plugin class
*
* @return <code>true</code> if successful
*/
private boolean createUserNode(String uid, boolean enabled, String person, String osuser, int mode,
String authPlugin, String alias, String[] groups, boolean protect,
byte[] password, DateValue date, TimeValue time)
{
// enumerate present attributes
int na = 2; // for "enabled" and "protected"
if (person != null)
{
na++;
}
if (osuser != null)
{
na++;
}
if (alias != null)
{
na++;
}
if (groups != null && groups.length > 0)
{
na++;
}
if (password != null)
{
na++;
}
if (date != null)
{
na++;
}
if (time != null)
{
na++;
}
if (mode != SecurityConstants.AUTH_MODE_NONE)
{
na++;
}
if (authPlugin != null)
{
na++;
}
// create the exact size array
Attribute[] data = new Attribute[na];
// stuff the array
int i = 0;
data[i++] = new Attribute(ds.getClassNodeAttribute("user", "enabled"),
new Object[] {Boolean.valueOf(enabled)});
data[i++] = new Attribute(ds.getClassNodeAttribute("user", "protected"),
new Object[] {Boolean.valueOf(protect)});
if (mode != SecurityConstants.AUTH_MODE_NONE)
{
data[i++] = new Attribute(ds.getClassNodeAttribute("user", "mode"),
new Object[] {Integer.valueOf(mode)});
}
if (authPlugin != null)
{
data[i++] = new Attribute(ds.getClassNodeAttribute("user",
"auth-plugin"),
new Object[] {authPlugin});
}
if (person != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("user", "person"),
new Object[] {person});
}
if (osuser != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("user", "osuser"),
new Object[] {osuser});
}
if (alias != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("user", "alias"),
new Object[] {alias});
}
if (groups != null && groups.length > 0)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("user", "groups"),
groups);
}
if (password != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("user", "password"),
new Object[] {password});
}
if (date != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("user", "pwsetdate"),
new Object[] {date});
}
if (time != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("user", "pwsettime"),
new Object[] {time});
}
// create directory object
return ds.addNode(uid, "user", data);
}
/**
* Creates a OS user account directory object.
*
* @param uid
* object ID for the new directory object
* @param description
* description of the OS user
* @param enabled
* enabled/disabled status of the account
* @param password
* password hash; may be <code>null</code>
* @param date
* date of the last password change; may be <code>null</code>
* @param time
* time of the last password change; may be <code>null</code>
* @param protect
* protect the password
*
* @return <code>true</code> if successful
*/
private boolean createOsUserNode(String uid,
String description,
boolean enabled,
byte[] password,
DateValue date,
TimeValue time)
{
// enumerate present attributes
int na = 1; // for "enabled"
if (description != null)
{
na++;
}
if (password != null)
{
na++;
}
if (date != null)
{
na++;
}
if (time != null)
{
na++;
}
// create the exact size array
Attribute[] data = new Attribute[na];
int i = 0;
data[i++] = new Attribute(ds.getClassNodeAttribute("osuser", "enabled"),
new Object[] {enabled});
if (description != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("osuser", "description"),
new Object[] {description});
}
if (password != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("osuser", "password"),
new Object[] {password});
}
if (date != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("osuser", "pwsetdate"),
new Object[] {date});
}
if (time != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("osuser", "pwsettime"),
new Object[] {time});
}
// create directory object
return ds.addNode(uid, "osuser", data);
}
/**
* Creates a process account directory object.
*
* @param id
* object ID for the new directory object
*
* @param enabled
* enabled/disabled status of the account
*
* @param text
* description
*
* @param alias
* process certificate alias
*
* @param server
* <code>true</code> if this process is a server,
* <code>false</code> if an application
*
* @param master
* <code>true</code> if this server process is a master server,
* <code>false</code> otherwise
*
* @return <code>true</code> if successful
*/
private boolean createProcessNode(String id, boolean enabled, String text,
String alias, boolean server,
boolean master)
{
// enumerate present attributes
int na = 3; // for "enabled", "server" and "master"
if (text != null)
{
na++;
}
if (alias != null)
{
na++;
}
// create the exact size array
Attribute[] data = new Attribute[na];
// stuff the array
int i = 0;
data[i++] = new Attribute(ds.getClassNodeAttribute("process", "enabled"),
new Object[] {enabled});
data[i++] = new Attribute(ds.getClassNodeAttribute("process", "server"),
new Object[] {server});
data[i++] = new Attribute(ds.getClassNodeAttribute("process", "master"),
new Object[] {master});
if (text != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("process", "description"),
new Object[] {text});
}
if (alias != null)
{
data[i++] = new Attribute(
ds.getClassNodeAttribute("process", "alias"),
new Object[] {alias});
}
// create directory object
return ds.addNode(id, "process", data);
}
/**
* Creates a certificate directory object.
*
* @param id
* object ID for the new directory object
*
* @param enc
* certificate encoding
*
* @return <code>true</code> if successful
*/
private boolean createCertNode(String id, byte[] enc)
{
// create directory object
Attribute[] data = new Attribute[]
{
// certificate data: encoding (bytes)
new Attribute(ds.getClassNodeAttribute("bytes", "value"),
new Object[] { enc }),
};
return ds.addNode(id, "bytes", data);
}
/**
* Modifies all ACLs that refer to the given subject so that they no longer
* do. If the subject was the only one in an ACL, the ACL gets removed.
*
* @param name
* subject name to be removed from all ACLs
*
* @return <code>true</code> if successful
*/
private boolean removeSubjectFromACLs(String name)
{
boolean rc = false;
// list all existing ACL instances for all resource types
String[] paths = enumerateACLInstancePaths();
// process ACL instances
for (int i = 0; i < paths.length; i++)
{
// open editing batch for the instance
rc = ds.openBatch(paths[i]);
if (!rc)
{
message("Can't lock directory branch " + paths[i]);
return false;
};
// enumerate resource types for this instance
String[] resources = ds.enumerateNodes(paths[i]);
// process all ACLs for every resource type
allAcls:
for (int j = 0; j < resources.length; j++)
{
// enumerate ACLs
String origin = paths[i] + "/" + resources[j];
String[] acls = ds.enumerateNodes(origin);
// process each ACL node
for (int k = 0; k < acls.length; k++)
{
// path to the ACL container
String acl = origin + "/" + acls[k];
// read existing subjects
String[] subjects = ds.getNodeStrings(acl + "/subjects",
"values");
if (subjects == null || subjects.length == 0)
{
continue;
}
// check if this ACL contains the name
int l = 0;
for (l = 0; l < subjects.length; l++)
{
if (name.equalsIgnoreCase(subjects[l]))
{
break;
}
}
if (l == subjects.length)
{
// name is not part of this ACL
continue;
}
// modify or delete the ACL
if (subjects.length == 1)
{
// the only subject is the name. delete the ACL
rc = Utils.removeDirectoryNode(ds, acl);
if (!rc)
{
message("Can't delete ACL node " + acl);
break allAcls;
};
}
else
{
// delete the name from the subject list, found at index l
rc = ds.deleteNodeAttributeValue(acl + "/subjects", "values",
l);
if (!rc)
{
message("Can't delete attribute value '" + name +
"' from " + acl+ "/subjects");
break allAcls;
};
}
}
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
rc = false;
break;
}
}
return rc;
}
/**
* Modifies all ACLs that refer to the given subject so that they also
* refer to the clone.
*
* @param name
* subject name to be cloned in all ACLs
*
* @param nameTo
* clone name
*
* @return <code>true</code> if successful
*/
private boolean cloneSubjectInACLs(String name, String nameTo)
{
boolean rc = false;
// list all existing ACL instances for all resource types
String[] paths = enumerateACLInstancePaths();
// process ACL instances
for (int i = 0; i < paths.length; i++)
{
// open editing batch for the instance
rc = ds.openBatch(paths[i]);
if (!rc)
{
message("Can't lock directory branch " + paths[i]);
return false;
};
// enumerate resource types for this instance
String[] resources = ds.enumerateNodes(paths[i]);
// process all ACLs for every resource type
allAcls:
for (int j = 0; j < resources.length; j++)
{
// enumerate ACLs
String origin = paths[i] + "/" + resources[j];
String[] acls = ds.enumerateNodes(origin);
// process each ACL node
for (int k = 0; k < acls.length; k++)
{
// path to the ACL container
String acl = origin + "/" + acls[k];
// read existing subjects
String[] subjects = ds.getNodeStrings(acl + "/subjects",
"values");
if (subjects == null || subjects.length == 0)
{
continue;
}
// check if this ACL contains the name
int l = 0;
for (l = 0; l < subjects.length; l++)
{
if (name.equalsIgnoreCase(subjects[l]))
{
break;
}
}
if (l == subjects.length)
{
// name is not part of this ACL
continue;
}
// add the clone name to the ACL
rc = ds.addNodeString(acl + "/subjects", "values", nameTo);
if (!rc)
{
message("Can't add attribute value '" + nameTo +
"' to " + acl+ "/subjects");
break allAcls;
};
}
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(rc, false);
if (!rcc)
{
message("Error committing directory changes.");
rc = false;
break;
}
}
return rc;
}
/**
* Enumerates ACL instances defined in the directory. Instances are
* identified by their directory path. The shared ACL instance has the
* fixed path <code>/security/acl</code>
*
* @return array of instance names, which may be empty
*/
private String[] enumerateACLInstances()
{
// container for the results
List<String> list = new LinkedList<String>();
// check the shared instance first
if (ds.getNodeClass("/security/acl").equals("/meta/class/container"))
{
list.add("");
}
// enumerate private instances
String[] priv = ds.enumerateNodes("/security/acls");
if (priv != null)
{
for (int i = 0; i < priv.length; i++)
{
list.add(priv[i]);
}
}
return list.toArray(new String[]{});
}
/**
* Enumerates ACL instances defined in the directory, that have ACLs
* defined for the specified resource type.
* <p>
* Instances are identified by their directory path. The shared ACL
* instance has the fixed path <code>/security/acl</code>
*
* @return array of instance names, which may be empty
*/
private String[] enumerateACLInstances(String resourceType)
{
// container for the results
List<String> list = new LinkedList<String>();
// check the shared instance first
if (ds.getNodeClass("/security/acl").equals("/meta/class/container"))
{
list.add("");
}
// enumerate private instances
String[] priv = ds.enumerateNodes("/security/acls");
if (priv != null)
{
for (int i = 0; i < priv.length; i++)
{
list.add(priv[i]);
}
}
// filter the list and exclude instances having no resource type
Iterator<String> il = list.iterator();
scanList:
while (il.hasNext())
{
// enumerate resource types for this instance
String instance = il.next();
String node = instance.equals("") ? "/security/acl" :
"/security/acls/" + instance;
String[] rtypes = ds.enumerateNodes(node);
// compare types
if (rtypes != null)
{
for (int i = 0; i < rtypes.length; i++)
{
if (rtypes[i].equals(resourceType))
{
continue scanList;
}
}
}
// no match found; delete this instance from the list
il.remove();
}
return list.toArray(new String[]{});
}
/**
* Enumerates ACL instances defined in the directory. Instances are
* identified by their directory path. The shared ACL instance has the
* fixed path <code>/security/acl</code>
*
* @return array of directory paths, which may be empty
*/
private String[] enumerateACLInstancePaths()
{
String[] names = enumerateACLInstances();
if (names == null)
{
return new String[0];
}
String[] paths = new String[names.length];
for (int i = 0; i < names.length; i++)
{
paths[i] = names[i].length() == 0 ?
"/security/acl" : "/security/acls/" + names[i];
}
return paths;
}
/**
* Enumerates ACLs under the given directory path and combines then into
* the given map.
*
* @param path
* directory branch to process
*
* @param comb
* a map to receive the IDs to object paths mappings
*
* @param badIds
* starting counter of bad IDs that should be incremented
*
* @return new count of bad object IDs
*/
private int enumerateACLs(String path, Map<Integer, String> comb, int badIds)
{
// enumerate children
String[] acls = ds.enumerateNodes(path);
if (acls == null || acls.length == 0)
{
return badIds;
}
// put every ACL into the map
for (int i = 0; i < acls.length; i++)
{
int k = 0;
try
{
// get the numeric ID
k = Integer.parseInt(acls[i]);
}
catch (NumberFormatException e)
{
// assign negative IDs to the misformed oids
badIds++;
k = -badIds;
}
comb.put(k, path + "/" + acls[i]);
}
return badIds;
}
/**
* Creates an ACL definition from the ACLs for the specified resource type.
* Gets the ACLs for the named instance only, if requested. Otherwise
* combines the ACLs of the shared and private instances. Creates the oid
* to the directory object map for all found ACLs.
*
* @param resourceType
* name of the resource of interest
*
*
* @param instanceName
* name of the ACL instance or <code>null</code> meaning the
* effective combination is requested
*
* @param mpath
* mandatory directory path to the resource container; can't be
* <code>null</code>
*
* @param opath
* optional directory path to the resource container; must be
* <code>null</code> if instanceNamwe is given; otherwise must be
* specified
*
* @return ACL definition
*/
private AclDef getAclWorker(String resourceType, String instanceName,
String mpath, String opath)
{
// validate the resource type
int ord = checkResourceType(resourceType);
if (ord == -1)
{
return null;
}
// validate other arguments
if (mpath == null)
{
message("internal error 1 while getting ACLs");
return null;
}
if (instanceName == null && opath == null)
{
message("internal error 2 while getting ACLs");
return null;
}
if (instanceName != null && opath != null)
{
message("internal error 3 while getting ACLs");
return null;
}
// instance of AclDef
AclDef ad = new AclDef();
ad.resourceName = resourceType;
ad.server = instanceName;
ad.subjects = new HashMap<Integer, String>();
ad.errors = 0;
// temporary reverse mapping of subject IDs into integers
Map<String, Integer> rmap = new HashMap<String, Integer>();
// create a combination map: Integer oid to ACL path
SortedMap<Integer, String> amap = new TreeMap<Integer, String>();
// enumerate ACLs under the mandatory path
int badIds = enumerateACLs(mpath, amap, 0);
// enumerate ACLs under the optional path
if (opath != null)
{
badIds = enumerateACLs(opath, amap, badIds);
}
// quick return if nothing to do
int numAcls = amap.size();
if (numAcls == 0)
{
ad.acls = new Acl[] {};
return ad;
}
// there are ACLs
ad.acls = new Acl[numAcls];
// read all ACLs and create records
// processing ordered sequence of ACLs
Set<Integer> set = amap.keySet();
Iterator<Integer> iter = set.iterator();
int i = 0;
while (iter.hasNext())
{
ad.acls[i] = new Acl();
ad.acls[i].errOid = false;
// get the next ACL and parse components
Integer k = iter.next();
String base = amap.get(k);
String oid = base.substring(base.lastIndexOf("/") + 1);
String ori = null;
if (base.startsWith("/security/acl/"))
{
ori = "";
}
else
{
ori = base.substring(15);
ori = ori.substring(0, ori.indexOf("/"));
}
// conditionally create the map for this ACL instance & resource type
mapTypes = mapInst.get(ori);
if (mapTypes == null)
{
mapTypes = new HashMap<String, Map<Integer, String>>();
mapInst.put(ori, mapTypes);
}
mapId = mapTypes.get(resourceType);
if (mapId == null)
{
mapId = new HashMap<Integer, String>();
mapTypes.put(resourceType, mapId);
}
// flag the bad oids
if (k.intValue() < 0)
{
ad.acls[i].errOid = true;
message(String.format(
"ACL number %d in <%s><%s> has bad ID <%s>", k.intValue(),
resourceType, instanceName, oid));
}
// store the numeric oid to node oid mapping
mapId.put(k, oid);
ad.acls[i].origin = instanceName == null ? ori : null;
ad.acls[i].oid = k.intValue();
// read resource info
ad.acls[i].name = ds.getNodeString(base + "/resource-instance",
"reference");
if (ad.acls[i].name == null)
{
ad.acls[i].errName = true;
ad.acls[i].name = "*** error ***";
}
else
{
ad.acls[i].errName = false;
}
Boolean xact = ds.getNodeBoolean(base + "/resource-instance",
"reftype");
if (xact == null)
{
ad.acls[i].errExact = true;
ad.acls[i].exact = true;
}
else
{
ad.acls[i].errExact = false;
ad.acls[i].exact = xact.booleanValue();
}
// read and instantiate rights
Rights rights = sc.createRights(ds, ord, base + "/rights");
if (rights == null)
{
ad.acls[i].errRights = true;
ad.acls[i].rights = null;
}
else
{
ad.acls[i].errRights = false;
ad.acls[i].rights = rights;
}
// read and map subjects
String[] subjs = ds.getNodeStrings(base + "/subjects", "values");
if (subjs == null || subjs.length == 0)
{
ad.acls[i].errSubjs = true;
subjs = new String[] {};
}
else
{
ad.acls[i].errSubjs = false;
}
ad.acls[i].subjects = new int[subjs.length];
for (int j = 0; j < subjs.length; j++)
{
// all_others case
if (subjs[j].equalsIgnoreCase("all_others"))
{
ad.acls[i].subjects[j] = AclDef.ALL_OTHERS;
}
else
{
Integer n = rmap.get(subjs[j]);
if (n == null)
{
n = rmap.size();
rmap.put(subjs[j], n);
ad.subjects.put(n, subjs[j]);
}
ad.acls[i].subjects[j] = n.intValue();
}
}
// keep track of the problematic ACLs
if (ad.acls[i].errOid || ad.acls[i].errName ||
ad.acls[i].errExact || ad.acls[i].errSubjs ||
ad.acls[i].errRights)
{
ad.errors++;
}
i++;
}
if (ad.errors > 0)
{
message(String.format(
"Found %d ACLs with various errors in <%s><%s>", ad.errors,
resourceType, instanceName));
}
return ad;
}
/**
* Calculates the common parent node for two given nodes.
*
* @param snode
* a directory node
*
* @param tnode
* another directory node
*
* @return common parent node, which may be just the root node "/".
*/
private String commonParent(String snode, String tnode)
{
String n1 = snode.toLowerCase();
String n2 = tnode.toLowerCase();
// quick path
if (n1.equals(n2))
{
return n1;
}
// find the common part character-wise
int i = 0;
for (i = 0; i < Math.min(n1.length(), n2.length()); i++)
{
if (n1.charAt(i) != n2.charAt(i))
{
break;
}
}
String cp = n1.substring(0, i);
cp = cp.substring(0, cp.lastIndexOf("/"));
return cp;
}
/**
* Utility method to store server messages for later retrieval by clients.
*
* @param msg
* array of strings that constitutes a message
*/
private void message(String[] msg)
{
AdminServerImpl.message(msg);
}
/**
* Utility method to store server messages for later retrieval by clients.
*
* @param msg
* a single string that constitutes a message
*/
private void message(String msg)
{
AdminServerImpl.message(new String[] {msg});
}
/**
* Synchronizes authentication-related directory node attributes with the
* given values. It means setting or removing the attribute values.
*
* @param node
* Object ID for a directory node.
* @param authMode
* Authentication mode to set.
* @param authPlugin
* Authentication plugin to set.
*
* @return <code>true</code> if successful
*/
private boolean syncAuthSettings(String node,
int authMode,
String authPlugin)
{
// do not keep the auth mode if it is AUTH_MODE_NONE
boolean rc = syncInteger(node, "mode",
authMode != SecurityConstants.AUTH_MODE_NONE ?
authMode :
null);
if (rc)
{
// do not keep the auth plugin if auth mode != AUTH_MODE_CUSTOM
rc = syncString(node, "auth-plugin",
authMode == SecurityConstants.AUTH_MODE_CUSTOM ?
authPlugin:
null);
}
return rc;
}
/**
* Utility method to get the custom server extension interface.
*
* @return the custom server extension interface or <code>null</code>
*/
private AdminAccountExtension getExtension()
{
String customExt = sm.getCustomServerExt();
if (customExt == null)
{
return null;
}
Class<?> cext = null;
try
{
cext = Class.forName(customExt);
}
catch (ClassNotFoundException cnfe)
{
return null;
}
Method mget = null;
try
{
mget = cext.getMethod("getInterface", (Class[])null);
}
catch (Exception e)
{
return null;
}
AdminAccountExtension aae = null;
try
{
aae = (AdminAccountExtension)mget.invoke(null, (Object[])null);
}
catch (Exception e)
{
return null;
}
return aae;
}
/**
* Admin permissions check routine.
*
* @return <code>true</code> if any admin permission is assigned
*/
private boolean checkPermission()
{
boolean perm = false;
if (ar != null)
{
// check the access rights with the admin plugin first
perm = ar.checkAnyAdminAccess();
}
else
{
// fall back to the system resource
perm = sr.checkAdminAccess();
}
if (!perm)
{
message("insufficient access rights for admin");
}
return perm;
}
/**
* Admin permission check routine for a specific resource instance, leaf
* node and permission.
*
* @param name
* resource instance name
*
* @param mode
* access modes (requested permission)
*
* @return <code>true</code> if access granted to any permission
*/
private boolean checkPermissions(String name, int[] mode)
{
boolean perm = false;
if (ar != null)
{
// check the access rights with the admin plugin first
for (int i = 0; i < mode.length; i++)
{
perm |= ar.checkAdminAccess(name, mode[i]) > 0;
if (perm)
{
break;
}
}
}
else
{
// fall back to the system resource
perm = sr.checkAdminAccess();
}
if (!perm)
{
StringBuilder sb = new StringBuilder();
sb.append("insufficient access rights for ");
sb.append(name);
sb.append(" (");
for (int i = 0; i < mode.length; i++)
{
sb.append(Integer.toString(mode[i]));
sb.append(",");
}
sb.setLength(sb.length() -1);
sb.append(" )");
message(sb.toString());
}
return perm;
}
/**
* Admin permission check routine for a specific resource instance, leaf
* node and permission.
*
* @param name
* resource instance name
*
* @param mode
* access mode (requested permission)
*
* @return <code>true</code> if access granted
*/
private boolean checkPermission(String name, int mode)
{
boolean perm = queryPermission(name, mode);
if (!perm)
{
message(String.format("insufficient access rights for %s (%d)",
name, mode));
}
return perm;
}
/**
* Admin permission check routine for a specific resource instance, leaf
* node and permission.
*
* @param name
* resource instance name
*
* @param mode
* access mode (requested permission)
*
* @return <code>true</code> if access granted
*/
private boolean queryPermission(String name, int mode)
{
boolean perm = false;
if (ar != null)
{
// check the access rights with the admin plugin first
perm = ar.checkAdminAccess(name, mode) > 0;
}
else
{
// fall back to the system resource
perm = sr.checkAdminAccess();
}
return perm;
}
/**
* Modifies all ACLs in the specified ACL instance which refer to a given
* subject ID. The modification consists of the deletions and the additions
* of the subject IDs. Both operations are conditional - they don't fail if
* the state is already final.
*
* @param ds
* instance of DirectoryService to use (should be bound)
*
* @param origin
* directory path to the ACLs to process, f.i.
* <code>/security/acl/application-specific-acls</code>
*
* @param name
* subject name which works as a selector; may be
* <code>null</code> if no selection needed
*
* @param namesDel
* array of subject IDs to delete conditionally; may be
* <code>null</code>
*
* @param namesAdd
* array of subject IDs to add conditionally; may be
* <code>null</code>
*
* @return <code>true</code> if successful
*/
private static boolean modifySubjectsInACLs(DirectoryService ds,
String origin,
String name,
String[] namesDel,
String[] namesAdd)
{
boolean rc = false;
// enumerate ACLs
String[] acls = ds.enumerateNodes(origin);
// sort the ACLs
Arrays.sort(acls);
// open editing batch
rc = ds.openBatch(origin);
if (!rc)
{
LOG.severe("Can't lock directory branch");
return false;
};
System.out.println("Processing " + origin);
System.out.print("Selector: ");
if (name == null)
{
System.out.println("none");
}
else
{
System.out.println("<" + name + ">");
}
System.out.print("Deletions: ");
if (namesDel == null)
{
System.out.println("none");
}
else
{
for (int i = 0; i < namesDel.length; i++)
{
System.out.print("<" + namesDel[i] + "> ");
}
System.out.println();
}
System.out.print("Additions: ");
if (namesAdd == null)
{
System.out.println("none");
}
else
{
for (int i = 0; i < namesAdd.length; i++)
{
System.out.print("<" + namesAdd[i] + "> ");
}
System.out.println();
}
System.out.println();
int err = 0;
// process each ACL node
for (int k = 0; k < acls.length; k++)
{
// path to the ACL container
String acl = origin + "/" + acls[k];
// read existing subjects
String[] subjects = ds.getNodeStrings(acl + "/subjects", "values");
if (subjects == null || subjects.length == 0)
{
LOG.finer("Skipping (no subjects)" + acls[k]);
continue;
}
List<String> subjectList = Arrays.asList(subjects);
// check if this ACL contains the selector name
if (name != null)
{
boolean ndx = subjectList.contains(name);
if (!ndx)
{
// name is not part of this ACL
LOG.finer("Skipping " + acls[k]);
continue;
}
}
LOG.finer("Editing " + acls[k]);
String nodeId = acl + "/subjects";
// process deletions
if (namesDel != null)
{
for (int m = 0; m < namesDel.length; m++)
{
int ndx = subjectList.indexOf(namesDel[m]);
if (ndx == -1)
{
System.out.println(" no " + namesDel[m]);
continue;
}
rc = ds.deleteNodeAttributeValue(nodeId, "values", ndx);
if (rc)
{
LOG.finer(" del " + namesDel[m]);
}
else
{
LOG.finer(" err del " + namesDel[m]);
err++;
}
}
}
// process additions
if (namesAdd != null)
{
for (int m = 0; m < namesAdd.length; m++)
{
int ndx = subjectList.indexOf(namesAdd[m]);
if (ndx >= 0)
{
LOG.finer(" has " + namesAdd[m]);
continue;
}
rc = ds.addNodeString(nodeId, "values", namesAdd[m]);
if (rc)
{
LOG.finer(" add " + namesAdd[m]);
}
else
{
LOG.finer(" err add " + namesAdd[m]);
err++;
}
}
}
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(true, false);
if (!rcc)
{
// Error unlocking directory branch
rc = false;
}
return rc;
}
/**
* ACL subjects filtering and editing utility.
*
* @param args
* args[0] - the directory file to process
* args[1] - the path to the ACLs
* args[2] - the selector name in ""
* args[3] - the names being deleted in ""
* args[4] - the names being added in ""
*/
private static void subjects(String[] args)
throws ConfigurationException
{
// process arguments
if (args.length != 5)
{
String[] staticText =
{
"The correct syntax is:",
"",
" java SecurityAdmin subjects dir-file dir-path" +
" quoted-selector quoted-deletions" +
" quoted-additions",
"",
"Use double quotes to specify no selection or more" +
" than one name in the deletions and/or additions",
""
};
LOG.info(StringHelper.arrayToString(staticText));
return;
}
// get the selector
String selector = null;
if (args[2].length() > 0)
{
selector = args[2];
}
// get the deletions
String[] dels = null;
if (args[3].length() > 0)
{
dels = args[3].trim().split("(\\p{Space})+");
if (dels.length == 0)
{
dels = null;
}
}
// get the additions
String[] adds = null;
if (args[4].length() > 0)
{
adds = args[4].trim().split("(\\p{Space})+");
if (adds.length == 0)
{
adds = null;
}
}
// Empty BootstrapConfig
BootstrapConfig bc = new BootstrapConfig(null, null, null, null);
// directory package configuration
bc.setServer(true);
bc.setConfigItem("directory", "backend", "type", "xml");
bc.setConfigItem("directory", "xml", "filename", args[0]);
DirectoryService ds = DirectoryService.createInstance(bc);
boolean bound = ds.bind();
if (!bound)
{
LOG.severe("Can't bind to the directory");
return;
}
LOG.info("Bound to " + args[0]);
// this utility changes the application-specific-acls ACLs
// - deletes references to "batch" in subjects
boolean rc = modifySubjectsInACLs(ds, args[1], selector, dels, adds);
if (rc)
{
LOG.info("Success.");
}
else
{
LOG.info("There were some errors.");
}
ds.unbind();
}
/**
* ACL migration utility.
*
* @param args
* args[0] - the directory file to process
* args[1] - the path to the ACLs
* args[2] - the class name for the resource plugin
*/
private static void migrate(String[] args)
throws ConfigurationException,
NoSuchMethodException
{
// process arguments
if (args.length != 3)
{
LOG.info("The correct syntax is:");
LOG.info("");
LOG.info(" java SecurityAdmin migrate dir-file dir-path resource-plugin");
LOG.info("");
return;
}
// Empty BootstrapConfig
BootstrapConfig bc = new BootstrapConfig(null, null, null, null);
// directory package configuration
bc.setServer(true);
bc.setConfigItem("directory", "backend", "type", "xml");
bc.setConfigItem("directory", "xml", "filename", args[0]);
DirectoryService ds = DirectoryService.createInstance(bc);
boolean bound = ds.bind();
if (!bound)
{
LOG.info("Can't bind to the directory");
return;
}
LOG.info("Bound to " + args[0]);
// mimic a server environment with SecurityManager and SecurityCache
// all accounts are read from the directory file
bc.setServer(false);
SecurityManager.createInstance(bc);
List<String[]> msgBuf = new LinkedList<String[]>();
SecurityCache sc = new SecurityCache(SecurityCache.LM_DUAL, msgBuf,
new String[] {args[2]});
printMessages(msgBuf);
msgBuf = null;
bc.setServer(true);
// this utility migrates the old style ACLs
String[] seqno = ds.enumerateNodes(args[1]);
if (seqno == null)
{
LOG.info("No input. Either the directory or the path is invalid");
ds.unbind();
return;
}
int ord = 0;
boolean rc = sc.migrateACLs(ds, args[1], seqno, ord);
if (rc)
{
LOG.info("Success.");
}
else
{
LOG.info("There were some errors.");
}
ds.unbind();
}
/**
* ACL subjects list verification utility.
*
* @param args
* args[0] - the directory file to process
* args[1] - the path to the ACLs
* args[2] - the class name for the resource plugin
*/
private static void johndoe(String[] args)
throws ConfigurationException,
NoSuchMethodException
{
// process arguments
if (args.length != 3)
{
LOG.info("The correct syntax is:");
LOG.info("");
LOG.info(" java SecurityAdmin johndoe dir-file dir-path resource-plugin");
LOG.info("");
return;
}
// Empty BootstrapConfig
BootstrapConfig bc = new BootstrapConfig(null, null, null, null);
// directory package configuration
bc.setServer(true);
bc.setConfigItem("directory", "backend", "type", "xml");
bc.setConfigItem("directory", "xml", "filename", args[0]);
DirectoryService ds = DirectoryService.createInstance(bc);
boolean bound = ds.bind();
if (!bound)
{
LOG.info("Can't bind to the directory");
return;
}
LOG.info("Bound to " + args[0]);
// mimic a server environment with SecurityManager and SecurityCache
// all accounts are read from the directory file
bc.setServer(false);
SecurityManager.createInstance(bc);
List<String[]> msgBuf = new LinkedList<String[]>();
SecurityCache sc = new SecurityCache(SecurityCache.LM_DUAL, msgBuf,
new String[] {args[2]});
printMessages(msgBuf);
msgBuf = null;
bc.setServer(true);
// enumerate all ACLs
String[] seqno = ds.enumerateNodes(args[1]);
if (seqno == null)
{
LOG.info("No input. Either the directory or the path is invalid");
ds.unbind();
return;
}
Arrays.sort(seqno);
int errors = 0;
// loop over all ACLs
for (int i = 0; i < seqno.length; i++)
{
String aclnode = args[1] + "/" + seqno[i];
String node = aclnode + "/subjects";
if (ds.getNodeClass(node) == null)
{
LOG.info("node " + aclnode + " has no subjects subnode");
errors++;
continue;
}
String[] subjs = ds.getNodeStrings(node, "values");
if (subjs == null || subjs.length == 0)
{
LOG.info("node " + aclnode + " has empty subjects list");
errors++;
continue;
}
for (int n = 0; n < subjs.length; n++)
{
if (subjs[n].equalsIgnoreCase("all_others"))
{
if (subjs.length != 1)
{
LOG.info("node " + node + " refers to all_others mixed with " +
"accounts (warning)");
}
continue;
}
if (sc.getAccountById(subjs[n]) == null)
{
LOG.info("node " + node + " refers to invalid account " + subjs[n]);
errors++;
continue;
}
}
LOG.info("node " + node + " ok");
}
if (errors == 0)
{
LOG.info("Success.");
}
else
{
LOG.info(String.format("Found %d errors.", errors));
}
ds.unbind();
}
/**
* ACL resource name verification utility.
*
* @param args
* args[0] - the directory file to process
* args[1] - the path to the ACLs
* args[2] - the class name for the resource plugin
*/
private static void resource(String[] args)
throws ConfigurationException,
NoSuchMethodException
{
// process arguments
if (args.length != 3)
{
LOG.info("The correct syntax is:");
LOG.info("");
LOG.info(" java SecurityAdmin resource dir-file dir-path resource-plugin");
LOG.info("");
return;
}
// Empty BootstrapConfig
BootstrapConfig bc = new BootstrapConfig(null, null, null, null);
// directory package configuration
bc.setServer(true);
bc.setConfigItem("directory", "backend", "type", "xml");
bc.setConfigItem("directory", "xml", "filename", args[0]);
DirectoryService ds = DirectoryService.createInstance(bc);
boolean bound = ds.bind();
if (!bound)
{
LOG.info("Can't bind to the directory");
return;
}
LOG.info("Bound to " + args[0]);
// mimic a server environment with SecurityManager and SecurityCache
// all accounts are read from the directory file
bc.setServer(false);
SecurityManager.createInstance(bc);
List<String[]> msgBuf = new LinkedList<String[]>();
SecurityCache sc = new SecurityCache(SecurityCache.LM_DUAL, msgBuf,
new String[] {args[2]});
printMessages(msgBuf);
msgBuf = null;
bc.setServer(true);
// enumerate all ACLs
String[] seqno = ds.enumerateNodes(args[1]);
if (seqno == null)
{
LOG.info("No input. Either the directory or the path is invalid");
ds.unbind();
return;
}
Arrays.sort(seqno);
int ord = 0;
// locate the plugin responsible for this resource type
AbstractResource plugin = sc.getRegistryByOrd(ord).getPlugin();
int errors = 0;
// loop over all ACLs
for (int i = 0; i < seqno.length; i++)
{
String aclnode = args[1] + "/" + seqno[i];
String node = aclnode + "/resource-instance";
if (ds.getNodeClass(node) == null)
{
LOG.info("node " + aclnode + " has no resource name");
errors++;
continue;
}
String reference = ds.getNodeString(node, "reference");
Boolean reftype = ds.getNodeBoolean(node, "reftype");
if (reference == null)
{
LOG.info("node " + aclnode + " has no resource reference");
errors++;
continue;
}
if (reftype == null)
{
LOG.info("node " + aclnode + " has no resource reference type");
errors++;
continue;
}
// for exact match instances, verify the instance name
if (reftype.booleanValue())
{
if (!plugin.isInstanceNameValid(reference))
{
LOG.info("node " + aclnode + " refers to invalid resource name <" +
reference + ">");
errors++;
continue;
}
}
LOG.info("node " + node + " ok");
}
if (errors == 0)
{
LOG.info("Success.");
}
else
{
LOG.info(String.format("Found %d errors.", errors));
}
ds.unbind();
}
/**
* Modifies all ACLs in the specified ACL instance which refer to a given
* subject ID. The modification consists of the deletions and the additions
* of the subject IDs. Both operations are conditional - they don't fail if
* the state is already final.
*
* @param ds
* instance of DirectoryService to use (should be bound)
*
* @param attrName
* attribute name to take the plain text password from
*
* @return <code>true</code> if successful
*/
private static boolean encodeWorker(DirectoryService ds, String attrName)
{
boolean rc = false;
// enumerate ACLs
String origin = "/security/accounts/users";
String[] accs = ds.enumerateNodes(origin);
// sort the ACLs
Arrays.sort(accs);
// open editing batch
rc = ds.openBatch(origin);
if (!rc)
{
LOG.warning("Can't lock directory branch");
return false;
};
LOG.info("Processing " + origin);
LOG.info("Password input: ");
LOG.info("<" + attrName + ">");
int err = 0;
// process each user account
for (int k = 0; k < accs.length; k++)
{
// path to the account
String acc = origin + "/" + accs[k];
// read plain password
String pw = ds.getNodeString(acc, attrName);
if (pw == null || pw.length() == 0)
{
LOG.info("Skipping (no password)" + accs[k]);
continue;
}
// encoding password
byte[] hash = HashPassword.hashPassword(pw);
// add or set the password hash
LOG.info("Editing " + accs[k]);
if (ds.getNodeByteArray(acc, "password") == null)
{
rc = ds.addNodeByteArray(acc, "password", hash);
if (rc)
{
LOG.finer(" add");
}
else
{
LOG.finer(" err add");
err++;
}
}
else
{
rc = ds.setNodeByteArray(acc, "password", 0, hash);
if (rc)
{
LOG.finer(" set");
}
else
{
LOG.finer(" err set");
err++;
}
}
}
// close batch with commit or rollback and no refresh
boolean rcc = ds.closeBatch(true, false);
if (!rcc)
{
// Error unlocking directory branch
rc = false;
}
return rc;
}
/**
* User password encoding utility.
*
* @param args
* args[0] - the directory file to process
* args[1] - the attribute name to take plain password from
*/
private static void encode(String[] args)
throws ConfigurationException
{
// process arguments
if (args.length != 2)
{
LOG.info("The correct syntax is:");
LOG.info("");
LOG.info(" java SecurityAdmin encode dir-file attr-name");
LOG.info("");
return;
}
// Empty BootstrapConfig
BootstrapConfig bc = new BootstrapConfig(null, null, null, null);
// directory package configuration
bc.setServer(true);
bc.setConfigItem("directory", "backend", "type", "xml");
bc.setConfigItem("directory", "xml", "filename", args[0]);
DirectoryService ds = DirectoryService.createInstance(bc);
boolean bound = ds.bind();
if (!bound)
{
LOG.severe("Can't bind to the directory");
return;
}
LOG.info("Bound to " + args[0]);
// this utility encodes passwords
boolean rc = encodeWorker(ds, args[1]);
if (rc)
{
LOG.info("Success.");
}
else
{
LOG.info("There were some errors.");
}
ds.unbind();
}
/**
* Message buffer printout
*
* @param msgBuf
* Message buffer after SecurityCache
*/
private static void printMessages(List<String[]> msgBuf)
{
msgBuf.stream()
.flatMap(Arrays::stream)
.forEachOrdered(LOG::info);
}
/**
* Application entry point. Since more than one operation cab be performed,
* the operation has to be specified with the first argument.
*
* @param args
* Command line parameters. The first one specifies the utility
* and the rest is utility specific. The currently implemented
* utilities are:
* <ul>
* <li>subjects - filters the ACLs by a subject and edits the
* list of subjects with deletions and additions
* <li>migrate - migrates the ACLs from the old to the new style
* <li>johndoe - verifies the accounts validity in ACLs
* <li>resource - verifies the resource names in ACLs
* <li>encode - encodes passwords in user accounts
* </ul>
*/
public static void main(String[] args)
{
// inspect the first argument and identify the operation
int argn = args.length;
int oper = 0;
boolean fail = false;
if (argn == 0)
{
fail = true;
}
else
{
if (args[0].equalsIgnoreCase("subjects"))
{
oper = 1;
}
else if (args[0].equalsIgnoreCase("migrate"))
{
oper = 2;
}
else if (args[0].equalsIgnoreCase("johndoe"))
{
oper = 3;
}
else if (args[0].equalsIgnoreCase("resource"))
{
oper = 4;
}
else if (args[0].equalsIgnoreCase("encode"))
{
oper = 5;
}
else
{
fail = true;
}
}
if (fail)
{
String[] help =
{
"A valid operation should be specified",
"Valid operations are:",
" subjects",
" migrate",
" jondoe",
" resource",
" encode",
"Type <operation ?> for more help"
};
LOG.info(StringHelper.arrayToString(help));
System.exit(1);
}
// prepare the parameters for the operation
argn--;
String[] pars = new String[argn];
System.arraycopy(args, 1, pars, 0, argn);
// invoke the utility
try
{
switch(oper)
{
case 1:
subjects(pars);
break;
case 2:
migrate(pars);
break;
case 3:
johndoe(pars);
break;
case 4:
resource(pars);
break;
case 5:
encode(pars);
break;
}
}
catch (Exception e)
{
LOG.severe("", e);
}
}
}