/*
userCustom.java
This file is a management class for user objects in Ganymede.
Created: 30 July 1997
Version: %I% %D%
Module By: Jonathan Abbey
Applied Research Laboratories, The University of Texas at Austin
*/
package arlut.csd.ganymede.custom;
import arlut.csd.ganymede.server.*;
import arlut.csd.ganymede.common.*;
import arlut.csd.ganymede.rmi.*;
import arlut.csd.JDialog.JDialogBuff;
import java.util.*;
import java.rmi.*;
/*------------------------------------------------------------------------------
class
userCustom
------------------------------------------------------------------------------*/
/**
*
* This class is the custom plug-in to handle the user object type in the
* Ganymede server. It does special validations of operations on the user,
* handles inactivation and reactivation logic, and generates Wizards as
* needed.<br>
*
* <br>See the userSchema.java file for a list of field definitions that this
* module expects to work with.<br>
*
* @see arlut.csd.ganymede.custom.userSchema
* @see arlut.csd.ganymede.DBEditObject
*
*/
public class userCustom extends DBEditObject implements SchemaConstants, userSchema {
static final boolean debug = false;
static QueryResult shellChoices = new QueryResult();
static Date shellChoiceStamp = null;
static String mailsuffix = null;
static String homedir = null;
// ---
QueryResult groupChoices = null;
/**
*
* Customization Constructor
*
*/
public userCustom(DBObjectBase objectBase)
{
super(objectBase);
}
/**
*
* Create new object constructor
*
*/
public userCustom(DBObjectBase objectBase, Invid invid, DBEditSet editset)
{
super(objectBase, invid, editset);
}
/**
*
* Check-out constructor, used by DBObject.createShadow()
* to pull out an object for editing.
*
*/
public userCustom(DBObject original, DBEditSet editset)
{
super(original, editset);
}
/**
*
* Initialize a newly created DBEditObject.
*
* When this method is called, the DBEditObject has been created,
* its ownership set, and all fields defined in the controlling
* DBObjectBase have been instantiated without defined
* values.<br><br>
*
* This method is responsible for filling in any default
* values that can be calculated from the DBSession
* associated with the editset defined in this DBEditObject.<br><br>
*
* If initialization fails for some reason, initializeNewObject()
* will return false. If the owning GanymedeSession is not in
* bulk-loading mode (i.e., enableOversight is true),
* DBSession.createDBObject() will checkpoint the transaction before
* calling this method. If this method returns false, the calling
* method will rollback the transaction. This method has no
* responsibility for undoing partial initialization, the
* checkpoint/rollback logic will take care of that.<br><br>
*
* If enableOversight is false, DBSession.createDBObject() will not
* checkpoint the transaction status prior to calling initializeNewObject(),
* so it is the responsibility of this method to handle any checkpointing
* needed.<br><br>
*
* This method should be overridden in subclasses.
*
*/
public boolean initializeNewObject()
{
ReturnVal retVal;
boolean success = true;
/* -- */
// we don't want to do any of this initialization during
// bulk-loading.
if (!getGSession().enableOversight)
{
return true;
}
// need to find a uid for this user
NumericDBField numField = (NumericDBField) getField(UID);
if (numField == null)
{
System.err.println("userCustom.initializeNewObject(): couldn't get uid field");
return false;
}
DBNameSpace namespace = numField.getNameSpace();
if (namespace == null)
{
System.err.println("userCustom.initializeNewObject(): couldn't get uid namespace");
return false;
}
// now, find a uid.. unfortunately, we have to use immutable Integers here.. not
// the most efficient at all.
Integer uidVal = new Integer(1001);
while (!namespace.testmark(editset, uidVal))
{
uidVal = new Integer(uidVal.intValue()+1);
}
// we use setValueLocal so we can set a value that the user can't edit.
retVal = numField.setValueLocal(uidVal);
if (!ReturnVal.didSucceed(retVal))
{
success = false;
}
// create a volume entry for the user.
InvidDBField invf = (InvidDBField) getField(userSchema.VOLUMES);
retVal = invf.createNewEmbedded(true);
if ((retVal == null) || (!retVal.didSucceed()))
{
return false;
}
Invid invid = retVal.getInvid();
if (invid != null)
{
// find the auto.home.default map, if we can.
Vector results = getGSession().internalQuery(new Query((short) 277,
new QueryDataNode(QueryDataNode.EQUALS,
"auto.home.default")));
// if we found auto.home.default, set the new volume entry map
// field to point to auto.home.default.
if (results != null && results.size() == 1)
{
Result objid = (Result) results.elementAt(0);
DBEditObject eObj = getSession().editDBObject(invid);
invf = (InvidDBField) eObj.getField(mapEntrySchema.MAP);
retVal = invf.setValueLocal(objid.getInvid());
if (!ReturnVal.didSucceed(retVal))
{
success = false;
}
// we want the permissions system to reject edit privs
// on this now.. by setting permCache to null, we allow
// the mapEntryCustom permOverride method to get a chance
// to refuse edit privileges.
invf.clearPermCache(); // *sync*
}
}
return success;
}
/**
*
* Hook to have this object create a new embedded object
* in the given field.
*
*/
public Invid createNewEmbeddedObject(InvidDBField field)
{
DBEditObject newObject;
DBObjectBase targetBase;
DBObjectBaseField fieldDef;
ReturnVal retVal;
/* -- */
if (field.getID() == VOLUMES) // auxiliary volume mappings
{
fieldDef = field.getFieldDef();
if (fieldDef.getTargetBase() > -1)
{
newObject = getSession().createDBObject(fieldDef.getTargetBase(),
null, null);
return newObject.getInvid();
}
else
{
throw new RuntimeException("error in schema.. interface field target base not restricted..");
}
}
else
{
return null; // default
}
}
/**
*
* Customization method to control whether a specified field
* is required to be defined at commit time for a given object.<br><br>
*
* To be overridden in DBEditObject subclasses.<br><br>
*
* <b>*PSEUDOSTATIC*</b>
*
*/
public boolean fieldRequired(DBObject object, short fieldid)
{
if (object.isInactivated())
{
switch (fieldid)
{
case userSchema.USERNAME:
case userSchema.UID:
case userSchema.LOGINSHELL:
case userSchema.HOMEDIR:
case userSchema.VOLUMES:
return true;
}
}
else
{
switch (fieldid)
{
case userSchema.USERNAME:
case userSchema.PASSWORD:
case userSchema.SIGNATURE:
case userSchema.EMAILTARGET:
case userSchema.UID:
case userSchema.LOGINSHELL:
case userSchema.HOMEDIR:
case userSchema.VOLUMES:
return true;
}
}
return false;
}
/**
*
* Customization method to verify whether this object type has an inactivation
* mechanism.
*
* To be overridden in DBEditObject subclasses.<br><br>
*
* <b>*PSEUDOSTATIC*</b>
*
*/
public boolean canBeInactivated()
{
return true;
}
/**
*
* Customization method to verify whether the user has permission
* to inactivate a given object. The client's DBSession object
* will call this per-class method to do an object type-
* sensitive check to see if this object feels like being
* available for inactivating by the client.<br><br>
*
* To be overridden in DBEditObject subclasses.<br><br>
*
* <b>*PSEUDOSTATIC*</b>
*
*/
public boolean canInactivate(DBSession session, DBEditObject object)
{
return true;
}
/**
* This method handles inactivation logic for this object type. A
* DBEditObject must first be checked out for editing, then the
* inactivate() method can then be called on the object to put the
* object into inactive mode. inactivate() will set the object's
* removal date and fix up any other state information to reflect
* the object's inactive status.<br><br>
*
* inactive() is designed to run synchronously with the user's
* request for inactivation. It can return a wizard reference
* in the ReturnVal object returned, to guide the user through
* a set of interactive dialogs to inactive the object.<br><br>
*
* The inactive() method can cause other objects to be deleted, can cause
* strings to be removed from fields in other objects, whatever.<br><br>
*
* If remove() returns a ReturnVal that has its success flag set to false
* and does not include a JDialogBuff for further interaction with the
* user, then DBSEssion.inactivateDBObject() method will rollback any changes
* made by this method.<br><br>
*
* IMPORTANT NOTE: If a custom object's inactivate() logic decides
* to enter into a wizard interaction with the user, that logic is
* responsible for calling finalizeInactivate() with a boolean
* indicating ultimate success of the operation.<br><br>
*
* Finally, it is up to commitPhase1() and commitPhase2() to handle
* any external actions related to object inactivation when
* the transaction is committed..
*
* @param interactive If true, the inactivate() logic can present
* a wizard to the client to customize the inactivation logic.
*
* @see #commitPhase1()
* @see #commitPhase2()
*/
public ReturnVal inactivate()
{
return inactivate(null, false);
}
public ReturnVal inactivate(String forward, boolean calledByWizard)
{
ReturnVal retVal;
StringDBField stringfield;
PasswordDBField passfield;
DateDBField date;
Calendar cal = Calendar.getInstance();
Date time;
/* -- */
if (!gSession.enableWizards || calledByWizard)
{
// ok, we want to null the password field and set the
// removal time to current time + 3 months.
passfield = (PasswordDBField) getField(userSchema.PASSWORD);
retVal = passfield.setCryptPass(null); // we know our schema uses crypted pass'es
if (!ReturnVal.didSucceed(retVal))
{
finalizeInactivate(false);
return retVal;
}
// set the shell to /bin/false
stringfield = (StringDBField) getField(LOGINSHELL);
retVal = stringfield.setValueLocal("/bin/false");
if (!ReturnVal.didSucceed(retVal))
{
finalizeInactivate(false);
return retVal;
}
// reset the forwarding address?
if (forward != null)
{
stringfield = (StringDBField) getField(EMAILTARGET);
while (stringfield.size() > 0)
{
retVal = stringfield.deleteElement(0);
if (!ReturnVal.didSucceed(retVal))
{
finalizeInactivate(false);
return retVal;
}
}
stringfield.addElement(forward);
}
// make sure that the expiration date is cleared.. we're on
// the removal track now.
date = (DateDBField) getField(SchemaConstants.ExpirationField);
retVal = date.setValueLocal(null);
if (!ReturnVal.didSucceed(retVal))
{
finalizeInactivate(false);
return retVal;
}
// determine what will be the date 3 months from now
time = new Date();
cal.setTime(time);
cal.add(Calendar.MONTH, 3);
// and set the removal date
date = (DateDBField) getField(SchemaConstants.RemovalField);
retVal = date.setValueLocal(cal.getTime());
if (!ReturnVal.didSucceed(retVal))
{
finalizeInactivate(false);
return retVal;
}
finalizeInactivate(true);
// ok, we succeeded, now we have to tell the client
// what to refresh to see the inactivation results
ReturnVal result = new ReturnVal(true);
result.addRescanField(this.getInvid(), SchemaConstants.RemovalField);
result.addRescanField(this.getInvid(), userSchema.LOGINSHELL);
result.addRescanField(this.getInvid(), userSchema.EMAILTARGET);
return result;
}
else // interactive, but not called by wizard
{
userInactivateWizard theWiz;
try
{
if (debug)
{
System.err.println("userCustom: creating inactivation wizard");
}
theWiz = new userInactivateWizard(this.gSession, this);
}
catch (RemoteException ex)
{
throw new RuntimeException("oops, userCustom couldn't create wizard for remote ex " + ex);
}
if (debug)
{
System.err.println("userCustom: returning inactivation wizard");
}
return theWiz.getStartDialog();
}
}
/**
* This method handles reactivation logic for this object type. A
* DBEditObject must first be checked out for editing, then the
* reactivate() method can then be called on the object to make the
* object active again. reactivate() will clear the object's
* removal date and fix up any other state information to reflect
* the object's reactive status.<br><br>
*
* reactive() is designed to run synchronously with the user's
* request for inactivation. It can return a wizard reference
* in the ReturnVal object returned, to guide the user through
* a set of interactive dialogs to reactive the object.<br>
*
* If reactivate() returns a ReturnVal that has its success flag set to false
* and does not include a JDialogBuff for further interaction with the
* user, then DBSEssion.inactivateDBObject() method will rollback any changes
* made by this method.<br><br>
*
* IMPORTANT NOTE: If a custom object's reactivate() logic decides
* to enter into a wizard interaction with the user, that logic is
* responsible for calling editset.rollback("reactivate" +
* getLabel()) in the case of a failure to properly do all the reactivation
* stuff, where getLabel() must be the name of the object
* prior to any attempts to clear fields which could impact the
* returned label.<br><br>
*
* Finally, it is up to commitPhase1() and commitPhase2() to handle
* any external actions related to object reactivation when
* the transaction is committed..
*
* @see arlut.csd.ganymede.DBEditObject#commitPhase1()
* @see arlut.csd.ganymede.DBEditObject#commitPhase2()
*/
public ReturnVal reactivate()
{
userReactivateWizard theWiz;
/* -- */
try
{
if (debug)
{
System.err.println("userCustom: creating reactivation wizard");
}
theWiz = new userReactivateWizard(this.gSession, this);
}
catch (RemoteException ex)
{
throw new RuntimeException("oops, userCustom couldn't create wizard for remote ex " + ex);
}
if (debug)
{
System.err.println("userCustom: returning reactivation wizard");
}
return theWiz.getStartDialog();
}
/**
* This method is called by the userReactivateWizard on successfully
* obtaining the necessary information from the client on a
* reactivate operation. We then do the actual work to reactivate
* the user in this method.
*
* @see arlut.csd.ganymede.custom.userReactivateWizard
*
*/
public ReturnVal reactivate(userReactivateWizard reactivateWizard)
{
ReturnVal retVal = null;
StringDBField stringfield;
PasswordDBField passfield;
DateDBField date;
/* -- */
if (reactivateWizard != null)
{
// reset the password
if (reactivateWizard.password != null && reactivateWizard.password.length() != 0)
{
passfield = (PasswordDBField) getField(userSchema.PASSWORD);
retVal = passfield.setPlainTextPass(reactivateWizard.password);
if (!ReturnVal.didSucceed(retVal))
{
finalizeReactivate(false);
return retVal;
}
}
else
{
retVal = new ReturnVal(false);
JDialogBuff dialog = new JDialogBuff("Reactivate User",
"You must set a password",
"OK",
"Cancel",
"question.gif");
dialog.addPassword("New Password", true);
updateShellChoiceList();
dialog.addChoice("Shell", userCustom.shellChoices.getLabels());
dialog.addString("Forwarding Address");
retVal.setDialog(dialog);
retVal.setCallback(reactivateWizard);
reactivateWizard.state = 2; // make sure ths wizard will be ready to process this
return retVal;
}
// reset the shell
if (reactivateWizard.shell != null)
{
stringfield = (StringDBField) getField(LOGINSHELL);
retVal = stringfield.setValue(reactivateWizard.shell);
if (!ReturnVal.didSucceed(retVal))
{
finalizeReactivate(false);
return retVal;
}
}
// reset the forwarding address
if (reactivateWizard.forward != null)
{
stringfield = (StringDBField) getField(EMAILTARGET);
while (stringfield.size() > 0)
{
retVal = stringfield.deleteElement(0);
if (!ReturnVal.didSucceed(retVal))
{
finalizeReactivate(false);
return retVal;
}
}
stringfield.addElement(reactivateWizard.forward);
}
// make sure that the removal date is cleared..
date = (DateDBField) getField(SchemaConstants.RemovalField);
retVal = date.setValueLocal(null);
if (!ReturnVal.didSucceed(retVal))
{
finalizeReactivate(false);
return retVal;
}
finalizeReactivate(true);
// ok, we succeeded, now we have to tell the client
// what to refresh to see the reactivation results
ReturnVal result = new ReturnVal(true);
result.addRescanField(this.getInvid(), SchemaConstants.RemovalField);
result.addRescanField(this.getInvid(), userSchema.LOGINSHELL);
result.addRescanField(this.getInvid(), userSchema.EMAILTARGET);
return result;
}
return Ganymede.createErrorDialog("userCustom.reactivate() error",
"Error, reactivate() called without a valid user wizard");
}
/**
*
* This method returns a key that can be used by the client
* to cache the value returned by choices(). If the client
* already has the key cached on the client side, it
* can provide the choice list from its cache rather than
* calling choices() on this object again.<br><br>
*
* If there is no caching key, this method will return null.<br><br>
*
* We don't want the HOMEGROUP field's choice list to be cached on
* the client because it is dynamically generated for this
* context, and doesn't make sense in other contexts.
*
*/
public Object obtainChoicesKey(DBField field)
{
if (field.getID() == HOMEGROUP)
{
return null;
}
else
{
return super.obtainChoicesKey(field);
}
}
/**
*
* This method provides a hook that a DBEditObject subclass
* can use to indicate whether a given string field can only
* choose from a choice provided by obtainChoiceList()
*
*/
public boolean mustChoose(DBField field)
{
if (field.getID() == SIGNATURE)
{
// we want to force signature alias choosing
return true;
}
return super.mustChoose(field);
}
/**
*
* This method provides a hook that can be used to generate
* choice lists for invid and string fields that provide
* such. String and Invid DBFields will call their owner's
* obtainChoiceList() method to get a list of valid choices.
*
* Notice that fields 263 (login shell) and 268 (signature alias)
* do not have their choice lists cached on the client, because
* they are custom generated without any kind of accompanying
* cache key.
*
*/
public QueryResult obtainChoiceList(DBField field)
{
switch (field.getID())
{
case LOGINSHELL: // login shell
updateShellChoiceList();
if (debug)
{
System.err.println("userCustom: obtainChoice returning " + shellChoices + " for shell field.");
}
return shellChoices;
case HOMEGROUP: // home group
updateGroupChoiceList();
return groupChoices;
case SIGNATURE: // signature alias
QueryResult result = new QueryResult();
/* -- */
// our list of possible aliases includes the user's name
// note that we first check the new value, if any, for the
// user name.. this way the user rename code can change the
// signature alias without having the StringDBField for the
// signature alias reject the new name.
String name = (String) ((DBField) getField(USERNAME)).getNewValue();
if (name != null)
{
result.addRow(null, name, false);
}
else
{
name = (String) ((DBField) getField(USERNAME)).getValue();
if (name != null)
{
result.addRow(null, name, false);
}
}
// and any aliases defined
Vector values = ((DBField) getField(ALIASES)).getValues();
for (int i = 0; i < values.size(); i++)
{
result.addRow(null, (String) values.elementAt(i), false);
}
return result;
default:
return super.obtainChoiceList(field);
}
}
void updateGroupChoiceList()
{
if (groupChoices == null)
{
groupChoices = new QueryResult();
Vector invids = getFieldValuesLocal(GROUPLIST); // groups list
Invid invid;
for (int i = 0; i < invids.size(); i++)
{
invid = (Invid) invids.elementAt(i);
groupChoices.addRow(invid, gSession.viewObjectLabel(invid), true); // must be editable because the client cares
}
}
}
void updateShellChoiceList()
{
synchronized (shellChoices)
{
DBObjectBase base = Ganymede.db.getObjectBase("Shell Choice");
// just go ahead and throw the null pointer if we didn't get our base.
if (shellChoiceStamp == null || shellChoiceStamp.before(base.getTimeStamp()))
{
if (debug)
{
System.err.println("userCustom - updateShellChoiceList()");
}
shellChoices = new QueryResult();
Query query = new Query("Shell Choice", null, false);
// internalQuery doesn't care if the query has its filtered bit set
if (debug)
{
System.err.println("userCustom - issuing query");
}
Vector results = internalSession().internalQuery(query);
if (debug)
{
System.err.println("userCustom - processing query results");
}
for (int i = 0; i < results.size(); i++)
{
shellChoices.addRow(null, results.elementAt(i).toString(), false); // no invid
}
if (shellChoiceStamp == null)
{
shellChoiceStamp = new Date();
}
else
{
shellChoiceStamp.setTime(System.currentTimeMillis());
}
}
}
}
/**
*
* Customization method to allow this Ganymede object type to
* override the default permissions mechanism for special
* purposes.<br><br>
*
* If this method returns null, the default permissions mechanism
* will be followed. If not, the permissions system will grant
* the permissions specified by this method for access to the
* given field, and no further elaboration of the permission
* will be performed. Note that this override capability does
* not apply to operations performed in supergash mode.<br><br>
*
* This method should be used very sparingly.<br><br>
*
* To be overridden in DBEditObject subclasses.<br><br>
*
* <b>*PSEUDOSTATIC*</b>
*
*/
public PermEntry permOverride(GanymedeSession session, DBObject object, short fieldid)
{
if (fieldid != UID)
{
return null;
}
// we don't want to allow anyone other than supergash to change our
// uid once it is set.
if (object.getFieldValueLocal(UID) != null)
{
return new PermEntry(true, false, true, false);
}
else
{
return null;
}
}
/**
*
* This method is called after the set value operation has been ok'ed
* by any appropriate wizard code.
*
*/
public synchronized boolean finalizeSetValue(DBField field, Object value)
{
InvidDBField inv;
Vector personaeInvids;
Vector oldNames = new Vector();
DBSession session = editset.getSession();
DBEditObject eobj;
String oldName, suffix;
StringDBField sf;
boolean okay = true;
/* -- */
// we don't want to allow the home directory to be changed except
// by when the username field is being changed.
if (field.getID() == HOMEDIR)
{
String dir = (String) value;
/* -- */
if (homedir == null)
{
homedir = System.getProperty("ganymede.homedirprefix");
}
// we will only check against a defined prefix if
// we have set one in our properties file.
if (homedir != null && homedir.length() != 0)
{
sf = (StringDBField) getField(USERNAME);
if (sf != null)
{
if (sf.getNewValue() != null)
{
String expected = homedir + (String) sf.getNewValue();
if (!dir.equals(expected))
{
return false;
}
}
}
}
return true;
}
// when we rename a user, we have lots to do.. a number of other
// fields in this object and others need to be updated to match.
if (field.getID() == USERNAME)
{
// if we are being told to clear the user name field, go ahead and
// do it.. we assume this is being done by user removal logic,
// so we won't press the issue.
if (isDeleting() && (value == null))
{
return true;
}
// signature alias field will need to be rescanned,
// but we don't need to do the persona rename stuff.
sf = (StringDBField) getField(USERNAME); // old user name
oldName = (String) sf.getValueLocal();
if (oldName != null)
{
sf = (StringDBField) getField(SIGNATURE); // signature alias
// if the signature alias was the user's name, we'll want
// to continue that.
if (oldName.equals((String) sf.getValueLocal()))
{
sf.setValueLocal(value); // set the signature alias to the user's new name
}
}
// update the home directory location.. we assume that if
// the user has permission to rename the user, they can
// automatically execute this change to the home directory.
if (homedir == null)
{
homedir = System.getProperty("ganymede.homedirprefix");
}
// do we have a homedir prefix? if so, set the home dir here
if (homedir != null && homedir.length() != 0)
{
sf = (StringDBField) getField(HOMEDIR);
sf.setValueLocal(homedir + (String) value); // ** ARL
}
// if we don't have a signature set, set it to the username.
sf = (StringDBField) getField(SIGNATURE);
String sigVal = (String) sf.getValueLocal();
if (sigVal == null || sigVal.equals(oldName))
{
sf.setValueLocal(value);
}
// update the email target field. We want to look for
// oldName@arlut.utexas.edu and replace it if we find it.
sf = (StringDBField) getField(EMAILTARGET);
if (mailsuffix == null)
{
mailsuffix = System.getProperty("ganymede.defaultmailsuffix");
}
if (mailsuffix == null)
{
Ganymede.debug("Error in userCustom: couldn't find property ganymede.defaultmailsuffix!");
}
String oldMail = oldName + mailsuffix;
if (sf.containsElement(oldMail))
{
sf.deleteElement(oldMail);
sf.addElement(value + mailsuffix);
}
else if (sf.size() == 0)
{
sf.addElement(value + mailsuffix);
}
inv = (InvidDBField) getField(PERSONAE);
if (inv == null)
{
return true;
}
// rename all the associated personae with the new user name
personaeInvids = inv.getValues();
String tempString;
for (int i = 0; i < personaeInvids.size(); i++)
{
eobj = session.editDBObject((Invid) personaeInvids.elementAt(i));
sf = (StringDBField) eobj.getField(SchemaConstants.PersonaNameField);
oldName = (String) sf.getValue();
oldNames.addElement(oldName);
suffix = oldName.substring(oldName.indexOf(':')+1);
tempString = value + ":" + suffix;
if (debug)
{
System.err.println("trying to rename admin persona " + oldName + " to "+ tempString);
}
ReturnVal retVal = sf.setValueLocal(tempString);
if (!ReturnVal.didSucceed(retVal))
{
if (okay)
{
return false;
}
else
{
// crap. we've changed at least one persona
// object, but we can't change all of them. So,
// let's try our best to undo what we did.
for (int j = 0; j < i; j++)
{
eobj = session.editDBObject((Invid) personaeInvids.elementAt(j));
sf = (StringDBField) eobj.getField(SchemaConstants.PersonaNameField);
sf.setValueLocal(oldNames.elementAt(j));
}
return false;
}
}
else
{
okay = false; // we've made a change, and we can't just return false
}
}
}
return true;
}
/**
*
* This is the hook that DBEditObject subclasses use to interpose wizards whenever
* a sensitive field is being changed.
*
*/
public ReturnVal wizardHook(DBField field, int operation, Object param1, Object param2)
{
userHomeGroupDelWizard groupWizard = null;
userRenameWizard renameWizard = null;
ReturnVal result;
/* -- */
// if the groups field is being changed, we may need to intervene
if (debug)
{
System.err.println("userCustom ** entering wizardHook, field = " +
field.getName() + ", op= " + operation);
}
try
{
// if we are changing the list of email aliases, we'll want
// to update the list of choices for the signature field.
if (field.getID() == ALIASES)
{
// the second true in the ReturnVal constructor makes the
// Ganymede logic go ahead and complete the operation
// normally, just taking the rescan information as an
// extra to pass back to the client.
result = new ReturnVal(true, true);
result.addRescanField(this.getInvid(), userSchema.SIGNATURE);
return result;
}
if (field.getID() == GROUPLIST)
{
switch (operation)
{
case ADDELEMENT:
// ok, no big deal, but we will need to have the client
// rescan the choice list for the home group field
result = new ReturnVal(true, true);
result.addRescanField(this.getInvid(), HOMEGROUP);
groupChoices = null;
return result;
case DELELEMENT:
// ok, this is more of a big deal.. first, see if the value
// being deleted is the home group. If not, still no big
// deal.
int index = ((Integer) param1).intValue();
Vector valueAry = getFieldValuesLocal(GROUPLIST);
Invid delVal = (Invid) valueAry.elementAt(index);
if (debug)
{
System.err.println("userCustom: deleting group element " +
gSession.viewObjectLabel(delVal));
}
if (!delVal.equals(getFieldValueLocal(HOMEGROUP)))
{
// whew, no big deal.. they are not removing the
// home group. The client will need to rescan,
// but no biggie.
if (debug)
{
System.err.println("userCustom: I don't think " +
gSession.viewObjectLabel(delVal) +
" is the home group");
}
result = new ReturnVal(true, true);
result.addRescanField(this.getInvid(), HOMEGROUP);
groupChoices = null;
return result;
}
if (gSession.isWizardActive() &&
gSession.getWizard() instanceof userHomeGroupDelWizard)
{
groupWizard = (userHomeGroupDelWizard) gSession.getWizard();
if (groupWizard.getState() == groupWizard.DONE)
{
// ok, assume the wizard has taken care of getting everything prepped and
// approved for us. An active wizard has approved the operation
groupWizard.unregister();
return null;
}
else
{
if (groupWizard.userObject != this)
{
System.err.println("userCustom.wizardHook(): bad object");
}
if (groupWizard.getState() != groupWizard.DONE)
{
System.err.println("userCustom.wizardHook(): bad state: " +
groupWizard.getState());
}
groupWizard.unregister();
return Ganymede.createErrorDialog("User Object Error",
"The client is attempting to do an operation on " +
"a user object with an active wizard.");
}
}
else if (gSession.isWizardActive() &&
!(gSession.getWizard() instanceof userHomeGroupDelWizard))
{
return Ganymede.createErrorDialog("User Object Error",
"The client is attempting to do an operation on " +
"a user object with mismatched active wizard.");
}
// eek. they are deleting the home group. Why Lord, why?!
try
{
groupWizard = new userHomeGroupDelWizard(this.gSession,
this,
param1);
}
catch (RemoteException ex)
{
throw new RuntimeException("Couldn't create userWizard " + ex.getMessage());
}
// if we get here, the wizard was able to register itself.. go ahead
// and return the initial dialog for the wizard. The ReturnVal code
// that wizard.getStartDialog() returns will have the success code
// set to false, so whatever triggered us will prematurely exit,
// returning the wizard's dialog.
return groupWizard.getStartDialog();
}
}
if ((field.getID() != USERNAME) ||
(operation != SETVAL))
{
return null; // by default, we just ok whatever else
}
// ok, we're doing a user rename.. check to see if we need to do a
// wizard
// If this is a newly created user, we won't pester them about setting
// or changing the user name field.
if ((field.getValue() == null) || (getStatus() == ObjectStatus.CREATING))
{
result = new ReturnVal(true, true); // have setValue() do the right thing
result.addRescanField(this.getInvid(), userSchema.HOMEDIR);
result.addRescanField(this.getInvid(), userSchema.ALIASES);
result.addRescanField(this.getInvid(), userSchema.SIGNATURE);
result.addRescanField(this.getInvid(), userSchema.VOLUMES);
result.addRescanField(this.getInvid(), userSchema.EMAILTARGET);
return result;
}
String oldname = (String) field.getValue();
if (!gSession.enableWizards)
{
return null; // no wizards if the user is non-interactive.
}
// Huh! Wizard time! We'll check here to see if there is a
// registered userRenameWizard in the system taking care of us.
if (gSession.isWizardActive() && gSession.getWizard() instanceof userRenameWizard)
{
renameWizard = (userRenameWizard) gSession.getWizard();
if ((renameWizard.getState() == renameWizard.DONE) &&
(renameWizard.field == field) &&
(renameWizard.userObject == this) &&
(renameWizard.newname == param1))
{
// ok, assume the wizard has taken care of getting
// everything prepped and approved for us. An active
// wizard has approved the operation
renameWizard.unregister();
// note that we don't have to return the rescan fields
// directive here.. the active wizard is what is going to
// respond directly to the user, we are presumably just
// here because the wizard task-completion code went ahead
// and called setValue on the user's name.. we'll trust
// that code to return the rescan indicators.
return null;
}
else
{
if (renameWizard.field != field)
{
System.err.println("userCustom.wizardHook(): bad field");
}
if (renameWizard.userObject != this)
{
System.err.println("userCustom.wizardHook(): bad object");
}
if (renameWizard.newname != param1)
{
System.err.println("userCustom.wizardHook(): bad param");
}
if (renameWizard.getState() != renameWizard.DONE)
{
System.err.println("userCustom.wizardHook(): bad state: " +
renameWizard.getState());
}
renameWizard.unregister();
return Ganymede.createErrorDialog("User Object Error",
"The client is attempting to do an operation on " +
"a user object with an active wizard.");
}
}
else if (gSession.isWizardActive() && !(gSession.getWizard() instanceof userRenameWizard))
{
return Ganymede.createErrorDialog("User Object Error",
"The client is attempting to do an operation on " +
"a user object with mismatched active wizard.");
}
else
{
// there's no wizard active, and this operation has to be approved by one. Go ahead
// and set up the wizard and let the client play with it.
// if we're setting the field to null, don't need to pass it through
// a wizard.. we're probably just deleting this user.
if (isDeleting() && (param1 == null))
{
return null;
}
try
{
// Mike Jittlov is the Wizard of Speed and Time
renameWizard = new userRenameWizard(this.gSession,
this,
field,
(String) param1,
oldname);
}
catch (RemoteException ex)
{
throw new RuntimeException("Couldn't create userWizard " + ex.getMessage());
}
// if we get here, the wizard was able to register itself.. go ahead
// and return the initial dialog for the wizard. The ReturnVal code
// that wizard.getStartDialog() returns will have the success code
// set to false, so whatever triggered us will prematurely exit,
// returning the wizard's dialog.
return renameWizard.getStartDialog();
}
}
finally
{
if (debug)
{
System.err.println("userCustom ** exiting wizardHook");
}
}
}
/**
*
* This method is a hook for subclasses to override to
* pass the phase-two commit command to external processes.<br><br>
*
* For normal usage this method would not be overridden. For
* cases in which change to an object would result in an external
* process being initiated whose success or failure would not
* affect the successful commit of this DBEditObject in the
* Ganymede server, the process invokation should be placed here,
* rather than in commitPhase1().<br><br>
*
* Subclasses that override this method may wish to make this method
* synchronized.
*
* @see arlut.csd.ganymede.DBEditSet
*/
public void commitPhase2()
{
switch (getStatus())
{
case DROPPING:
// the user never really existed.. no external actions required.
break;
case CREATING:
// handle creating the user.. creating their home directory, setting
// up their mail spool, etc., etc.
createUserExternals();
break;
case DELETING:
deleteUserExternals();
break;
case EDITING:
// did the user's name change?
String name = getLabel();
String oldname = original.getLabel();
if (!name.equals(oldname))
{
handleUserRename();
}
// did we change home directory volumes?
Invid volumeId;
String volumeName;
DBObject mapEntry;
Invid mapInvid;
Vector newEntries = getFieldValuesLocal(userSchema.VOLUMES);
Vector oldEntries = original.getFieldValuesLocal(userSchema.VOLUMES);
int newSize;
int oldSize;
if (newEntries != null)
{
newSize = newEntries.size() + 1;
}
else
{
newSize = 0;
}
if (oldEntries != null)
{
oldSize = oldEntries.size() + 1;
}
else
{
oldSize = 0;
}
Hashtable newVolumes = new Hashtable(newSize, 1.0f);
Hashtable oldVolumes = new Hashtable(oldSize, 1.0f);
Vector addedVolumes = new Vector();
Vector deletedVolumes = new Vector();
if (oldEntries != null)
{
for (int i = 0; i < oldEntries.size(); i++)
{
mapInvid = (Invid) oldEntries.elementAt(i);
mapEntry = (DBObject) getSession().viewDBObject(mapInvid);
volumeId = (Invid) mapEntry.getFieldValueLocal(mapEntrySchema.VOLUME);
volumeName = gSession.viewObjectLabel(volumeId);
oldVolumes.put(volumeId, volumeName);
}
}
if (newEntries != null)
{
for (int i = 0; i < newEntries.size(); i++)
{
mapInvid = (Invid) newEntries.elementAt(i);
mapEntry = (DBObject) getSession().viewDBObject(mapInvid);
volumeId = (Invid) mapEntry.getFieldValueLocal(mapEntrySchema.VOLUME);
volumeName = gSession.viewObjectLabel(volumeId);
newVolumes.put(volumeId, volumeName);
if (!oldVolumes.containsKey(volumeId))
{
addedVolumes.addElement(volumeName);
}
}
}
Enumeration oldValues = oldVolumes.keys();
while (oldValues.hasMoreElements())
{
Object key = oldValues.nextElement();
if (!newVolumes.containsKey(key))
{
deletedVolumes.addElement(oldVolumes.get(key));
}
}
// okay, addedVolumes and deletedVolumes have a list of changes..
if (addedVolumes.size() != 0 && deletedVolumes.size() != 0)
{
handleUserDirectoryChange(addedVolumes, deletedVolumes);
}
}
return;
}
/**
*
* This method runs from userCustom's commitPhase2() and runs an external
* script that can create the user's home directory, and anything else
* that might need doing.
*
*/
private void createUserExternals()
{
if (debug)
{
System.err.println("userCustom: " + getLabel() + ", in createUserExternals().");
}
}
/**
*
* This method runs from userCustom's commitPhase2() and runs an external
* script that can do whatever bookkeeping might be desired when a user
* is taken out of the passwd/user_info file generated by Ganymede. This
* may include removing the user's mailbox, home directory, and files, or
* simply notifying someone that the user is no longer valid.
*
*/
private void deleteUserExternals()
{
if (debug)
{
System.err.println("userCustom: " + getLabel() + ", in deleteUserExternals().");
}
}
/**
*
* This method takes care of executing whatever external code is required
* to handle this user being moved from volume to volume
*
*/
private void handleUserDirectoryChange(Vector addedVolumes, Vector deletedVolumes)
{
if (debug)
{
System.err.println("userCustom.handleUserDirectoryChange(): user " + getLabel());
if (addedVolumes != null)
{
System.err.print("has been added to");
for (int i = 0; i < addedVolumes.size(); i++)
{
System.err.print(" ");
System.err.print(addedVolumes.elementAt(i));
}
System.err.println();
}
if (addedVolumes != null)
{
System.err.print("has been deleted from");
for (int i = 0; i < deletedVolumes.size(); i++)
{
System.err.print(" ");
System.err.print(deletedVolumes.elementAt(i));
}
System.err.println();
}
}
}
private void handleUserRename()
{
if (debug)
{
System.err.println("userCustom.handleUserRename(): user " + original.getLabel() +
"has been renamed to " + getLabel());
}
}
}