/*
DBPermissionManager.java
Contains the permissions management logic for the Ganymede Server.
Created: 18 April 2012
Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu, ARL:UT
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2014
The University of Texas at Austin
Ganymede is a registered trademark of The University of Texas at Austin
Contact information
Web site: http://www.arlut.utexas.edu/gash2
Author Email: ganymede_author@arlut.utexas.edu
Email mailing list: ganymede@arlut.utexas.edu
US Mail:
Computer Science Division
Applied Research Laboratories
The University of Texas at Austin
PO Box 8029, Austin TX 78713-8029
Telephone: (512) 835-3200
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package arlut.csd.ganymede.server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import arlut.csd.Util.TranslationService;
import arlut.csd.Util.VectorUtils;
import arlut.csd.ganymede.common.BaseListTransport;
import arlut.csd.ganymede.common.CategoryTransport;
import arlut.csd.ganymede.common.Invid;
import arlut.csd.ganymede.common.NotLoggedInException;
import arlut.csd.ganymede.common.ObjectHandle;
import arlut.csd.ganymede.common.PermEntry;
import arlut.csd.ganymede.common.PermMatrix;
import arlut.csd.ganymede.common.Query;
import arlut.csd.ganymede.common.QueryResult;
import arlut.csd.ganymede.common.ReturnVal;
import arlut.csd.ganymede.common.SchemaConstants;
/*------------------------------------------------------------------------------
class
DBPermissionManager
------------------------------------------------------------------------------*/
/**
* <p>Permissions manager for the Ganymede Server.</p>
*
* <p>Each GanymedeSession logged into the Ganymede Server will have
* its own DBPermissionManager attached, which does permission
* management for it.</p>
*
* @author Jonathan Abbey, jonabbey@arlut.utexas.edu, ARL:UT
*/
public final class DBPermissionManager {
/**
* TranslationService object for handling string localization in
* the Ganymede server.
*/
static final TranslationService ts =
TranslationService.getTranslationService("arlut.csd.ganymede.server.DBPermissionManager");
/**
* Invid for the supergash Owner Group Object
*/
static final Invid SUPERGASH_GROUP_INVID =
Invid.createInvid(SchemaConstants.OwnerBase,
SchemaConstants.OwnerSupergash);
/**
* Invid for the supergash Persona Object
*/
static final Invid SUPERGASH_PERSONA_INVID =
Invid.createInvid(SchemaConstants.PersonaBase,
SchemaConstants.PersonaSupergashObj);
/**
* Invid for the default Role Object
*/
static final Invid DEFAULT_ROLE_INVID =
Invid.createInvid(SchemaConstants.RoleBase,
SchemaConstants.RoleDefaultObj);
// ---
/**
* The GanymedeSession that this DBPermissionManager is connected to.
*/
final private GanymedeSession gSession;
/**
* The DBSession that lays under gSession.
*/
final private DBSession dbSession;
/**
* GanymedeSessions created for internal operations always operate
* with supergash privileges. We'll set this flag to true to avoid
* having to do persona membership checks on initial set-up.
*/
final private boolean beforeversupergash; // Be Forever Yamamoto
/**
* The name that the session is given. Must be non-null and unique
* among logged in sessions on the server.
*/
final private String sessionName;
/**
* The object reference identifier for the logged in user, if
* any. If the client logged in directly to a non user-linked
* persona account (e.g., supergash, monitor), this will be null.
* See personaInvid in that case.
*/
final private Invid userInvid;
/**
* <p>The name of the user logged in.</p>
*
* <p>May be null if the containing GanymedeSession is created by an
* internal Ganymede task or process.</p>
*/
final private String username;
// --
/**
* <p>True if the gSession currently has supergash privileges.</p>
*
* <p>May change if {@link
* arlut.csd.ganymede.server.DBPermissionManager#selectPersona(String,
* String} is called.</p>
*/
private boolean supergashMode = false;
/**
* False if we've detected that the underlying user or admin persona
* for this DBPermissionManager has been deleted out from under us.
*/
private boolean valid = true;
/**
* <p>The name of the current persona, of the form
* '<username>:<description>', for example,
* 'broccol:GASH Admin'. If the user is logged in with just
* end-user privileges, personaName will be null.</p>
*
* <p>May change if {@link
* arlut.csd.ganymede.server.DBPermissionManager#selectPersona(String,
* String} is called.</p>
*/
private String personaName;
/**
* <p>The object reference identifier for the current persona, if
* any.</p>
*
* <p>May change if {@link
* arlut.csd.ganymede.server.DBPermissionManager#selectPersona(String,
* String} is called.</p>
*/
private Invid personaInvid;
/**
* <p>A reference to our current persona object. We save this so we
* can look up owner groups and what not more quickly. An end-user
* logged in without any extra privileges will have a null
* personaObj value.</p>
*
* <p>May change if {@link
* arlut.csd.ganymede.server.DBPermissionManager#selectPersona(String,
* String} is called.</p>
*/
private DBObject personaObj;
/**
* When did we last check our persona permissions?
*/
private Date personaTimeStamp;
/**
* <p>This variable stores the permission bits that are applicable to
* objects that the current persona has ownership privilege over.
* This matrix is always a permissive superset of {@link
* arlut.csd.ganymede.server.DBPermissionManager#unownedObjectPerms
* unownedObjectPerms}.</p>
*
* <p>May change if {@link
* arlut.csd.ganymede.server.DBPermissionManager#selectPersona(String,
* String} is called or if the relevant Role Objects are changed in
* the database.</p>
*
* <p>If this DBPermissionManager has supergash privileges, this
* PermMatrix will be null.</p>
*/
private PermMatrix ownedObjectPerms;
/**
* <p>This variable stores the permission bits that are applicable
* to generic objects not specifically owned by this persona.</p>
*
* <p>May change if {@link
* arlut.csd.ganymede.server.DBPermissionManager#selectPersona(String,
* String} is called or if the relevant Role Objects are changed in
* the database.</p>
*
* <p>If this DBPermissionManager has supergash privileges, this
* PermMatrix will be null.</p>
*/
private PermMatrix unownedObjectPerms;
/**
* <p>This variable stores the permission bits that are applicable
* to objects that the current persona has ownership privilege over
* and which the current admin has permission to delegate to
* subordinate roles. This matrix is always a permissive superset
* of {@link
* arlut.csd.ganymede.server.DBPermissionManager#delegatableUnownedObjectPerms
* delegatableUnownedObjectPerms}.</p>
*
* <p>May change if {@link
* arlut.csd.ganymede.server.DBPermissionManager#selectPersona(String,
* String} is called or if the relevant Role Objects are changed in
* the database.</p>
*
* <p>Used by code in {@link
* arlut.csd.ganymede.server.PermissionMatrixDBField} to control
* what privileges personae are able to grant to new personae.</p>
*
* <p>If this DBPermissionManager has supergash privileges, this
* PermMatrix will be null.</p>
*/
private PermMatrix delegatableOwnedObjectPerms;
/**
* <p>This variable stores the permission bits that are applicable to
* generic objects not specifically owned by this persona and which
* the current admin has permission to delegate to subordinate
* roles.</p>
*
* <p>May change if {@link
* arlut.csd.ganymede.server.DBPermissionManager#selectPersona(String,
* String} is called or if the relevant Role Objects are changed in
* the database.</p>
*
* <p>Used by code in {@link
* arlut.csd.ganymede.server.PermissionMatrixDBField} to control
* what privileges personae are able to grant to new personae.</p>
*
* <p>If this DBPermissionManager has supergash privileges, this
* PermMatrix will be null.</p>
*/
private PermMatrix delegatableUnownedObjectPerms;
/**
* <p>A reference to the checked-in Ganymede {@link
* arlut.csd.ganymede.server.DBObject DBObject} storing our default
* permissions, or the permissions that applies when we are not in
* supergash mode and we do not have any ownership over the object
* in question.</p>
*
* <p>May change if the relevant Role Object is changed in the
* database.</p>
*/
private DBObject defaultRoleObj;
/**
* When did we last notice a change in any Role Objects?
*/
private Date rolesLastCheckedTimeStamp;
/**
* When did we last check our User Object?
*/
private Date userTimeStamp;
/**
* <p>This variable is a non-modifiable List of object references
* ({@link arlut.csd.ganymede.common.Invid Invids}) to the owner
* groups that the client has requested newly created objects be
* placed in. While this List is not-null, any new objects created
* will be owned by the list of ownergroups held here.</p>
*
* <p>May change if {@link
* arlut.csd.ganymede.server.DBPermissionManager#setDefaultOwner(java.util.List)}
* is called.</p>
*/
private List<Invid> newObjectOwnerInvids;
/**
* <p>This variable is an unmodifiable List of object references
* ({@link arlut.csd.ganymede.common.Invid Invids}) to the owner
* groups that the client has requested the listing of objects be
* restricted to. That is, the client has requested that the
* results of Queries and Dumps only include those objects owned by
* owner groups in this list. This feature is used primarily for
* when a client is logged in with supergash privileges, but the
* user wants to restrict the visibility of objects for
* convenience.</p>
*
* <p>May change if {@link
* arlut.csd.ganymede.server.DBPermissionManager#filterQueries(java.util.List)}
* is called.</p>
*/
private List<Invid> visibilityFilterInvids;
/* -- */
/**
* Constructor for a privileged internal session
*
* @param gSession The GanymedeSession that we are managing
* permissions for.
*
* @param sessionName The name of this session, used for identifying
* the task or server component that is using our GanymedeSession to
* perform work in the server. Must be unique among logged-in
* sessions on the server and may not be null.
*/
public DBPermissionManager(GanymedeSession gSession, String sessionName)
{
if (gSession == null)
{
throw new IllegalArgumentException("gSession must be non-null");
}
if (sessionName == null)
{
throw new IllegalArgumentException("sessionName may not be null");
}
this.gSession = gSession;
this.dbSession = gSession.getDBSession();
this.sessionName = sessionName;
this.beforeversupergash = true;
this.supergashMode = true;
this.username = null;
this.userInvid = null;
this.personaInvid = null;
this.personaName = null;
}
/**
* Constructor for a logged-in user
*
* @param gSession The GanymedeSession that we are managing
* permissions for.
*
* @param userObject A DBObject describing the user logged in, or
* null if the user is logging in with a non-user-linked persona
* object (supergash, monitor, etc.)
*
* @param personaObject A DBObject describing the Admin Persona
* logged in. May be null if the user is logged in only with his
* unprivileged end-user account.
*
* @param sessionName The name of this session, used for
* identifying the task or server component that is using our
* GanymedeSession to perform work in the server. Must be unique
* among logged-in sessions in the server and may not be null.
*/
public DBPermissionManager(GanymedeSession gSession,
DBObject userObject,
DBObject personaObject,
String sessionName)
{
if (gSession == null)
{
throw new IllegalArgumentException("gSession must be non-null");
}
if (sessionName == null)
{
throw new IllegalArgumentException("sessionLabel may not be null");
}
if (userObject == null && personaObject == null)
{
throw new IllegalArgumentException("userObject or personaObject must be non-null");
}
this.gSession = gSession;
this.dbSession = gSession.getDBSession();
if (personaObject != null &&
personaObject.getInvid().equals(SUPERGASH_PERSONA_INVID))
{
this.beforeversupergash = true;
this.supergashMode = true;
}
else
{
this.beforeversupergash = false;
this.supergashMode = false;
}
this.sessionName = sessionName;
if (userObject != null)
{
this.userInvid = userObject.getInvid();
this.username = userObject.getLabel();
if (this.userInvid == null || this.username == null)
{
throw new NullPointerException();
}
if ("".equals(this.username.trim()))
{
throw new IllegalArgumentException("empty username");
}
}
else
{
this.userInvid = null;
this.username = personaObject.getLabel();
}
if (personaObject != null)
{
this.personaInvid = personaObject.getInvid();
this.personaName = personaObject.getLabel();
if (this.personaInvid == null || this.personaName == null)
{
throw new NullPointerException();
}
if ("".equals(this.personaName.trim()))
{
throw new IllegalArgumentException("empty personaName");
}
}
else
{
this.personaInvid = null;
this.personaName = null;
}
try
{
updatePerms();
}
catch (NotLoggedInException ex)
{
throw new IllegalStateException(ex);
}
}
/**
* Returns true if the session is operating with unrestricted 'root'
* level privileges.
*/
public synchronized boolean isSuperGash()
{
return this.supergashMode;
}
/**
* Returns true if the session has any kind of privileges beyond the
* default end-user privileges.
*/
public synchronized boolean isPrivileged()
{
return personaInvid != null || isSuperGash();
}
/**
* Returns true if the session is either an end-user user or an
* end-user user using a persona.
*/
public boolean isUserLinked()
{
return userInvid != null;
}
/**
* Returns true if the session is operating solely with unprivileged
* end-users privileges.
*/
public boolean isEndUser()
{
return !isSuperGash() && userInvid != null && personaInvid == null;
}
/**
* This method returns the name of the user that is logged into this
* session, or null if this session was created for supergash,
* monitor, or a Ganymede server task or other internal process.
*/
public String getUserName()
{
return username;
}
/**
* <p>Convenience method to get access to this session's user
* invid.</p>
*
* <p>May be null if supergash, monitor, or a Ganymede server task
* or internal process is running the session.</p>
*/
public Invid getUserInvid()
{
return userInvid;
}
/**
* <p>Convenience method to get access to this session's User
* Object.</p>
*
* <p>May be null if supergash, monitor, or Ganymede server task or
* internal process is running the session.</p>
*/
synchronized DBObject getUser()
{
if (userInvid != null)
{
// using dbSession to skip perms checking
DBObject userObject = dbSession.viewDBObject(userInvid);
if (userObject == null)
{
return null;
}
return userObject.getOriginal();
}
return null;
}
/**
* This method returns the name of the persona who is active. May
* be null or empty if we have an end-user who is logged in with no
* elevated persona privileges.
*/
public synchronized String getPersonaName()
{
return personaName;
}
/**
* Convenience method to get access to this session's persona invid.
*/
public synchronized Invid getPersonaInvid()
{
return personaInvid;
}
/**
* This method gives access to the DBObject for the administrator's
* persona record, if any.
*/
synchronized DBObject getPersona()
{
return personaObj;
}
/**
* <p>Returns the session name assigned to the GanymedeSession that
* owns us. Must be unique among all logged in sessions.</p>
*
* <p>getSessionName() will never return a null value.</p>
*/
public String getSessionName()
{
return sessionName;
}
/**
* This method returns the name of the user who is active, or the
* name of the internal Ganymede task or process that is running the
* session if no user is attached to this session.
*/
public synchronized String getBaseIdentity()
{
if (username != null)
{
return username;
}
return sessionName;
}
/**
* This method returns the name of the persona who is active, the
* raw user name if no persona privileges have been assumed, or the
* name of the internal Ganymede task or process that is running the
* session if no user is attached to this session.
*/
public synchronized String getIdentity()
{
if (personaName != null)
{
return personaName;
}
return getBaseIdentity();
}
/**
* <p>This method returns the Invid of the user who logged in, or
* the non-user-linked persona (supergash, monitor) if there was no
* underlying user attached to the persona.</p>
*
* <p>May return null if this session is being run by a Ganymede
* server task or internal process.</p>
*/
public synchronized Invid getIdentityInvid()
{
if (userInvid != null)
{
return userInvid;
}
return personaInvid;
}
/**
* <p>Returns an unmodifiable List of Invids containing user and
* persona Invids for the GanymedeSession that this
* DBPermissionManager is attached to.</p>
*
* <p>May return an empty List if this session is being run by a
* Ganymede server task or internal process.</p>
*/
public synchronized List<Invid> getIdentityInvids()
{
List<Invid> ids = new ArrayList<Invid>();
if (userInvid != null)
{
ids.add(userInvid);
}
if (personaInvid != null)
{
ids.add(personaInvid);
}
return Collections.unmodifiableList(ids);
}
/**
* Returns the email address that should be used in the 'From:'
* field of mail sent by the GanymedeSession which owns this
* DBPermissionManager.
*/
public synchronized String getReturnAddress()
{
if (!isUserLinked())
{
return Ganymede.returnaddrProperty;
}
String mailsuffix = System.getProperty("ganymede.defaultmailsuffix");
if (mailsuffix != null)
{
if (mailsuffix.contains("@"))
{
return username + mailsuffix;
}
else
{
return username + "@" + mailsuffix;
}
}
return username;
}
/**
* Returns the Invid of the admin persona (or user, if running with
* unelevated privileges) who is responsible for actions taken by
* the containing GanymedeSession.
*/
public synchronized Invid getResponsibleInvid()
{
if (personaInvid != null)
{
return personaInvid;
}
return userInvid;
}
/**
* <p>Returns a successful ReturnVal if the user / persona
* credentials for this session are currently valid.</p>
*
* <p>If the user or persona connected to this session have been
* deleted by another session, we'll return an error dialog
* explaining that.</p>
*
* @return null if the session is valid, else a ReturnVal with an
* error dialog encoded.
*/
public synchronized ReturnVal isValidSession()
{
if (this.beforeversupergash)
{
return null;
}
if (isEndUser() && getUser() == null)
{
// "Session Invalidated"
// "User object {0} deleted while logged in with session {1}"
return Ganymede.createErrorDialog(gSession,
ts.l("isValidSession.error"),
ts.l("isValidSession.user_deleted",
this.username,
this.sessionName));
}
if (this.personaInvid != null &&
dbSession.viewDBObject(this.personaInvid) == null)
{
// "Session Invalidated"
// "Persona object {0} deleted while logged in with session {1}"
return Ganymede.createErrorDialog(gSession,
ts.l("isValidSession.error"),
ts.l("isValidSession.persona_deleted",
this.personaName,
this.sessionName));
}
return null;
}
/**
* Returns a serializable Vector of personae names available to the
* user logged in, or null if we're logged in as a non user-linked
* supergash persona.
*/
public synchronized Vector<String> getAvailablePersonae()
{
DBObject u = getUser();
if (u == null)
{
return null;
}
Vector<String> results = new Vector<String>();
Vector personae = u.getFieldValuesLocal(SchemaConstants.UserAdminPersonae);
for (Invid invid: (List<Invid>) personae)
{
try
{
results.add(dbSession.getCommittedObjectLabel(invid));
}
catch (NullPointerException ex)
{
}
}
results.add(u.getLabel()); // add their 'end-user' persona
return results;
}
public synchronized PermMatrix getOwnedObjectPerms()
{
return ownedObjectPerms;
}
public synchronized PermMatrix getDefaultPerms()
{
return unownedObjectPerms;
}
public synchronized PermMatrix getDelegatableOwnedObjectPerms()
{
return delegatableOwnedObjectPerms;
}
public synchronized PermMatrix getDelegatableUnownedObjectPerms()
{
return delegatableUnownedObjectPerms;
}
/**
* <p>This method is used to select an admin persona linked to the
* end-user connected to the linked GanymedeSession, or the
* unprivileged end user itself.</p>
*
* <p>If the persona is successfully changed, the linked
* GanymedeSession's transaction will be aborted and re-started with
* recalculated permissions.</p>
*
* @param label The canonical label of the persona (or end user) to change to
* @param password The password for the persona to change to. May
* be null if label is the user's name.
*
* @return true if the persona could be changed
*/
public synchronized boolean selectPersona(String label, String password)
{
DBObject userObject = getUser();
if (userObject == null)
{
return false;
}
if (!findPersona(userObject, label, password))
{
// "Failed attempt to switch to persona {0} for user: {1}"
Ganymede.debug(ts.l("selectPersona.no_persona", label, this.username));
return false;
}
// "User {0} switched to persona {1}."
Ganymede.debug(ts.l("selectPersona.switched", this.username, label));
gSession.restartTransaction();
this.visibilityFilterInvids = null;
this.ownedObjectPerms = null;
this.unownedObjectPerms = null;
this.delegatableOwnedObjectPerms = null;
this.delegatableUnownedObjectPerms = null;
this.personaTimeStamp = null; // force updatePerms()
try
{
updatePerms();
}
catch (NotLoggedInException ex)
{
return false;
}
gSession.resetAdminEntry();
gSession.setLastEvent("selectPersona: " + label);
return true;
}
/**
* Sets this.personaName and this.personaInvid and returns true if a
* persona object with name matching label and password matching
* pass linked to the object user can be found, or if the name of
* the object user matches label.
*
* @param user The DBObject containing information about the user
* looking to change his persona
* @param label The name of the persona he is attempting to change to
* @param pass The password that he is using to try to change his
* persona, or null if the user is attempting to change to his
* unprivileged end-user privs.
*/
private boolean findPersona(DBObject user, String label, String pass)
{
if (user == null || label == null)
{
return false;
}
// we don't need to check a password to switch to our end-user
// privs
if (user.getLabel().equals(label))
{
this.personaInvid = null;
this.personaName = null;
this.personaObj = null;
return true;
}
if (pass == null)
{
return false;
}
List personae = user.getFieldValuesLocal(SchemaConstants.UserAdminPersonae);
for (Invid invid: (List<Invid>) personae)
{
DBObject persona = dbSession.viewDBObject(invid).getOriginal();
if (!label.equals(persona.getLabel()))
{
continue;
}
PasswordDBField pdbf =
persona.getPassField(SchemaConstants.PersonaPasswordField);
if (pdbf != null && pdbf.matchPlainText(pass))
{
if (persona.getLabel() == null || persona.getInvid() == null)
{
throw new NullPointerException();
}
this.personaName = persona.getLabel();
this.personaInvid = persona.getInvid();
this.personaObj = persona;
return true;
}
}
return false;
}
/**
* This method returns a QueryResult of owner groups that the
* current persona has access to. This list is the transitive
* closure of the list of owner groups in the current persona. That
* is, the list includes all the owner groups in the current persona
* along with all of the owner groups those owner groups own, and so
* on.
*/
public synchronized QueryResult getAvailableOwnerGroups()
{
QueryResult result = new QueryResult();
QueryResult fullOwnerList;
/* -- */
if (!isPrivileged())
{
return result;
}
try
{
Query q = new Query(SchemaConstants.OwnerBase);
q.setFiltered(false);
fullOwnerList = gSession.query(q);
}
catch (NotLoggedInException ex)
{
throw new RuntimeException(ex);
}
// if we're in supergash mode, return a complete list of owner groups
if (isSuperGash())
{
return fullOwnerList;
}
// otherwise, we've got to do a very little bit of legwork
for (ObjectHandle handle: fullOwnerList.getHandles())
{
if (isMemberOfOwnerGroup(handle.getInvid()))
{
result.addRow(handle);
}
}
return result;
}
/**
* <p>This method may be used to set the owner groups of any objects
* created hereafter.</p>
*
* @param ownerInvids a List of Invid objects pointing to ownergroup
* objects.
*
* @return A ReturnVal indicating success or failure. May
* be simply 'null' to indicate success if no feedback need
* be provided.
*/
public synchronized ReturnVal setDefaultOwner(List<Invid> ownerInvids)
{
List<Invid> tmpInvids;
/* -- */
if (ownerInvids == null)
{
this.newObjectOwnerInvids = null;
return null;
}
tmpInvids = new ArrayList<Invid>();
for (Invid ownerInvidItem: ownerInvids)
{
// this check is actually redundant, as the InvidDBField link
// logic would catch such for us, but it makes a nice couplet
// with the getNum() check below, so I'll leave it here.
if (ownerInvidItem.getType() != SchemaConstants.OwnerBase)
{
// "Error in setDefaultOwner()"
// "Error.. ownerInvids contains an invalid invid"
return Ganymede.createErrorDialog(gSession,
ts.l("setDefaultOwner.error_title"),
ts.l("setDefaultOwner.error_text"));
}
// we don't want to explicitly place the object in
// supergash.. all objects are implicitly availble to
// supergash, no sense in making a big deal of it.
// this is also redundant, since DBSession.createDBObject()
// will filter this out as well. Err.. I probably should
// have faith in DBSession.createDBObject() and take this
// whole loop out, but I'm gonna leave it for now.
if (ownerInvidItem.getNum() == SchemaConstants.OwnerSupergash)
{
continue;
}
tmpInvids.add(ownerInvidItem);
}
tmpInvids = Collections.unmodifiableList(tmpInvids);
if (!this.supergashMode && !isMemberOfAllOwnerGroups(tmpInvids))
{
// "Error in setDefaultOwner()"
// "Error.. ownerInvids contains invid that the persona is not a member of."
return Ganymede.createErrorDialog(gSession,
ts.l("setDefaultOwner.error_title"),
ts.l("setDefaultOwner.error_text2"));
}
this.newObjectOwnerInvids = tmpInvids;
gSession.setLastEvent("setDefaultOwner");
return null;
}
/**
* <p>Returns an unmodifiable List of Invids of the owner groups
* that should be made owners of a newly created object by the
* GanymedeSession owned by this DBPermissionManager.</p>
*
* <p>If an admin has authority over more than one owner group and
* they have not previously specified the collection of owner groups
* that they want to assign to new objects, we'll just pick the
* first one in the list.</p>
*/
public synchronized List<Invid> getNewOwnerInvids()
{
if (this.newObjectOwnerInvids != null)
{
return this.newObjectOwnerInvids;
}
// supergash is allowed to create objects with no owners, so if
// they haven't called setDefaultOwner(), provide an empty list
if (isSuperGash())
{
return Collections.unmodifiableList(new ArrayList<Invid>());
}
List<Invid> ownerInvids = new ArrayList<Invid>();
QueryResult ownerList = getAvailableOwnerGroups();
if (ownerList.size() > 0)
{
// If we're interactive, the client really should have
// helped us out by prompting the user for their
// preferred default owner list, but if we are talking
// to a custom client, this might not be the case, in
// which case we'll just pick the first owner group we
// can put it into and put it there.
//
// The client can always manually set the owner group
// in a created object after we return it, of course.
ownerInvids.add(ownerList.getInvid(0));
}
return Collections.unmodifiableList(ownerInvids);
}
/**
* <p>This method may be used to cause the server to pre-filter any
* object listing to only show those objects directly owned by owner
* groups referenced in the ownerInvids list. This filtering will
* not restrict the ability of the client to directly view any
* object that the client's persona would normally have access to,
* but will reduce clutter and allow the client to present the world
* as would be seen by administrator personas with just the listed
* ownerGroups accessible.</p>
*
* <p>This method cannot be used to grant access to objects that are
* not accessible by the client's adminPersona.</p>
*
* <p>Calling this method with ownerInvids set to null will turn off
* the filtering.</p>
*
* @param ownerInvids a List of Invid objects pointing to ownergroup objects.
*
* @return A ReturnVal indicating success or failure. May
* be simply 'null' to indicate success if no feedback need
* be provided.
*/
public synchronized ReturnVal filterQueries(List<Invid> ownerInvids)
{
if (ownerInvids == null || ownerInvids.size() == 0)
{
visibilityFilterInvids = null;
return null;
}
List<Invid> copyList =
Collections.unmodifiableList(new ArrayList<Invid>(ownerInvids));
if (this.supergashMode || isMemberOfAllOwnerGroups(copyList))
{
this.visibilityFilterInvids = copyList;
gSession.setLastEvent("filterQueries");
return null;
}
// "Server: Error in filterQueries()"
// "Error.. ownerInvids contains invid that the persona is not a member of."
return Ganymede.createErrorDialog(gSession,
ts.l("filterQueries.error"),
ts.l("setDefaultOwner.error_text2"));
}
// Database operations
/**
* Returns a serialized representation of the basic category and
* base structure on the server.
*
* @param hideNonEditables If true, the CategoryTransport returned
* will only include those object types that are editable by the
* client.
*
* @see arlut.csd.ganymede.rmi.Category
*/
public synchronized CategoryTransport getCategoryTree(boolean hideNonEditables)
{
if (this.supergashMode)
{
if (Ganymede.catTransport != null)
{
return Ganymede.catTransport;
}
// hiding noneditables for supergash? nonsense.
Ganymede.catTransport = Ganymede.db.rootCategory.getTransport(this.gSession, true);
return Ganymede.catTransport;
}
return Ganymede.db.rootCategory.getTransport(this.gSession, hideNonEditables);
}
/**
* Returns a serialized representation of the object types defined
* on the server. This BaseListTransport object will not include
* field information. The client is obliged to call
* getFieldTemplateVector() on any bases that it needs field
* information for.
*
* @see arlut.csd.ganymede.common.BaseListTransport
*/
public synchronized BaseListTransport getBaseList()
{
if (this.supergashMode && Ganymede.baseTransport != null)
{
return Ganymede.baseTransport;
}
BaseListTransport transport = new BaseListTransport();
for (DBObjectBase base: Ganymede.db.bases())
{
base.addBaseToTransport(transport, this.gSession);
}
if (this.supergashMode)
{
Ganymede.baseTransport = transport;
}
return transport;
}
/**
* <p>This method applies this GanymedeSession's current owner
* filter to the given QueryResult <qr> and returns a
* QueryResult with any object handles that are not matched by the
* filter stripped.</p>
*
* <p>If the submitted QueryResult <qr> is null,
* filterQueryResult() will itself return null.</p>
*/
public QueryResult filterQueryResult(QueryResult qr)
{
if (qr == null)
{
return null;
}
if (this.visibilityFilterInvids == null ||
this.visibilityFilterInvids.size() == 0)
{
return qr;
}
QueryResult result = new QueryResult();
for (ObjectHandle handle: qr.getHandles())
{
if (filterMatch(handle.getInvid()))
{
result.addRow(handle);
}
}
return result;
}
/**
* This method returns true if the visibility filter vector allows
* visibility of the object in question. The visibility vector
* works by direct ownership identity (i.e., no recursing up), so
* it's a simple loop-di-loop.
*/
public boolean filterMatch(Invid invid)
{
if (invid == null)
{
return false;
}
if (visibilityFilterInvids == null || visibilityFilterInvids.size() == 0)
{
return true;
}
return filterMatch(dbSession.viewDBObject(invid));
}
/**
* This method returns true if the visibility filter vector allows
* visibility of the object in question. The visibility vector
* works by direct ownership identity (i.e., no recursing up), so
* it's a simple loop-di-loop.
*/
public boolean filterMatch(DBObject obj)
{
if (obj == null)
{
return false;
}
if (visibilityFilterInvids == null || visibilityFilterInvids.size() == 0)
{
return true;
}
List owners = obj.getFieldValuesLocal(SchemaConstants.OwnerListField);
return VectorUtils.overlaps(visibilityFilterInvids, owners);
}
/**
* Returns the authorized privileges for this DBPermissionManager on
* object.
*
* @return a non-null PermEntry
*/
public synchronized PermEntry getPerm(DBObject object)
{
if (object == null)
{
throw new NullPointerException();
}
try
{
updatePerms();
}
catch (NotLoggedInException ex)
{
return PermEntry.noPerms;
}
if (this.supergashMode)
{
return PermEntry.fullPerms;
}
return this.getObjectPerm(object, isOwnedByUs(object));
}
/**
* Returns the authorized privileges for this DBPermissionManager on
* field fieldID in object.
*
* @return a non-null PermEntry
*/
public synchronized PermEntry getPerm(DBObject object, short fieldID)
{
if (object == null)
{
throw new NullPointerException();
}
try
{
updatePerms();
}
catch (NotLoggedInException ex)
{
return PermEntry.noPerms;
}
boolean owned = isOwnedByUs(object);
PermEntry objectPerm = this.getObjectPerm(object, owned);
PermEntry fieldPerm = this.getFieldPerm(object, fieldID, owned);
PermEntry result;
if (fieldPerm == null)
{
// it's possible to lack per-field perms, in which case we
// devolve to the object-level perms
result = objectPerm;
}
else
{
// the only perm that we can sensibly have on a field that we
// don't possess on the object is the create perm
result = fieldPerm.intersection(objectPerm);
if (fieldPerm.isCreatable())
{
result = result.union(PermEntry.createPerms);
}
}
// the following check we do even for supergash, as we don't want
// to allow supergash-privileged end users from messing with
// metadata.
//
// DBEditSet.commit_recordModificationDates() bypasses perms, so
// no problem there
if ((fieldID == SchemaConstants.OwnerListField &&
(!owned || this.isEndUser())) ||
(fieldID == SchemaConstants.CreationDateField ||
fieldID == SchemaConstants.CreatorField ||
fieldID == SchemaConstants.ModificationDateField ||
fieldID == SchemaConstants.ModifierField))
{
result = PermEntry.viewPerms.intersection(result);
}
return result != null ? result : PermEntry.noPerms;
}
/**
* <p>This method returns the generic permissions for a object type.
* This is currently used primarily to check to see whether a user
* has privileges to create an object of a specific type.</p>
*
* @param ownedByUs If true, this method will return the permission
* that the current persona would have for an object that was owned
* by the current persona. If false, this method will return the
* default permissions that apply to objects not owned by the
* persona.
*
* @return a non-null PermEntry
*/
synchronized PermEntry getPerm(short baseID, boolean ownedByUs)
{
try
{
updatePerms();
}
catch (NotLoggedInException ex)
{
return PermEntry.noPerms;
}
if (this.supergashMode)
{
return PermEntry.fullPerms;
}
PermMatrix pm = ownedByUs ? this.ownedObjectPerms : this.unownedObjectPerms;
PermEntry result = pm.getPerm(baseID);
return result != null ? result : PermEntry.noPerms;
}
/**
* <p>This method returns the current persona's default permissions
* for a base and field. This permission applies generically to
* objects that are not owned by this persona and to objects that
* are owned.</p>
*
* <p>This is used by the {@link
* arlut.csd.ganymede.server.GanymedeSession#dump(arlut.csd.ganymede.common.Query)
* dump()} code to determine whether a field should be added to the
* set of possible fields to be returned at the time that the dump
* results are being prepared.</p>
*
* @return a non-null PermEntry
*/
synchronized PermEntry getPerm(short baseID, short fieldID, boolean ownedByUs)
{
try
{
updatePerms();
}
catch (NotLoggedInException ex)
{
return PermEntry.noPerms;
}
if (this.supergashMode)
{
return PermEntry.fullPerms;
}
PermMatrix pm = ownedByUs ? ownedObjectPerms : unownedObjectPerms;
PermEntry result = pm.getPerm(baseID, fieldID);
if (result == null)
{
result = pm.getPerm(baseID);
}
return result != null ? result : PermEntry.noPerms;
}
/**
* Returns the permissions for obj.
*
* @return a non-null PermEntry
*/
private PermEntry getObjectPerm(DBObject obj, boolean ownedByUs)
{
if (obj == null)
{
throw new NullPointerException();
}
// getPerm() calls updatePerms() before calling us
if (this.supergashMode)
{
return PermEntry.fullPerms;
}
PermEntry customP = obj.getHook().permOverride(gSession, obj);
if (customP != null)
{
return customP;
}
PermEntry expandP = obj.getHook().permExpand(gSession, obj);
if (expandP == null)
{
expandP = PermEntry.noPerms;
}
PermMatrix pm = ownedByUs ? ownedObjectPerms : unownedObjectPerms;
// we always union below so that we'll return PermEntry.noPerms
// rather than null even if the applicable PermMatrix doesn't have
// an entry for this object type.
return expandP.union(pm.getPerm(obj.getTypeID()));
}
/**
* Returns the permissions for fieldID in obj, without considering
* object-level permissions.
*
* @return A null PermEntry if no appropriate field-level permission
* is granted, or a non-null PermEntry if we have an explicit
* permission recorded for this field type.
*/
private synchronized PermEntry getFieldPerm(DBObject obj,
short fieldID,
boolean ownedByUs)
{
if (obj == null)
{
throw new NullPointerException();
}
// getPerm() calls updatePerms() before calling us
if (this.supergashMode)
{
return PermEntry.fullPerms;
}
PermEntry customP = obj.getHook().permOverride(gSession, obj, fieldID);
if (customP != null)
{
return customP;
}
PermMatrix pm = ownedByUs ? ownedObjectPerms: unownedObjectPerms;
PermEntry expandP = obj.getHook().permExpand(gSession, obj, fieldID);
if (expandP == null)
{
// unlike in the getObjectPerm case, we do want to return null
// if there is no explicit permission recorded for a specific
// field
return pm.getPerm(obj.getTypeID(), fieldID);
}
else
{
return expandP.union(pm.getPerm(obj.getTypeID(), fieldID));
}
}
/**
* <p>Sets supergash mode and/or the four PermMatrix objects that
* DBPermissionManager uses to track permissions.</p>
*
* <p>This method is synchronized, and a whole lot of operations in
* the server need to pass through here to ensure that the effective
* permissions for this session haven't changed. This method is
* designed to return very quickly if permissions have not
* changed.</p>
*/
private synchronized void updatePerms() throws NotLoggedInException
{
if (beforeversupergash || Ganymede.firstrun)
{
this.supergashMode = true;
return;
}
if (!rolesWereChanged() && !personaWasChanged() && !userWasChanged())
{
// there's a bit of a race here, as the calling getPerm()
// method won't check for the currency of the perms we've got
// configured until the next updatePerms() call, but returning
// slightly out of date perms won't break consistency.
return;
}
DBReadLock updatePermsLock = null;
try
{
updatePermsLock = dbSession.openReadLock(Ganymede.db.getPermBases());
updateDefaultRoleObj();
DBObject persona = updatePersonaObj();
this.supergashMode = false;
if (this.isEndUser())
{
initializeDefaultPerms();
configureEndUser();
return;
}
if (persona.containsFieldValueLocal(SchemaConstants.PersonaGroupsField,
SUPERGASH_GROUP_INVID))
{
this.supergashMode = true;
return;
}
initializeDefaultPerms();
// Personae do not get the default 'objects-owned' privileges for
// the wider range of objects under their ownership. Any special
// privileges granted to admins over objects owned by them must be
// derived from a non-default role.
List roles = persona.getFieldValuesLocal(SchemaConstants.PersonaPrivs);
for (Invid role: (List<Invid>) roles)
{
DBObject roleObj = dbSession.viewDBObject(role).getOriginal();
if (roleObj.hasField(SchemaConstants.RoleMatrix))
{
PermissionMatrixDBField pmdbf =
roleObj.getPermField(SchemaConstants.RoleMatrix);
PermMatrix m = pmdbf.getMatrix();
this.ownedObjectPerms = this.ownedObjectPerms.union(m);
if (roleObj.isSet(SchemaConstants.RoleDelegatable))
{
this.delegatableOwnedObjectPerms =
this.delegatableOwnedObjectPerms.union(m);
}
}
if (roleObj.hasField(SchemaConstants.RoleDefaultMatrix))
{
PermissionMatrixDBField pmdbf =
roleObj.getPermField(SchemaConstants.RoleDefaultMatrix);
PermMatrix m = pmdbf.getMatrix();
this.ownedObjectPerms = this.ownedObjectPerms.union(m);
this.unownedObjectPerms = this.unownedObjectPerms.union(m);
if (roleObj.isSet(SchemaConstants.RoleDelegatable))
{
this.delegatableOwnedObjectPerms =
this.delegatableOwnedObjectPerms.union(m);
this.delegatableUnownedObjectPerms =
this.delegatableUnownedObjectPerms.union(m);
}
}
}
}
catch (InterruptedException ex)
{
throw new RuntimeException(ex);
}
catch (Exception ex2)
{
if (!this.valid)
{
throw new NotLoggedInException(ex2);
}
}
finally
{
try
{
if (updatePermsLock != null)
{
dbSession.releaseLock(updatePermsLock);
}
}
catch (Exception ex3)
{
if (this.valid)
{
throw new RuntimeException(ex3);
}
}
}
}
/**
* Checks to see if any Role Objects have changed in the server
* since we last updated our perms.
*
* @return true if any changes have been made to Role Objects in the
* server
*/
private synchronized boolean rolesWereChanged()
{
return (this.rolesLastCheckedTimeStamp == null ||
Ganymede.db.getObjectBase(SchemaConstants.RoleBase).changedSince(this.rolesLastCheckedTimeStamp));
}
/**
* Updates the defaultRoleObj we reference. Separated from
* rolesWereChanged() so that we can do this part in a DBReadLock.
*/
private synchronized void updateDefaultRoleObj()
{
try
{
// get the time before we view our object, so if we get a race
// between the date and the viewDBObject call, that will cause
// us to check again next time.
Date roleTime = new Date();
this.defaultRoleObj =
dbSession.viewDBObject(DEFAULT_ROLE_INVID).getOriginal();
this.rolesLastCheckedTimeStamp = roleTime;
}
catch (NullPointerException ex)
{
// "Serious error! No default permissions object found in database!"
throw new IllegalStateException(ts.l("updateDefaultRoleObj.no_default_perms"), ex);
}
}
/**
* Returns true if this.personaObj may have changed in the database.
*
* @return true if this.personaObj may have been changed
*/
private synchronized boolean personaWasChanged()
{
return ((this.personaObj == null && this.personaInvid != null) ||
(this.personaObj != null && this.personaInvid == null) ||
this.personaTimeStamp == null ||
Ganymede.db.getObjectBase(SchemaConstants.PersonaBase).changedSince(this.personaTimeStamp));
}
/**
* Returns true if this session is being run by an end-user and the
* user linked to this session may have changed in the database.
*
* @return true if the user referenced by this.userInvid may have
* been changed
*/
private synchronized boolean userWasChanged()
{
return this.isEndUser() &&
(this.userTimeStamp == null ||
Ganymede.db.getObjectBase(SchemaConstants.UserBase).changedSince(this.userTimeStamp));
}
/**
* Updates the personaObj we reference. Separated from
* personaWasChanged() so that we can do this part in a DBReadLock.
*/
private synchronized DBObject updatePersonaObj()
{
// get the time before we view our object, so if we get a race
// between the date and the viewDBObject call, that will cause
// us to check again next time.
Date personaTime = new Date();
if (this.personaInvid == null)
{
this.personaObj = null;
this.personaTimeStamp = personaTime;
return null;
}
DBObject currentPersonaObj = dbSession.viewDBObject(this.personaInvid);
this.personaTimeStamp = personaTime;
if (currentPersonaObj == null)
{
this.valid = false;
this.personaTimeStamp = null;
// "Persona object {0} deleted while persona {0} logged in with session {1}"
gSession.forceOff(ts.l("updatePersonaObj.not_logged_in",
this.personaName, this.sessionName));
return null;
}
this.personaObj = currentPersonaObj.getOriginal();
return this.personaObj;
}
/**
* This convenience method resets all privilege matricies from the
* default unowned permissions in the default Role object.
*/
private synchronized void initializeDefaultPerms()
{
PermissionMatrixDBField pField =
this.defaultRoleObj.getPermField(SchemaConstants.RoleDefaultMatrix);
PermMatrix defaultMatrix;
if (pField != null)
{
defaultMatrix = pField.getMatrix();
}
else
{
defaultMatrix = new PermMatrix();
}
this.unownedObjectPerms = defaultMatrix;
this.delegatableUnownedObjectPerms = defaultMatrix;
this.ownedObjectPerms = defaultMatrix;
this.delegatableOwnedObjectPerms = defaultMatrix;
}
/**
* <p>Do the perms configuration needed for an unprivileged end
* user.</p>
*
* <p>This is the only case in which the defaultRoleObj's owned
* objects matrix (SchemaConstants.RoleMatrix) is consulted.</p>
*/
private synchronized void configureEndUser()
{
// get the time before we view our object, so if we get a race
// between the date and the viewDBObject call, that will cause
// us to check again next time.
Date userTime = new Date();
if (getUser() == null)
{
this.userTimeStamp = null;
this.valid = false;
// "User object for user {0} deleted while user {0} logged in with session {1}"
gSession.forceOff(ts.l("configureEndUser.not_logged_in",
this.username, this.sessionName));
return;
}
this.userTimeStamp = userTime;
PermissionMatrixDBField permField = this.defaultRoleObj.getPermField(SchemaConstants.RoleMatrix);
if (permField == null)
{
return;
}
PermMatrix selfPerms = permField.getMatrix();
this.ownedObjectPerms = this.ownedObjectPerms.union(selfPerms);
this.delegatableOwnedObjectPerms =
this.delegatableOwnedObjectPerms.union(selfPerms);
}
/**
* Returns true if the active persona is allowed to exert owned
* object permissions against obj. Note that isOwnedByUs() checks
* the grantOwnership() method in custom plugin code, and must not
* be called from a grantOwnership call, lest recursion result.
*
* @perm obj The DBObject to check ownership privileges on
*/
private synchronized boolean isOwnedByUs(DBObject obj)
{
if (obj == null)
{
return false;
}
if (this.supergashMode)
{
return true;
}
// end users are considered to own themselves
if (!isPrivileged() &&
this.userInvid != null &&
this.userInvid.equals(obj.getInvid()))
{
return true;
}
while (obj.isEmbedded())
{
if (obj.getHook().grantOwnership(gSession, obj))
{
return true;
}
Invid inv = (Invid) obj.getFieldValueLocal(SchemaConstants.ContainerField);
if (inv == null)
{
// "isOwnedByUs couldn''t find owner of embedded object {0}"
throw new IntegrityConstraintException(ts.l("isOwnedByUs.integrity",
obj.getLabel()));
}
obj = dbSession.viewDBObject(inv);
}
if (obj.getHook().grantOwnership(gSession, obj))
{
return true;
}
if (!isPrivileged())
{
return false;
}
return personaMatch(obj);
}
/**
* Returns true if the active person has ownership privileges over
* obj without consulting custom plugin code, solely through owner
* group membership.
*/
public boolean personaMatch(DBObject obj)
{
if (obj == null)
{
return false;
}
if (this.supergashMode)
{
return true;
}
// personaMatch() may be called from custom code without going
// through isOwnedByUs(), so make sure that we've got the
// top-level object
if (obj.isEmbedded())
{
obj = dbSession.getContainingObj(obj);
}
// end users are considered to own themselves
if (!isPrivileged())
{
return this.userInvid != null && this.userInvid.equals(obj.getInvid());
}
List<Invid> owners = (List<Invid>)
obj.getFieldValuesLocal(SchemaConstants.OwnerListField);
// All owner group objects are considered to be self-owning.
if (obj.getTypeID() == SchemaConstants.OwnerBase)
{
if (!owners.contains(obj.getInvid()))
{
owners.add(obj.getInvid());
}
}
// All admin personae are considered to be owned by the owner groups
// that they are members of
if (obj.getTypeID() == SchemaConstants.PersonaBase)
{
List<Invid> values = (List<Invid>)
obj.getFieldValuesLocal(SchemaConstants.PersonaGroupsField);
owners = arlut.csd.Util.VectorUtils.union(owners, values);
}
return isMemberOfAnyOwnerGroups(owners);
}
/**
* Returns true if this.personaInvid is a member of the owner group
* pointed to by the owner Invid, or in any of the owner groups that
* own that owner group, transitively.
*
* @param owner An Invid pointing to an OwnerBase object
* @return true if a match is found
*/
private synchronized boolean isMemberOfOwnerGroup(Invid owner)
{
return isMemberOfOwnerGroup(owner, new HashSet<Invid>());
}
/**
* Returns true if this.personaInvid is a member of the owner group
* pointed to by the owner Invid, or in any of the owner groups that
* own that owner group, transitively.
*
* @param owner An Invid pointing to an OwnerBase object
* @param alreadySeen A Set of owner group Invid's that have already
* been checked and which are known. (For infinite loop avoidance).
*
* @return true if a match is found
*/
private synchronized boolean isMemberOfOwnerGroup(Invid owner, Set<Invid> alreadySeen)
{
if (owner == null)
{
throw new IllegalArgumentException("Null owner");
}
if (owner.getType() != SchemaConstants.OwnerBase)
{
throw new IllegalArgumentException("bad owner group");
}
if (alreadySeen.contains(owner))
{
return false; // cycle
}
alreadySeen.add(owner);
DBObject ownerObj = dbSession.viewDBObject(owner).getOriginal();
List<Invid> personaeInOwnerGroup = (List<Invid>)
ownerObj.getFieldValuesLocal(SchemaConstants.OwnerMembersField);
if (personaeInOwnerGroup.contains(getPersonaInvid()))
{
return true;
}
// didn't find, recurse up
List<Invid> ownersOfOwnerGroup = (List<Invid>)
ownerObj.getFieldValuesLocal(SchemaConstants.OwnerListField);
return isMemberOfAnyOwnerGroups(ownersOfOwnerGroup, alreadySeen);
}
/**
* Returns true if this.personaInvid is a member of any of the owner
* group objects whose Invids are included in the owners List, or in
* any of the owner groups that own those owner groups,
* transitively.
*
* @param owners A List of invids pointing to OwnerBase objects
*
* @return true if a match is found
*/
private synchronized boolean isMemberOfAnyOwnerGroups(List<Invid> owners)
{
return isMemberOfAnyOwnerGroups(owners, new HashSet<Invid>());
}
/**
* Returns true if this.personaInvid is a member of any of the owner
* group objects whose Invids are included in the owners List, or in
* any of the owner groups that own those owner groups,
* transitively.
*
* @param owners A List of invids pointing to OwnerBase objects
* @param alreadySeen A Set of owner group Invid's that have already
* been checked. (For infinite loop avoidance).
*
* @return true if a match is found
*/
private synchronized boolean isMemberOfAnyOwnerGroups(List<Invid> owners,
Set<Invid> alreadySeen)
{
if (owners == null)
{
return false;
}
for (Invid owner: owners)
{
if (isMemberOfOwnerGroup(owner, alreadySeen))
{
return true;
}
}
return false;
}
/**
* This helper method iterates through the owners vector and checks
* to see if the current personaInvid is a member of all of the
* groups through either direct membership or through membership of
* an owning group. This method depends on isMemberOfOwnerGroup().
*/
private synchronized boolean isMemberOfAllOwnerGroups(List<Invid> owners)
{
if (owners == null)
{
return false;
}
for (Invid owner: owners)
{
if (!isMemberOfOwnerGroup(owner))
{
return false;
}
}
return true;
}
}