/*
groupCustom.java
This file is a management class for group objects in Ganymede.
Created: 30 July 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
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.gasharl;
import java.io.File;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Calendar;
import java.util.Date;
import java.util.Vector;
import org.doomdark.uuid.EthernetAddress;
import org.doomdark.uuid.UUIDGenerator;
import arlut.csd.Util.FileOps;
import arlut.csd.Util.PathComplete;
import arlut.csd.Util.VectorUtils;
import arlut.csd.ganymede.common.Invid;
import arlut.csd.ganymede.common.NotLoggedInException;
import arlut.csd.ganymede.common.ReturnVal;
import arlut.csd.ganymede.common.SchemaConstants;
import arlut.csd.ganymede.rmi.db_field;
import arlut.csd.ganymede.rmi.db_object;
import arlut.csd.ganymede.server.DBEditObject;
import arlut.csd.ganymede.server.DBEditSet;
import arlut.csd.ganymede.server.DBField;
import arlut.csd.ganymede.server.DBNameSpace;
import arlut.csd.ganymede.server.DBObject;
import arlut.csd.ganymede.server.DBObjectBase;
import arlut.csd.ganymede.server.DBSession;
import arlut.csd.ganymede.server.DateDBField;
import arlut.csd.ganymede.server.Ganymede;
import arlut.csd.ganymede.server.GanymedeSession;
import arlut.csd.ganymede.server.InvidDBField;
import arlut.csd.ganymede.server.NumericDBField;
import arlut.csd.ganymede.server.StringDBField;
/*------------------------------------------------------------------------------
class
groupCustom
------------------------------------------------------------------------------*/
public class groupCustom extends DBEditObject implements SchemaConstants, groupSchema {
static final boolean debug = false;
static final boolean debug2 = false;
/**
*
* Customization Constructor
*
*/
public groupCustom(DBObjectBase objectBase)
{
super(objectBase);
}
/**
*
* Create new object constructor
*
*/
public groupCustom(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 groupCustom(DBObject original, DBEditSet editset)
{
super(original, editset);
}
/**
* <p>Initializes a newly created DBEditObject.</p>
*
* <p>When this method is called, the DBEditObject has been created,
* its ownership set, and all fields defined in the controlling
* {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase} have
* been instantiated without defined values. If this DBEditObject
* is an embedded type, it will have been linked into its parent
* object before this method is called.</p>
*
* <p>This method is responsible for filling in any default values
* that can be calculated from the {@link
* arlut.csd.ganymede.server.DBSession DBSession} associated with
* the editset defined in this DBEditObject.</p>
*
* <p>If initialization fails for some reason, initializeNewObject()
* will return a ReturnVal with an error result.. If the owning
* GanymedeSession is not in bulk-loading mode (i.e.,
* GanymedeSession.enableOversight is true), {@link
* arlut.csd.ganymede.server.DBSession#createDBObject(short,
* arlut.csd.ganymede.common.Invid, java.util.Vector)
* DBSession.createDBObject()} will checkpoint the transaction
* before calling this method. If this method returns a failure
* code, 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.</p>
*
* <p>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.</p>
*
* <p>This method should be overridden in subclasses.</p>
*
* @return A ReturnVal indicating success or failure. May
* be simply 'null' to indicate success if no feedback need
* be provided.
*/
@Override public ReturnVal initializeNewObject()
{
ReturnVal retVal;
Integer gidVal = new Integer(2000);
/* -- */
// we don't want to do any of this initialization during
// bulk-loading.
if (!getGSession().enableOversight)
{
return null;
}
// need to find a global unique id (guid) for this user
StringDBField guidField = getStringField(GUID);
if (guidField == null)
{
return Ganymede.createErrorDialog(this.getGSession(),
"Group Initialization Failure",
"Couldn't find the guid field.. schema problem?");
}
String guid = generateGUID(); // create a globally unique uid
retVal = guidField.setValueLocal(guid);
if (retVal != null && !retVal.didSucceed())
{
return retVal;
}
// need to find a gid for this group
// see if we have an owner set, check it for our starting gid
Vector<Invid> owners = (Vector<Invid>) getFieldValuesLocal(SchemaConstants.OwnerListField);
if (owners.size() > 0)
{
Invid primaryOwner = owners.get(0);
DBObject owner = getDBSession().viewDBObject(primaryOwner);
if (owner != null)
{
// field 256 in the owner group is the GASHARL starting
// uid/gid
gidVal = (Integer) owner.getFieldValueLocal((short) 256);
if (gidVal == null)
{
gidVal = new Integer(1001);
}
}
}
NumericDBField numField = getNumericField((short) 258);
if (numField == null)
{
return Ganymede.createErrorDialog(this.getGSession(),
"Group Initialization Failure",
"Couldn't find the gid field.. schema problem?");
}
DBNameSpace namespace = numField.getNameSpace();
if (namespace == null)
{
return Ganymede.createErrorDialog(this.getGSession(),
"Group Initialization Failure",
"Couldn't find the gid namespace.. schema problem?");
}
// now, find a gid.. unfortunately, we have to use immutable Integers here.. not
// the most efficient at all.
while (!namespace.reserve(editset, gidVal))
{
gidVal = new Integer(gidVal.intValue()+1);
}
// we use setValueLocal so we can set a value that the user can't edit.
retVal = numField.setValueLocal(gidVal);
return retVal;
}
/**
* <p>Customization method to verify whether this object type has an inactivation
* mechanism.</p>
*
* <p>To be overridden on necessity in DBEditObject subclasses.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*/
@Override public boolean canBeInactivated()
{
return true;
}
/**
* <p>Customization method to verify whether the user has permission
* to inactivate a given object. The client's
* {@link arlut.csd.ganymede.server.DBSession 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.</p>
*
* <p>Note that unlike canRemove(), canInactivate() takes a
* DBEditObject instead of a DBObject. This is because inactivating
* an object is based on editing the object, and so we have the
* GanymedeSession/DBSession classes go ahead and check the object
* out for editing before calling us. This serves to force the
* session classes to check for write permission before attempting
* inactivation.</p>
*
* <p>Use canBeInactivated() to test for the presence of an inactivation
* protocol outside of an edit context if needed.</p>
*
* <p>To be overridden on necessity in DBEditObject subclasses.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*/
@Override public boolean canInactivate(DBSession session, DBEditObject object)
{
return true;
}
/**
* <p>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.</p>
*
* <p>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.</p>
*
* <p>The inactive() method can cause other objects to be deleted,
* can cause strings to be removed from fields in other objects,
* whatever.</p>
*
* <p>If inactivate() 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.</p>
*
* <p>If inactivate() returns a success value, we expect that the
* object will have a removal date set.</p>
*
* <p>IMPORTANT NOTE 1: This method is intended to be called by the
* DBSession.inactivateDBObject() method, which establishes a
* checkpoint before calling inactivate. If this method is not
* called by DBSession.inactivateDBObject(), you need to push a
* checkpoint with the key 'inactivate'+label, where label is the
* returned name of this object.</p>
*
* <p>IMPORTANT NOTE 2: 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.</p>
*
* <p>Finally, it is up to commitPhase1() and commitPhase2() to
* handle any external actions related to object inactivation when
* the transaction is committed..</p>
*
* @see arlut.csd.ganymede.server.DBEditObject#commitPhase1()
* @see arlut.csd.ganymede.server.DBEditObject#commitPhase2()
*
* @param ckp_label The checkpoint label which should be popped or
* rolledback on necessity by the custom inactivate method.
*
* @return A ReturnVal indicating success or failure. May
* be simply 'null' to indicate success if no feedback need
* be provided.
*/
@Override public ReturnVal inactivate(String ckp_label)
{
return inactivate(false, false, ckp_label);
}
public ReturnVal inactivate(boolean suceeded, boolean fromWizard, String ckp_label)
{
ReturnVal retVal = null;
groupInactivateWizard wiz;
/* -- */
if (fromWizard)
{
if (suceeded)
{
if (debug)
{
System.err.println("groupCustom.inactivate: setting removal date.");
}
DateDBField date;
Calendar cal = Calendar.getInstance();
Date time;
// make sure that the expiration date is cleared.. we're on
// the removal track now.
date = getDateField(SchemaConstants.ExpirationField);
retVal = date.setValueLocal(null);
if (retVal != null && !retVal.didSucceed())
{
super.finalizeInactivate(false, ckp_label);
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
if (debug)
{
System.err.println("groupCustom.inactivate: setting removal date to: " + cal.getTime());
}
date = getDateField(SchemaConstants.RemovalField);
retVal = date.setValueLocal(cal.getTime());
if ((retVal == null) || (retVal.didSucceed()))
{
if (debug)
{
System.err.println("groupCustom.inactivate: retVal returns true, I am finalizing.");
}
finalizeInactivate(true, ckp_label);
}
return retVal;
}
else
{
finalizeInactivate(false, ckp_label);
return new ReturnVal(false);
}
}
else // not called by wizard
{
if (getEditSet().isInteractive())
{
try
{
if (debug)
{
System.err.println("groupCustom.inactivate: Starting new groupInactivateWizard");
}
wiz = new groupInactivateWizard(this.gSession, this, ckp_label);
return wiz.respond(null);
}
catch (RemoteException rx)
{
throw new RuntimeException("Could not create groupInactivateWizard: " + rx);
}
}
else
{
// non-interactive. we can only inactivate if there are no home users
// in this group
InvidDBField homeField = getInvidField(groupSchema.HOMEUSERS);
if (homeField.size() == 0)
{
return this.inactivate(true, true, ckp_label);
}
else
{
return this.inactivate(false, true, ckp_label);
}
}
}
}
/**
* <p>Customization method to control whether a specified field
* is required to be defined at commit time for a given object.</p>
*
* <p>To be overridden on necessity in DBEditObject subclasses.</p>
*
* <p>Note that this method will not be called if the controlling
* GanymedeSession's enableOversight is turned off, as in
* bulk loading.</p>
*
* <p>Note as well that the designated label field for objects are
* always required, whatever this method returns, and that this
* requirement holds without regard to the GanymedeSession's
* enableOversight value.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*/
@Override public boolean fieldRequired(DBObject object, short fieldid)
{
switch (fieldid)
{
case groupSchema.GROUPNAME:
case groupSchema.GID:
return true;
}
return false;
}
/**
* <p>Customization method to verify overall consistency of a
* DBObject. This method is intended to be overridden in
* DBEditObject subclasses, and will be called by {@link
* arlut.csd.ganymede.server.DBEditObject#commitPhase1()
* commitPhase1()} to verify the readiness of this object for
* commit. The DBObject passed to this method will be a
* DBEditObject, complete with that object's GanymedeSession
* reference if this method is called during transaction commit, and
* that session reference may be used by the verifying code if the
* code needs to access the database.</p>
*
* <p>This method is for custom checks specific to custom
* DBEditObject subclasses. Standard checking for missing fields
* for which fieldRequired() returns true is done by {@link
* arlut.csd.ganymede.server.DBEditSet#commit_checkObjectMissingFields(arlut.csd.ganymede.server.DBEditObject)}
* during {@link
* arlut.csd.ganymede.server.DBEditSet#commit_handlePhase1()}.</p>
*
* <p>To be overridden on necessity in DBEditObject subclasses.</p>
*
* <p><b>*PSEUDOSTATIC*</b></p>
*
* @return A ReturnVal indicating success or failure. May
* be simply 'null' to indicate success if no feedback need
* be provided.
*/
@Override public ReturnVal consistencyCheck(DBObject object)
{
GanymedeSession gsession = object.getGSession();
Vector<Invid> users = (Vector<Invid>) object.getFieldValuesLocal(USERS);
Vector<Invid> homeUsers = (Vector<Invid>) object.getFieldValuesLocal(HOMEUSERS);
Vector<Invid> diff = VectorUtils.difference(homeUsers, users);
if (diff.size() != 0)
{
Vector<String> names = new Vector<String>();
for (Invid objId: diff)
{
if (gsession != null)
{
names.add(gsession.getDBSession().getObjectLabel(objId));
}
else
{
names.add(objId.toString());
}
}
return Ganymede.createErrorDialog(object.getGSession(),
"Group Consistency Violation",
"Error, the following users have group " + object.getLabel() + " listed as their home " +
"group, but are not listed as normal members of the group:\n\n" +
VectorUtils.vectorString(names));
}
// okay, then
return null;
}
/**
* <p>This method is a hook for subclasses to override to
* pass the phase-two commit command to external processes.</p>
*
* <p>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 <b>success or failure would not
* affect the successful commit of this DBEditObject in the
* Ganymede server</b>, the process invocation should be placed here,
* rather than in
* {@link arlut.csd.ganymede.server.DBEditObject#commitPhase1() commitPhase1()}.</p>
*
* <p>commitPhase2() is generally the last method called on a
* DBEditObject before it is discarded by the server in the
* {@link arlut.csd.ganymede.server.DBEditSet DBEditSet}
* {@link arlut.csd.ganymede.server.DBEditSet#commit(java.lang.String) commit()} method.</p>
*
* <p>Subclasses that override this method may wish to make this method
* synchronized.</p>
*
* <p><b>WARNING!</b> this method is called at a time when portions
* of the database are locked for the transaction's integration into
* the database. You must not call methods that seek to gain a lock
* on the Ganymede database. At this point, this means no composite
* queries on embedded object types, where you seek an object based
* on a field in an embedded object and in the object itself, using
* the GanymedeSession query calls, or else you will lock the server.</p>
*
* <p>This method should NEVER try to edit or change any DBEditObject
* in the server.. at this point in the game, the server has fixed the
* transaction working set and is depending on commitPhase2() not trying
* to make changes internal to the server.</p>
*
* <p>To be overridden on necessity in DBEditObject subclasses.</p>
*/
@Override public void commitPhase2()
{
switch (getStatus())
{
case DROPPING:
break;
case CREATING:
break;
case DELETING:
handleGroupDelete(original.getLabel());
break;
case EDITING:
String name = getLabel();
String oldname = original.getLabel();
if (!name.equals(oldname))
{
handleGroupRename(oldname, name);
}
}
return;
}
/**
* <p>This method is the hook that DBEditObject subclasses use to interpose
* {@link arlut.csd.ganymede.server.GanymediatorWizard wizards} when a field's
* value is being changed.</p>
*
* <p>Whenever a field is changed in this object, this method will be
* called with details about the change. This method can refuse to
* perform the operation, it can make changes to other objects in
* the database in response to the requested operation, or it can
* choose to allow the operation to continue as requested.</p>
*
* <p>In the latter two cases, the wizardHook code may specify a list
* of fields and/or objects that the client may need to update in
* order to maintain a consistent view of the database.</p>
*
* <p>If server-local code has called
* {@link arlut.csd.ganymede.server.GanymedeSession#enableOversight(boolean)
* enableOversight(false)},
* this method will never be
* called. This mode of operation is intended only for initial
* bulk-loading of the database.</p>
*
* <p>This method may also be bypassed when server-side code uses
* setValueLocal() and the like to make changes in the database.</p>
*
* <p>This method is called before the finalize*() methods.. the finalize*()
* methods is where last minute cascading changes should be performed..
* Note as well that wizardHook() is called before the namespace checking
* for the proposed value is performed, while the finalize*() methods are
* called after the namespace checking.</p>
*
* <p>The operation parameter will be a small integer, and should hold one of the
* following values:</p>
*
* <dl>
* <dt>1 - SETVAL</dt>
* <dd>This operation is used whenever a simple scalar field is having
* it's value set. param1 will be the value being placed into the field.</dd>
* <dt>2 - SETELEMENT</dt>
* <dd>This operation is used whenever a value in a vector field is being
* set. param1 will be an Integer holding the element index, and
* param2 will be the value being set.</dd>
* <dt>3 - ADDELEMENT</dt>
* <dd>This operation is used whenever a value is being added to the
* end of a vector field. param1 will be the value being added.</dd>
* <dt>4 - DELELEMENT</dt>
* <dd>This operation is used whenever a value in a vector field is being
* deleted. param1 will be an Integer holding the element index.</dd>
* <dt>5 - ADDELEMENTS</dt>
* <dd>This operation is used whenever a set of elements is being
* added to a vector field en masse. param1 will be a Vector containing
* the values that are being added.</dd>
* <dt>6 - DELELEMENTS</dt>
* <dd>This operation is used whenever a set of elements is being
* deleted from a vector field en masse. param1 will be a Vector containing
* the values that are being deleted.</dd>
* <dt>7 - SETPASSPLAIN</dt>
* <dd>This operation is used when a password field is having its password
* set using a plaintext source. param1 will be a String containing the
* submitted password, or null if the password is being cleared.</dd>
* <dt>8 - SETPASSCRYPT</dt>
* <dd>This operation is used when a password field is having its password
* set using a UNIX crypt() hashed source. param1 will be a String containing the
* submitted hashed password, or null if the password is being cleared.</dd>
* <dt>9 - SETPASSMD5</dt>
* <dd>This operation is used when a password field is having its password
* set using an md5Ccrypt() hashed source. param1 will be a String containing the
* submitted hashed password, or null if the password is being cleared.</dd>
* <dt>10 - SETPASSWINHASHES</dt>
* <dd>This operation is used when a password field is having its password
* set using Windows style password hashes. param1 will be the password in
* LANMAN hash form, param2 will be the password in NT Unicode MD4 hash
* form. Either or both of param1 and param2 may be null.</dd>
* <dt>11 - SETPASSAPACHEMD5</dt>
* <dd>This operation is used when a password field is having its
* password set using the Apache variant of the md5crypt algorithm.
* param1 will be the password in Apache md5crypt hash form, or null
* if the password hash is being cleared. param2 will be null.</dd>
* <dt>12 - SETPASSSSHA</dt>
* <dd>This operation is used when a password field is having its
* password set using the OpenLDAP-style SSHA password hash. param1
* will be the password in SSHA form, or null if the password is
* being cleared. param2 will be null.</dd>
* <dt>13 - SETPASS_SHAUNIXCRYPT</dt>
* <dd>This operation is used when a password field is having its
* password set using Ulrich Drepper's SHA256 or SHA512 Unix Crypt
* algorithms. param1 will be the password in SHA Unix Crypt form,
* or null if the password is being cleared. param2 will be
* null.</dd>
* <dt>14 - SETPASS_BCRYPT</dt>
* <dd>This operation is used when a password field is having its
* password set using the OpenBSD-style BCrypt password hash. param1
* will be the password in BCrypt form, or null if the password is
* being cleared. param2 will be null.</dd>
* </dl>
*
* <p>To be overridden on necessity in DBEditObject subclasses.</p>
*
* @return null if the operation is approved without comment, or a
* ReturnVal object indicating success or failure, objects and
* fields to be rescanned by the client, and a doNormalProcessing
* flag that will indicate to the field code whether or not the
* operation should continue to completion using the field's
* standard logic. <b>It is very important that wizardHook return a
* new ReturnVal(true, true) if the wizardHook wishes to simply
* specify rescan information while having the field perform its
* standard operation.</b> wizardHook() may return new
* ReturnVal(true, false) if the wizardHook performs the operation
* (or a logically related operation) itself. The same holds true
* for the respond() method in GanymediatorWizard subclasses.
*/
@Override public ReturnVal wizardHook(DBField field, int operation, Object param1, Object param2)
{
try
{
groupHomeGroupWizard homeWizard;
ReturnVal retVal = null;
/* -- */
// First find out what they are changing
if (debug)
{
System.out.println("Field name: " + field.getName() +
" Field typeDesc: " + field.getTypeDesc());
}
if (field.getID() == HOMEUSERS) // from groupSchema
{
// What are they doing to the home users?
switch (operation)
{
case ADDELEMENT:
case ADDELEMENTS:
if (debug)
{
print("it's an ADDELEMENT, ignoring it.");
}
// we don't need to rescan anything, do we?
return null;
case DELELEMENTS:
if (gSession == null)
{
// If there is no session, the server is doing something special.
// Assume the server knows what is going on, and let it do the deed.
return null;
}
else
{
return Ganymede.createErrorDialog(this.getGSession(),
"Group Validation Error",
"Can't do bulk removal of home group entries right now.");
}
case DELELEMENT:
if (debug)
{
print("HOMEUSERS field changing.");
}
Vector<Invid> users = (Vector<Invid>) getFieldValuesLocal(HOMEUSERS);
int index = ((Integer) param1).intValue();
Invid userInvid = users.get(index);
if (gSession == null)
{
// If there is no session, the server is doing something special.
// Assume the server knows what is going on, and let it do the deed.
return null;
}
else if (!gSession.enableWizards)
{
// Stupid client won't let us show wizards. We'll teach them!
// First, find out what is going on. How many groups is this user in?
db_object user = gSession.edit_db_object(userInvid).getObject();
int size = 0;
try
{
size = user.getField(userSchema.GROUPLIST).size();
}
catch (RemoteException rx)
{
throw new RuntimeException("How come I can't talk to the server, when I AM the server? " + rx);
}
if (size == 2)
{
// They belong to two groups: this one, and one other one.
// We will make the other one the home group.
try
{
db_field groupListField = user.getField(userSchema.GROUPLIST);
Vector<Invid> groupList = (Vector<Invid>) groupListField.getValues();
for (Invid group: groupList)
{
if (!this.equals(group))
{
// this will be the new home group
if (debug)
{
print("Found the other group, changing the user's home group.");
}
db_field homeGroup = user.getField(userSchema.HOMEGROUP);
retVal = homeGroup.setValue(group);
break;
}
}
}
catch (RemoteException rx)
{
throw new RuntimeException("Again, with the remote exceptions: " + rx);
}
}
else if (size < 1)
{
// They are only in one group, so what good is that?
return Ganymede.createErrorDialog(this.getGSession(),
"Group Change Failed",
"This user has this group for a home group. " +
" You cannot remove this user, since this is his only group.");
}
else
{
return Ganymede.createErrorDialog(this.getGSession(),
"Group Change Failed",
"This user has many groups to choose from. " +
" You must choose one to be the home group, or turn wizards on.");
}
}
// This calls for a wizard. See if one is running already
if (gSession.isWizardActive() && gSession.getWizard() instanceof groupHomeGroupWizard)
{
if (debug)
{
print("Ok, wizard is running. Checking to see if it is done.");
}
// We are already in this wizard, lets see where we are
homeWizard = (groupHomeGroupWizard)gSession.getWizard();
if (homeWizard.getState() == homeWizard.DONE)
{
// Ok, the home wizard has done its deed, so get
// rid of it
homeWizard.unregister();
// I don't think it is a good idea to return null
// here.
if (debug)
{
print("Returning null, because I am in groupCustom.wizardHook " +
"with an active wizard that is done.");
}
return null;
}
else
{
if (homeWizard.groupObject != this)
{
print("bad object, group objects confused somehow.");
}
if (homeWizard.getState() != homeWizard.DONE)
{
print(" bad state: " + homeWizard.getState());
}
homeWizard.unregister(); // get rid of it, so it doesn't mess other stuff up
return Ganymede.createErrorDialog(this.getGSession(),
"Group 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 groupHomeGroupWizard))
{
return Ganymede.createErrorDialog(this.getGSession(),
"Group Object Error",
"The client is trying to change the group object " +
"while other wizards are running around.");
}
// Ok, if we get to here, then we need to start up a new wizard.
// The user is trying to remove someone out of the HOMEUSER field, which may cause problems.
try
{
if (debug)
{
print("Starting up a new wizard");
}
homeWizard = new groupHomeGroupWizard(this.gSession, this, userInvid);
return homeWizard.respond(null);
}
catch (RemoteException rx)
{
throw new RuntimeException("Could not send wizard to client: " + rx);
}
}
}
else if (field.getID() == USERS) // from groupSchema
{
switch (operation)
{
case DELELEMENTS:
if (gSession == null)
{
return null; // fine, whatever
}
else
{
return Ganymede.createErrorDialog(this.getGSession(),
"Group Validation Error",
"Can't do bulk removal of group entries right now.");
}
case DELELEMENT:
Vector<Invid> users = (Vector<Invid>) getFieldValuesLocal(USERS);
int index = ((Integer) param1).intValue();
Invid userInvid = users.get(index);
Vector<Invid> homeUsers = (Vector<Invid>) getFieldValuesLocal(HOMEUSERS);
if (!homeUsers.contains(userInvid))
{
return null; // fine, whatever
}
if (gSession == null)
{
// no session, this is being done by an automated process, let
// it go
return null;
}
if (!gSession.getDBSession().isInteractive())
{
// let it go for now, we'll verify at transaction commit if we have to
return null;
}
String username = gSession.getDBSession().getObjectLabel(userInvid);
return Ganymede.createErrorDialog(this.getGSession(),
"Group Validation Error",
"Can't remove user " + username + " from group " + getLabel()
+ "'s list of users, this user is using " + getLabel() +
" as their home group. Remove this user from the home users list first.");
}
}
// otherwise, we don't care, at least not yet
return retVal;
}
catch (NotLoggedInException ex)
{
return Ganymede.loginError(ex);
}
}
/**
* This method handles external actions for deleting a user.
*/
private void handleGroupDelete(String name)
{
String deleteFilename;
File deleteHandler = null;
/* -- */
// if the system log is null, we're running in the direct loader, and we
// don't want to create anything external.
// This would be unusual for a delete, but..
if (Ganymede.log == null)
{
return;
}
if (debug)
{
System.err.println("groupCustom.handleGroupDelete(): group " + name +
"is being deleted");
}
deleteFilename = System.getProperty("ganymede.builder.scriptlocation");
if (deleteFilename != null)
{
// make sure we've got the path separator at the end of
// deleteFilename, add our script name
deleteFilename = PathComplete.completePath(deleteFilename) + "/scripts/group_deleter";
deleteHandler = new File(deleteFilename);
}
else
{
Ganymede.debug("groupCustom.handleGroupDelete(): Couldn't find " +
"ganymede.builder.scriptlocation property");
}
if (deleteHandler != null && deleteHandler.exists())
{
try
{
String execLine = deleteFilename + " " + name;
if (debug)
{
System.err.println("handleGroupDelete: running " + execLine);
}
try
{
if (debug)
{
System.err.println("handleGroupDelete: blocking");
}
int result = FileOps.runProcess(execLine);
if (debug)
{
System.err.println("handleGroupDelete: done");
}
if (result != 0)
{
Ganymede.debug("Couldn't handle externals for deleting group " + name +
"\n" + deleteFilename +
" returned a non-zero result: " + result);
}
}
catch (InterruptedException ex)
{
Ganymede.debug("Couldn't handle externals for deleting group " + name +
ex.getMessage());
}
}
catch (IOException ex)
{
Ganymede.debug("Couldn't handle externals for deleting group " + name +
ex.getMessage());
}
}
}
/**
* This method handles external actions for renaming a group.
*/
private void handleGroupRename(String orig, String newname)
{
String renameFilename;
File renameHandler = null;
/* -- */
// if the system log is null, we're running in the direct loader, and we
// don't want to create anything external.
if (Ganymede.log == null)
{
return;
}
if (debug)
{
System.err.println("groupCustom.handleGroupRename(): user " + orig +
"is being renamed to " + newname);
}
renameFilename = System.getProperty("ganymede.builder.scriptlocation");
if (renameFilename != null)
{
// make sure we've got the path separator at the end of
// renameFilename, add our script name
renameFilename = PathComplete.completePath(renameFilename) + "/scripts/group_namer";
renameHandler = new File(renameFilename);
}
else
{
Ganymede.debug("groupCustom.handleGroupRename(): Couldn't find " +
"ganymede.builder.scriptlocation property");
}
if (renameHandler != null && renameHandler.exists())
{
try
{
String execLine = renameFilename + " " + orig + " " + newname;
if (debug)
{
System.err.println("handleGroupRename: running " + execLine);
}
try
{
if (debug)
{
System.err.println("handleGroupRename: blocking");
}
int result = FileOps.runProcess(execLine);
if (debug)
{
System.err.println("handleGroupRename: done");
}
if (result != 0)
{
Ganymede.debug("Couldn't handle externals for renaming group " + orig +
" to " + newname + "\n" + renameFilename +
" returned a non-zero result: " + result);
}
}
catch (InterruptedException ex)
{
Ganymede.debug("Couldn't handle externals for renaming group " + orig +
" to " +
newname + "\n" +
ex.getMessage());
}
}
catch (IOException ex)
{
Ganymede.debug("Couldn't handle externals for renaming group " + orig +
" to " +
newname + "\n" +
ex.getMessage());
}
}
}
private void print(String s)
{
System.err.println("groupCustom.wizardHook(): " + s);
}
/**
* <p>Private method to create a globally unique UID value suitable
* for certain LDAP applications</p>
*/
private String generateGUID()
{
UUIDGenerator gen = UUIDGenerator.getInstance();
org.doomdark.uuid.UUID guid = gen.generateTimeBasedUUID(new EthernetAddress("8:0:20:fd:6b:7")); // csdsun9
return guid.toString();
}
}