/*
PermissionMatrixDBField.java
This class defines the permission matrix field used in the
'Role' DBObjectBase class.
Created: 27 June 1997
Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu
-----------------------------------------------------------------------
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.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import arlut.csd.Util.TranslationService;
import arlut.csd.ganymede.common.PermEntry;
import arlut.csd.ganymede.common.PermMatrix;
import arlut.csd.ganymede.common.ReturnVal;
import arlut.csd.ganymede.common.SchemaConstants;
import arlut.csd.ganymede.rmi.Base;
import arlut.csd.ganymede.rmi.BaseField;
import arlut.csd.ganymede.rmi.perm_field;
/*------------------------------------------------------------------------------
class
PermissionMatrixDBField
------------------------------------------------------------------------------*/
/**
* <p>PermissionMatrixDBField is a subclass of {@link
* arlut.csd.ganymede.server.DBField DBField} for the storage and handling of
* permission matrix fields (used only in the Role
* {@link arlut.csd.ganymede.server.DBObject DBObjects}) in the
* {@link arlut.csd.ganymede.server.DBStore DBStore} on the Ganymede
* server.</p>
*
* <p>The Ganymede client talks to PermissionMatrixDBFields through the
* {@link arlut.csd.ganymede.rmi.perm_field perm_field} RMI interface.</p>
*
* <p>This class differs a bit from most subclasses of {@link
* arlut.csd.ganymede.server.DBField DBField} in that the normal setValue()/getValue()
* methods are non-functional. Instead, there are special methods used to set or
* access permission information from the specially coded Hashtable held by
* a PermissionMatrixDBField. This Hashtable maps strings encoded by the
* {@link arlut.csd.ganymede.server.PermissionMatrixDBField#matrixEntry(short, short)
* matrixEntry()} methods to {@link arlut.csd.ganymede.common.PermEntry PermEntry}
* objects, which hold create, edit, view, and delete bits.</p>
*
* <p>PermissionMatrixDBField's methods encode part of the server's permissions
* logic, including the restrictions on what bits can be set in a Role's
* permission matrix based on the rights granted in the client's
* {@link arlut.csd.ganymede.server.GanymedeSession GanymedeSession}. We determine
* what GanymedeSession we are operating in for that case
* by asking our {@link arlut.csd.ganymede.server.DBEditObject DBEditObject} owner.</p>
*/
public class PermissionMatrixDBField extends DBField implements perm_field {
static final boolean debug = false;
/**
* TranslationService object for handling string localization in the
* Ganymede server.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.PermissionMatrixDBField");
// ---
/**
* Returns true if this field has a value associated
* with it, or false if it is an unfilled 'placeholder'.
*
* @see arlut.csd.ganymede.rmi.db_field
*/
@Override public boolean isDefined()
{
return matrix.size() > 0;
}
/**
* <p>This method is used to mark a field as undefined when it is
* checked out for editing. Different subclasses of {@link
* arlut.csd.ganymede.server.DBField DBField} may implement this in
* different ways, if simply setting the field's value member to
* null is not appropriate. Any namespace values claimed by the
* field will be released, and when the transaction is committed,
* this field will be released.</p>
*
* <p>Note that this method is really only intended for those fields
* which have some significant internal structure to them, such as
* permission matrix, field option matrix, and password fields.</p>
*
* <p>NOTE: There is, at present, no defined DBEditObject callback
* method that tracks generic field nullification. This means that
* if your code uses setUndefined on a PermissionMatrixDBField,
* FieldOptionDBField, or PasswordDBField, the plugin code is not
* currently given the opportunity to review and refuse that
* operation. Caveat Coder.</p>
*/
@Override public synchronized ReturnVal setUndefined(boolean local)
{
if (isEditable(local))
{
matrix.clear();
return null;
}
// "Don''t have permission to clear this permission matrix field\n{0}"
return Ganymede.createErrorDialog(this.getGSession(),
null,
ts.l("setUndefined.error", getName()));
}
/**
* This utility method extracts the
* {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase} name from a coded
* permission entry held in a
* {@link arlut.csd.ganymede.common.PermMatrix PermMatrix}/PermissionMatrixDBField
* Matrix.
*/
public static String decodeBaseName(String entry)
{
int sepIndex;
short basenum;
DBObjectBase base;
String basename;
/* -- */
sepIndex = entry.indexOf(':');
if (sepIndex != -1)
{
try
{
basenum = Short.valueOf(entry.substring(0, sepIndex)).shortValue();
base = Ganymede.db.getObjectBase(basenum);
if (base == null)
{
basename = "INVALID: " + entry;
}
else
{
basename = base.getName();
}
}
catch (NumberFormatException ex)
{
basename = entry;
}
}
else
{
basename = entry;
}
return basename;
}
/**
* This utility method extracts the
* {@link arlut.csd.ganymede.server.DBObjectBaseField DBObjectBaseField} name from a coded
* permission entry held in a
* {@link arlut.csd.ganymede.common.PermMatrix PermMatrix}/PermissionMatrixDBField
* Matrix.
*/
public static String decodeFieldName(String entry)
{
int sepIndex;
short basenum;
DBObjectBase base;
String fieldId;
short fieldnum;
DBObjectBaseField field;
String fieldname;
/* -- */
sepIndex = entry.indexOf(':');
if (sepIndex != -1)
{
try
{
basenum = Short.valueOf(entry.substring(0, sepIndex)).shortValue();
base = Ganymede.db.getObjectBase(basenum);
if (base == null)
{
fieldname = "[error " + entry + "]";
}
else
{
fieldId = entry.substring(sepIndex+1);
if (fieldId.equals(":"))
{
fieldname = "[base]";
}
else
{
try
{
fieldnum = Short.valueOf(fieldId).shortValue();
field = base.getField(fieldnum);
if (field == null)
{
fieldname = "invalid:" + fieldId;
}
else
{
fieldname = field.getName();
}
}
catch (NumberFormatException ex)
{
fieldname = fieldId;
}
}
}
}
catch (NumberFormatException ex)
{
fieldname = "[error " + entry + "]";
}
}
else
{
fieldname = "[error " + entry + "]";
}
return fieldname;
}
/**
* Returns true if the given String encodes the identity of
* a {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase} and
* not a field within a DBObjectBase.
*/
private boolean isBasePerm(String matrixEntry)
{
return (matrixEntry.indexOf("::") != -1);
}
/**
* This method returns true if the given
* {@link arlut.csd.ganymede.common.PermMatrix PermMatrix}/
* PermissionMatrixDBField key refers to a currently valid
* {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase}/
* {@link arlut.csd.ganymede.server.DBObjectBaseField DBObjectBaseField}
* in the loaded schema.
*/
public static boolean isValidCode(String entry)
{
int sepIndex;
short basenum;
DBObjectBase base;
String fieldId;
short fieldnum;
DBObjectBaseField field;
/* -- */
sepIndex = entry.indexOf(':');
if (sepIndex == -1)
{
return false;
}
try
{
basenum = Short.valueOf(entry.substring(0, sepIndex)).shortValue();
base = Ganymede.db.getObjectBase(basenum);
if (base == null)
{
return false;
}
else
{
fieldId = entry.substring(sepIndex+1);
if (!fieldId.equals(":"))
{
try
{
fieldnum = Short.valueOf(fieldId).shortValue();
field = base.getField(fieldnum);
if (field == null)
{
return false;
}
}
catch (NumberFormatException ex)
{
return false;
}
}
}
}
catch (NumberFormatException ex)
{
return false;
}
return true;
}
/**
* This method does a dump to System.err of the permission
* contents held in PermissionMatrixDBField me.
*/
public void debugdump(PermissionMatrixDBField me)
{
debugdump(me.getMatrix());
}
static public void debugdump(PermMatrix matrix)
{
debugdump(matrix.getMatrix());
}
/**
* This method does a dump to System.err of the permission
* contents held in matrix.
*/
static private void debugdump(Map<String, PermEntry> matrix)
{
System.err.println(debugdecode(matrix));
}
/**
* This method generates a string version of the debugdump
* output.
*/
static public String debugdecode(Map<String, PermEntry> matrix)
{
StringBuilder result = new StringBuilder();
Enumeration en;
PermEntry entry;
String basename;
Hashtable<String, Vector<String>> baseHash = new Hashtable<String, Vector<String>>();
Vector<String> vec;
/* -- */
result.append("PermMatrix DebugDump\n");
for (Map.Entry<String, PermEntry> item: matrix.entrySet())
{
entry = item.getValue();
basename = decodeBaseName(item.getKey());
if (baseHash.containsKey(basename))
{
vec = baseHash.get(basename);
}
else
{
vec = new Vector<String>();
baseHash.put(basename, vec);
}
vec.add(decodeFieldName(item.getKey()) + " -- " + entry.toString());
}
for (Map.Entry<String, Vector<String>> item: baseHash.entrySet())
{
vec = item.getValue();
for (String val: vec)
{
result.append(item.getKey() + ":" + val + "\n");
}
result.append("\n");
}
return result.toString();
}
// ---
private Hashtable<String, PermEntry> matrix;
/* -- */
/**
* Receive constructor. Used to create a PermissionMatrixDBField from a
* {@link arlut.csd.ganymede.server.DBStore DBStore}/{@link arlut.csd.ganymede.server.DBJournal DBJournal}
* DataInput stream.
*/
PermissionMatrixDBField(DBObject owner,
DataInput in,
DBObjectBaseField definition) throws IOException
{
super(owner, definition.getID());
this.value = null;
receive(in, definition);
}
/**
* <p>No-value constructor. Allows the construction of a
* 'non-initialized' field, for use where the
* {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase}
* definition indicates that a given field may be present,
* but for which no value has been stored in the
* {@link arlut.csd.ganymede.server.DBStore DBStore}.</p>
*
* <p>Used to provide the client a template for 'creating' this
* field if so desired.</p>
*/
PermissionMatrixDBField(DBObject owner, DBObjectBaseField definition)
{
super(owner, definition.getID());
this.matrix = new Hashtable<String, PermEntry>();
this.value = null;
}
/**
* Copy constructor.
*/
public PermissionMatrixDBField(DBObject owner, PermissionMatrixDBField field)
{
super(owner, field.getID());
this.value = null;
if (debug)
{
System.err.println("PermissionMatrixDBField: Copy constructor");
}
this.matrix = new Hashtable<String, PermEntry>(field.matrix.size());
for (String key: field.matrix.keySet())
{
PermEntry entry = field.matrix.get(key);
if (debug)
{
System.err.println("PermissionMatrixDBField: copying " + key + ", contents: " + entry);
}
this.matrix.put(key, entry);
}
}
// we never allow setValue
@Override public boolean verifyTypeMatch(Object v)
{
return false;
}
// we never allow setValue
@Override public ReturnVal verifyNewValue(Object v)
{
return Ganymede.createErrorDialog(this.getGSession(),
"Permission Matrix Field Error",
"setValue() not allowed on PermissionMatrixDBField.");
}
/**
* We don't expect these fields to ever be stored in a hash.
*/
@Override public int hashCode()
{
throw new UnsupportedOperationException();
}
/**
* Fancy equals method really does check for value equality.
*/
@Override public synchronized boolean equals(Object obj)
{
PermissionMatrixDBField pmdb;
/* -- */
if (obj == null)
{
return false;
}
if (!(obj.getClass().equals(this.getClass())))
{
return false;
}
pmdb = (PermissionMatrixDBField) obj;
if (matrix.size() != pmdb.matrix.size())
{
return false;
}
for (String key: matrix.keySet())
{
try
{
if (!(matrix.get(key).equals(pmdb.matrix.get(key))))
{
return false;
}
}
catch (NullPointerException ex)
{
return false;
}
}
return true;
}
/**
* This method copies the current value of this DBField
* to target. The target DBField must be contained within a
* checked-out DBEditObject in order to be updated. Any actions
* that would normally occur from a user manually setting a value
* into the field will occur.
*
* @param target The DBField to copy this field's contents to.
* @param local If true, permissions checking is skipped.
*/
@Override public synchronized ReturnVal copyFieldTo(DBField target, boolean local)
{
if (!local)
{
if (!verifyReadPermission())
{
// "Copy Field Error"
// "Can''t copy field {0}, no read privileges."
return Ganymede.createErrorDialog(this.getGSession(),
ts.l("copyFieldTo.error_subj"),
ts.l("copyFieldTo.no_read", getName()));
}
}
if (!target.isEditable(local))
{
// "Copy Field Error"
// "Can''t copy field {0}, no write privileges."
return Ganymede.createErrorDialog(this.getGSession(),
ts.l("copyFieldTo.error_subj"),
ts.l("copyFieldTo.no_write", getName()));
}
if (!(target instanceof PermissionMatrixDBField))
{
// "Copy Field Error"
// "Can''t copy field {0}, target is not a PermissionMatrixDBField"
return Ganymede.createErrorDialog(this.getGSession(),
ts.l("copyFieldTo.error_subj"),
ts.l("copyFieldTo.bad_param", getName()));
}
// doing a simple clone of the hashtable is okay, since both the
// keys and values of the matrix are treated as immutable (they
// are replaced, not changed in-place)
((PermissionMatrixDBField) target).matrix = new Hashtable<String, PermEntry>(this.matrix);
return null;
}
/**
* We don't really want to hash according to our permission
* contents, so just hash according to our containing object's
* i.d.
*/
@Override public Object key()
{
return Integer.valueOf(this.owner.getID());
}
/**
* We always return null here..
*/
@Override public Object getValue()
{
return null;
}
/**
* <p>Returns an Object carrying the value held in this field.</p>
*
* <p>This is intended to be used within the Ganymede server, it bypasses
* the permissions checking that getValues() does.</p>
*/
@Override public Object getValueLocal()
{
return null;
}
/**
* <p>We don't allow setValue().</p>
*
* <p>PermissionMatrixDBField doesn't allow
* direct setting of the entire matrix.. just use the get() and
* set() methods below.</p>
*/
@Override public ReturnVal setValue(Object value, boolean local, boolean noWizards)
{
// "Server: Error in PermissionMatrixDBField.setValue()"
// "Error.. can''t call setValue() on a PermissionMatrixDBField."
return Ganymede.createErrorDialog(this.getGSession(),
ts.l("setValue.error_subj"),
ts.l("setValue.error_text"));
}
@Override public Object clone() throws CloneNotSupportedException
{
throw new CloneNotSupportedException();
}
@Override synchronized void emit(DataOutput out) throws IOException
{
if (debug)
{
debugdump(matrix);
}
// If we have invalid entries, we're just going to throw them out,
// forget they even existed.. this is reasonable
// because matrix is private to this class.. PermissionMatrixDBField
// is responsible for maintaining the meaningful content of the permissions
// entered into it, not the particulars of the matrix.
// The permisison matrix bits generally become invalid after
// schema editing. Since normally the database/schema needs to be
// dumped after changing the schema, this is an appropriate place
// to do the cleanup.
if (DBSchemaEdit.schemaEditedSinceStartup)
{
clean();
}
// now actually emit stuff.
out.writeInt(matrix.size());
for (String key: matrix.keySet())
{
PermEntry pe = matrix.get(key);
out.writeUTF(key);
pe.emit(out);
}
}
@Override synchronized void receive(DataInput in, DBObjectBaseField definition) throws IOException
{
int tableSize;
PermEntry pe;
String key;
/* -- */
tableSize = in.readInt();
if (tableSize <= 0)
{
matrix = new Hashtable<String, PermEntry>();
}
else
{
matrix = new Hashtable<String, PermEntry>(tableSize);
}
for (int i = 0; i < tableSize; i++)
{
key = in.readUTF();
pe = PermEntry.getPermEntry(in);
matrix.put(key, pe);
}
}
@Override void emitXML(XMLDumpContext dump) throws IOException
{
this.emitXML(dump, true);
}
synchronized void emitXML(XMLDumpContext xmlOut, boolean writeSurroundContext) throws IOException
{
Enumeration en, enum2;
PermEntry entry;
String basename;
Hashtable<String, Hashtable<String, PermEntry>> baseHash = new Hashtable<String, Hashtable<String, PermEntry>>();
Hashtable<String, PermEntry> innerTable;
/* -- */
// build up a hashtable structure so we get all the permission
// entries grouped by base.
for (String key: matrix.keySet())
{
entry = matrix.get(key);
basename = decodeBaseName(key);
if (baseHash.containsKey(basename))
{
innerTable = baseHash.get(basename);
}
else
{
innerTable = new Hashtable<String, PermEntry>();
baseHash.put(basename, innerTable);
}
innerTable.put(decodeFieldName(key), entry);
}
if (writeSurroundContext)
{
xmlOut.startElementIndent(this.getXMLName());
xmlOut.indentOut();
}
xmlOut.startElementIndent("permissions");
xmlOut.indentOut();
for (Map.Entry<String,Hashtable<String, PermEntry>> item: baseHash.entrySet())
{
innerTable = item.getValue();
entry = innerTable.get("[base]");
xmlOut.startElementIndent(arlut.csd.Util.XMLUtils.XMLEncode(item.getKey()));
xmlOut.indentOut();
if (entry != null)
{
xmlOut.attribute("perm", entry.getXMLCode());
}
for (Map.Entry<String, PermEntry> fieldItem: innerTable.entrySet())
{
if (fieldItem.getKey().equals("[base]"))
{
continue; // we've already wrote perms for the base
}
PermEntry fieldEntry = fieldItem.getValue();
xmlOut.startElementIndent(arlut.csd.Util.XMLUtils.XMLEncode(fieldItem.getKey()));
xmlOut.attribute("perm", fieldEntry.getXMLCode());
xmlOut.endElement(arlut.csd.Util.XMLUtils.XMLEncode(fieldItem.getKey()));
}
xmlOut.indentIn();
xmlOut.endElementIndent(arlut.csd.Util.XMLUtils.XMLEncode(item.getKey()));
}
xmlOut.indentIn();
xmlOut.endElementIndent("permissions");
if (writeSurroundContext)
{
xmlOut.indentIn();
xmlOut.endElementIndent(this.getXMLName());
}
}
@Override public synchronized String getValueString()
{
StringBuilder result = new StringBuilder();
/* -- */
for (String key: matrix.keySet())
{
PermEntry entry = matrix.get(key);
if (isBasePerm(key))
{
result.append(decodeBaseName(key) + " -- " + entry.difference(null));
}
else
{
result.append(decodeBaseName(key) + " " + decodeFieldName(key) +
" -- " + entry.difference(null));
}
result.append("\n");
}
return result.toString();
}
/**
* We don't try and give a comprehensive encoding string for permission
* matrices, let's just give enough so they know what we are.
*/
@Override public String getEncodingString()
{
if (!verifyReadPermission())
{
throw new IllegalArgumentException("permission denied to read this field");
}
return "PermissionMatrix";
}
/**
* <p>Returns a String representing the change in value between this
* field and orig. This String is intended for logging and email,
* not for any sort of programmatic activity. The format of the
* generated string is not defined, but is intended to be suitable
* for inclusion in a log entry and in an email message.</p>
*
* <p>If there is no change in the field, null will be returned.</p>
*/
@Override public String getDiffString(DBField orig)
{
PermissionMatrixDBField origP;
/* -- */
if (!(orig instanceof PermissionMatrixDBField))
{
throw new IllegalArgumentException("bad field comparison");
}
origP = (PermissionMatrixDBField) orig;
if (origP.equals(this))
{
return null;
}
StringBuilder result = new StringBuilder();
Vector<String> myKeys = new Vector<String>();
Vector<String> origKeys = new Vector<String>();
for (String key: matrix.keySet())
{
myKeys.add(key);
}
for (String key: origP.matrix.keySet())
{
origKeys.add(key);
}
Vector<String> keptKeys = arlut.csd.Util.VectorUtils.intersection(myKeys, origKeys);
Vector<String> newKeys = arlut.csd.Util.VectorUtils.difference(myKeys, keptKeys);
Vector<String> lostKeys = arlut.csd.Util.VectorUtils.difference(origKeys, keptKeys);
for (String key: keptKeys)
{
PermEntry entryA = matrix.get(key);
PermEntry entryB = origP.matrix.get(key);
if (!entryA.equals(entryB))
{
result.append(decodeBaseName(key) + " " + decodeFieldName(key) +
" -- " + entryA.difference(entryB));
result.append("\n");
}
}
for (String key: newKeys)
{
PermEntry entryA = matrix.get(key);
// "{0} {1} -- {2}\n"
result.append(ts.l("getValueString.new_pattern",
decodeBaseName(key),
decodeFieldName(key),
entryA.difference(null)));
}
for (String key: lostKeys)
{
PermEntry entryB = origP.matrix.get(key);
// "{0} {1} -- {2}\n"
result.append(ts.l("getValueString.old_pattern",
decodeBaseName(key),
decodeFieldName(key),
entryB));
}
return result.toString();
}
/**
* Return a serializable, read-only copy of this field's permission
* matrix
*
* @see arlut.csd.ganymede.rmi.perm_field
*/
public synchronized PermMatrix getMatrix()
{
return new PermMatrix(this.matrix);
}
/**
* <p>Return a serializable, read-only copy of the maximum permissions
* that can be set for this field's permission matrix. This matrix
* is drawn from the union of delegatable roles that the client's
* adminPersona is a member of.</p>
*
* <p>This method will return null if this perm_field is not associated
* with an object that is being edited, or if the client is logged
* into the server as supergash.</p>
*
* @see arlut.csd.ganymede.rmi.perm_field
*/
public synchronized PermMatrix getTemplateMatrix()
{
if (!(this.owner instanceof DBEditObject))
{
return null;
}
if (this.owner.gSession.getPermManager().isSuperGash())
{
return null;
}
if (getID() == SchemaConstants.RoleMatrix)
{
return this.owner.gSession.getPermManager().getDelegatableOwnedObjectPerms();
}
if (getID() == SchemaConstants.RoleDefaultMatrix)
{
return this.owner.gSession.getPermManager().getDelegatableUnownedObjectPerms();
}
return null;
}
/**
* Returns a PermEntry object representing this
* {@link arlut.csd.ganymede.common.PermMatrix PermMatrix}'s
* permissions on the field <fieldID> in base <baseID>
*
* @see arlut.csd.ganymede.rmi.perm_field
* @see arlut.csd.ganymede.common.PermMatrix
*/
public synchronized PermEntry getPerm(short baseID, short fieldID)
{
return matrix.get(matrixEntry(baseID, fieldID));
}
/**
* Returns a PermEntry object representing this PermMatrix's
* permissions on the base <baseID>
*
* @see arlut.csd.ganymede.rmi.perm_field
* @see arlut.csd.ganymede.common.PermMatrix
*/
public synchronized PermEntry getPerm(short baseID)
{
return matrix.get(matrixEntry(baseID));
}
/**
* Returns a PermEntry object representing this PermMatrix's
* permissions on the field <field> in base <base>
*
* @see arlut.csd.ganymede.rmi.perm_field
* @see arlut.csd.ganymede.common.PermMatrix
*/
public synchronized PermEntry getPerm(Base base, BaseField field)
{
try
{
return matrix.get(matrixEntry(base.getTypeID(),
field.getID()));
}
catch (RemoteException ex)
{
throw new RuntimeException("caught remote: " + ex);
}
}
/**
* Returns a PermEntry object representing this PermMatrix's
* permissions on the base <base>
*
* @see arlut.csd.ganymede.rmi.perm_field
* @see arlut.csd.ganymede.common.PermMatrix
*/
public synchronized PermEntry getPerm(Base base)
{
try
{
return matrix.get(matrixEntry(base.getTypeID()));
}
catch (RemoteException ex)
{
throw new RuntimeException("caught remote: " + ex);
}
}
/**
* <p>Resets the permissions in this PermissionMatrixDBField to
* the empty set. Used by non-interactive clients to reset
* the Permission Matrix to a known state before setting
* permissions.</p>
*
* <p>Returns null on success, or a failure-coded ReturnVal
* on permissions failure.</p>
*
* @see arlut.csd.ganymede.rmi.perm_field
*/
public ReturnVal resetPerms()
{
if (isEditable())
{
matrix.clear();
matrix = new Hashtable<String, PermEntry>();
return null;
}
else
{
// "Permissions Failure"
// "You don''t have permissions to reset {0}''s permission matrix."
return Ganymede.createErrorDialog(this.getGSession(),
ts.l("resetPerms.error_subj"),
ts.l("resetPerms.error_text", toString()));
}
}
/**
* <p>Sets the permission entry for base <base>,
* field <field> to PermEntry <entry></p>
*
* <p>This operation will fail if this
* PermissionMatrixDBField is not editable.</p>
*
* @see arlut.csd.ganymede.rmi.perm_field
* @see arlut.csd.ganymede.common.PermEntry
*/
public ReturnVal setPerm(Base base, BaseField field, PermEntry entry)
{
try
{
return setPerm(base.getTypeID(), field.getID(), entry);
}
catch (RemoteException ex)
{
// "Couldn''t process setPerm(): {0}"
return Ganymede.createErrorDialog(this.getGSession(),
null,
ts.l("setPerm.error_text", ex.getMessage()));
}
}
/**
* <p>Sets the permission entry for base <baseID>,
* field <fieldID> to PermEntry <entry>.</p>
*
* <p>This operation will fail if this
* PermissionMatrixDBField is not editable.</p>
*
* @param baseID the object type to set permissions for
* @param fieldID the field to set permissions for. If fieldID < 0,
* the permission will be applied to the object as a whole rather
* than any individual field within the object
*
* @return A ReturnVal encoding success or failure
*
* @see arlut.csd.ganymede.rmi.perm_field
* @see arlut.csd.ganymede.common.PermEntry
*/
public synchronized ReturnVal setPerm(short baseID, short fieldID, PermEntry entry)
{
if (isEditable())
{
if (allowablePermEntry(baseID, fieldID, entry))
{
matrix.put(matrixEntry(baseID, fieldID), entry);
}
else
{
DBObjectBase base = Ganymede.db.getObjectBase(baseID);
DBObjectBaseField field = base.getField(fieldID);
String baseName = base.getName();
String fieldName = field.getName();
// "You can''t set privileges for base {0}, field {1}, that you yourself do not have."
return Ganymede.createErrorDialog(this.getGSession(),
null,
ts.l("setPerm.delegation_error", baseName, fieldName));
}
}
else
{
throw new IllegalArgumentException("not an editable field");
}
if (debug)
{
System.err.println("PermissionMatrixDBField: base " +
baseID + ", field " + fieldID + " set to " + entry);
}
return null;
}
/**
* <p>Sets the permission entry for base <baseID>
* to PermEntry <entry>.</p>
*
* <p>This operation will fail if this
* PermissionMatrixDBField is not editable.</p>
*
* @see arlut.csd.ganymede.rmi.perm_field
* @see arlut.csd.ganymede.common.PermMatrix
*/
public ReturnVal setPerm(Base base, PermEntry entry)
{
// no need for synchronization, since we call a synchronized
// setPerm() call
try
{
return setPerm(base.getTypeID(), entry);
}
catch (RemoteException ex)
{
// "Couldn''t process setPerm(): {0}"
return Ganymede.createErrorDialog(this.getGSession(),
null,
ts.l("setPerm.error_text", ex.getMessage()));
}
}
/**
* <p>Sets the permission entry for this matrix for base <baseID>
* to PermEntry <entry></p>
*
* <p>This operation will fail if this
* PermissionMatrixDBField is not editable.</p>
*
* @see arlut.csd.ganymede.rmi.perm_field
* @see arlut.csd.ganymede.common.PermEntry
*/
public synchronized ReturnVal setPerm(short baseID, PermEntry entry)
{
if (isEditable())
{
if (allowablePermEntry(baseID, (short) -1, entry))
{
matrix.put(matrixEntry(baseID), entry);
}
else
{
DBObjectBase base = Ganymede.db.getObjectBase(baseID);
String baseName = base.getName();
// "You can''t set privileges for base {0} that you yourself do not have."
return Ganymede.createErrorDialog(this.getGSession(),
null,
ts.l("setPerm.base_delegation_error", baseName));
}
}
else
{
throw new IllegalArgumentException("not an editable field");
}
if (debug)
{
System.err.println("PermissionMatrixDBField: base " + baseID + " set to " + entry);
}
return null;
}
/**
* This internal method is used to cull out any entries in this
* permissions field that are non-operative, either by referring to
* an object/field type that does not exist, or by being redundant.
*/
private void clean()
{
Set<Map.Entry<String, PermEntry>> elements = matrix.entrySet();
Iterator<Map.Entry<String, PermEntry>> iterator = elements.iterator();
while (iterator.hasNext())
{
Map.Entry<String, PermEntry> entry = iterator.next();
String key = entry.getKey();
PermEntry pe = entry.getValue();
// If we have invalid entries, we're just going to throw them out,
// forget they even existed.. this is only remotely reasonable
// because matrix is private to this class, and because these
// invalid entries could serve no useful purpose, and will only
// become invalid after schema editing in any case. Since
// normally the database/schema needs to be dumped after changing
// the schema, this is an appropriate place to do the cleanup.
if (!isValidCode(key))
{
iterator.remove();
if (debug)
{
System.err.println("**** PermissionMatrixDBField.clean(): throwing out invalid entry " +
decodeBaseName(key) + " " +
decodeFieldName(key) + " ---- " +
pe.toString());
}
}
}
}
/**
* Private method to generate a key for use in
* our internal Hashtable, used to encode the
* permission for a given {@link arlut.csd.ganymede.server.DBObjectBase
* DBObjectBase} and {@link arlut.csd.ganymede.server.DBObjectBaseField
* DBObjectBaseField}.
*/
private String matrixEntry(short baseID, short fieldID)
{
return (baseID + ":" + fieldID);
}
/**
* Private method to generate a key for use in
* our internal Hashtable, used to encode the
* permission for a given {@link arlut.csd.ganymede.server.DBObjectBase
* DBObjectBase}.
*/
private String matrixEntry(short baseID)
{
return (baseID + "::");
}
/**
* This method is used to basically dump state out of this field
* so that the {@link arlut.csd.ganymede.server.DBEditSet DBEditSet}
* {@link arlut.csd.ganymede.server.DBEditSet#checkpoint(java.lang.String) checkpoint()}
* code can restore it later
* if need be.
*/
@Override public synchronized Object checkpoint()
{
if (matrix != null)
{
return new PermMatrixCkPoint(this);
}
else
{
return null;
}
}
/**
* <p>This method is used to basically force state into this field.</p>
*
* <p>It is used to place a value or set of values that were known to
* be good during the current transaction back into this field,
* without creating or changing this DBField's object identity.</p>
*/
@Override public synchronized void rollback(Object oldval)
{
if (!(this.owner instanceof DBEditObject))
{
throw new RuntimeException("Invalid rollback on field " +
getName() + ", not in an editable context");
}
if (oldval == null)
{
this.setUndefined(true);
return;
}
if ((oldval instanceof PermMatrixCkPoint))
{
this.matrix = ((PermMatrixCkPoint) oldval).matrix;
return;
}
throw new RuntimeException("Invalid rollback on field " +
getName() + ", not a PermMatrixCkPoint");
}
/**
* <p>This method is used to check that the given operation can be set by the
* current administrator.</p>
*
* <p>If fieldID < 0, entry will be checked against the
* administrator's applicable object/base permissions, rather than
* those for the field itself.</p>
*/
public synchronized boolean allowablePermEntry(short baseID, short fieldID, PermEntry entry)
{
if (this.owner.gSession == null)
{
return false;
}
if (entry == null)
{
throw new IllegalArgumentException("entry is null");
}
DBObjectBase base = Ganymede.db.getObjectBase(baseID);
if (base == null)
{
throw new IllegalArgumentException("bad base id");
}
if (fieldID != -1 && base.getField(fieldID) == null)
{
throw new IllegalArgumentException("bad field id");
}
if (this.owner.gSession.getPermManager().isSuperGash())
{
return true;
}
if (getID() == SchemaConstants.RoleMatrix)
{
if (this.owner.gSession.getPermManager().getOwnedObjectPerms() == null)
{
return false;
}
PermEntry adminPriv;
if (fieldID < 0)
{
adminPriv = (PermEntry) this.owner.gSession.getPermManager().getDelegatableOwnedObjectPerms().getPerm(baseID);
}
else
{
adminPriv = (PermEntry) this.owner.gSession.getPermManager().getDelegatableOwnedObjectPerms().getPerm(baseID, fieldID);
}
// the adminPriv should have all the bits set that we are seeking to set
return entry.equals(entry.intersection(adminPriv));
}
else if (getID() == SchemaConstants.RoleDefaultMatrix)
{
if (this.owner.gSession.getPermManager().getDefaultPerms() == null)
{
return false;
}
PermEntry adminPriv;
if (fieldID < 0)
{
adminPriv = (PermEntry) this.owner.gSession.getPermManager().getDelegatableUnownedObjectPerms().getPerm(baseID);
}
else
{
adminPriv = (PermEntry) this.owner.gSession.getPermManager().getDelegatableUnownedObjectPerms().getPerm(baseID, fieldID);
}
// the adminPriv should have all the bits set that we are seeking to set
return entry.equals(entry.intersection(adminPriv));
}
else
{
throw new RuntimeException("Error, don't recognize field id.. should be 'Owned Object Bits' or " +
"'Default Bits'.");
}
}
/**
* <p>Server-side method used by DBPermissionManager to extract Map
* for PermMatrix creation.</p>
*/
public synchronized Hashtable<String, PermEntry> getInnerMatrix()
{
return new Hashtable<String, PermEntry>(this.matrix);
}
/**
* This method does a dump to System.err of the permission
* contents held in this field.
*/
public void debugdump()
{
debugdump(this);
}
}
/*------------------------------------------------------------------------------
class
PermMatrixCkPoint
------------------------------------------------------------------------------*/
/**
* <p>Helper class used to handle checkpointing of a
* {@link arlut.csd.ganymede.server.PermissionMatrixDBField PermissionMatrixDBField}'s
* state.</p>
*
* <p>See {@link arlut.csd.ganymede.server.PermissionMatrixDBField#checkpoint()
* PermissionMatrixDBField.checkpoint()} for more detail.</p>
*/
class PermMatrixCkPoint {
Hashtable<String, PermEntry> matrix;
/* -- */
public PermMatrixCkPoint(PermissionMatrixDBField field)
{
this.matrix = field.getInnerMatrix();
}
}