/* adminPersonaCustom.java This file is a management class for admin personae objects in Ganymede. Created: 8 October 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.server; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.List; import java.util.Vector; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.NotLoggedInException; import arlut.csd.ganymede.common.Query; import arlut.csd.ganymede.common.QueryDataNode; import arlut.csd.ganymede.common.QueryNode; import arlut.csd.ganymede.common.QueryNotNode; import arlut.csd.ganymede.common.QueryResult; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.common.SchemaConstants; import arlut.csd.Util.TranslationService; /*------------------------------------------------------------------------------ class adminPersonaCustom ------------------------------------------------------------------------------*/ /** * This file is a management class for Admin Persona objects in * Ganymede. */ public class adminPersonaCustom extends DBEditObject implements SchemaConstants { 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.adminPersonaCustom"); /** * <p>This method takes an Invid pointing to an Admin Persona or * User record, and returns a string that can be used to send email * to that person. This method will return null if no address could * be determined for this administrator / user.</p> */ static public String convertAdminInvidToString(Invid adminInvid, DBSession session) { DBObject admin; /* -- */ if (adminInvid.getType() != SchemaConstants.PersonaBase && adminInvid.getType() != SchemaConstants.UserBase) { throw new RuntimeException("not an administrator or user invid"); } if (session == null) { session = Ganymede.internalSession.getDBSession(); } admin = session.viewDBObject(adminInvid); List<String> addresses = admin.getEmailTargets(); if (addresses == null || addresses.size() == 0) { return null; } return addresses.get(0); } /** * * Customization Constructor * */ public adminPersonaCustom(DBObjectBase objectBase) throws RemoteException { super(objectBase); } /** * * Create new object constructor * */ public adminPersonaCustom(DBObjectBase objectBase, Invid invid, DBEditSet editset) throws RemoteException { super(objectBase, invid, editset); } /** * * Check-out constructor, used by DBObject.createShadow() * to pull out an object for editing. * */ public adminPersonaCustom(DBObject original, DBEditSet editset) throws RemoteException { super(original, editset); } // and now the customizations /** * <p>This method provides a hook to allow custom DBEditObject subclasses to * indicate that the given object is interested in receiving notification * when changes involving it occur, and can provide one or more addresses for * such notification to go to.</p> * * <p><b>*PSEUDOSTATIC*</b></p> */ @Override public boolean hasEmailTarget(DBObject object) { return true; } /** * <p>This method provides a hook to allow custom DBEditObject subclasses to * return a Vector of Strings comprising a list of addresses to be * notified above and beyond the normal owner group notification when * the given object is changed in a transaction. Used for letting end-users * be notified of changes to their account, etc.</p> * * <p><b>*PSEUDOSTATIC*</b></p> */ @Override public List<String> getEmailTargets(DBObject object) { List<String> x = new ArrayList<String>(); String address = (String) object.getFieldValueLocal(SchemaConstants.PersonaMailAddr); if (address == null || address.trim().equals("")) { // okay, we got no address pre-registered for this // admin.. we need now to try to guess at one, by looking // to see this admin's name is of the form user:role, in // which case we can just try to send to 'user', which will // work as long as Ganymede's users cohere with the user names // at Ganymede.mailHostProperty. String adminName = object.getLabel(); int colondex = adminName.indexOf(':'); if (colondex == -1) { // supergash? return null; } address = adminName.substring(0, colondex); } if (x != null) { x.add(address); } return x; } /** * <p>We want any change to the 'name' or associated user field to * update our hidden label field, which both provides our composite * label and does our namespace checks for us. We do this in * finalizeSetValue() so that this operation is always done, even * if our GanymedeSession's enableOversight is set to false.</p> * * <p>----</p> * * <p>This method allows the DBEditObject to have executive approval * of any scalar set operation, and to take any special actions in * reaction to the set. When a scalar field has its value set, it * will call its owners finalizeSetValue() method, passing itself as * the <field> parameter, and passing the new value to be * approved as the <value> parameter. A Ganymede customizer * who creates custom subclasses of the DBEditObject class can * override the finalizeSetValue() method and write his own logic * to examine any change and either approve or reject the change.</p> * * <p>A custom finalizeSetValue() method will typically need to * examine the field parameter to see which field is being changed, * and then do the appropriate checking based on the value * parameter. The finalizeSetValue() method can call the normal * this.getFieldValueLocal() type calls to examine the current state * of the object, if such information is necessary to make * appropriate decisions.</p> * * <p>If finalizeSetValue() returns null or a ReturnVal object with * a positive success value, the DBField that called us is * guaranteed to proceed to make the change to its value. If this * method returns a non-success code in its ReturnVal, as with the * result of a call to Ganymede.createErrorDialog(), the DBField * that called us will not make the change, and the field will be * left unchanged. Any error dialog returned from finalizeSetValue() * will be passed to the user.</p> * * <p>The DBField that called us will take care of all standard * checks on the operation (including a call to our own * verifyNewValue() method before calling this method. Under normal * circumstances, we won't need to do anything here. * finalizeSetValue() is useful when you need to do unusually * involved checks, and for when you want a chance to trigger other * changes in response to a particular field's value being * changed.</p> * * @return A ReturnVal indicating success or failure. May * be simply 'null' to indicate success if no feedback need * be provided. */ @Override public ReturnVal finalizeSetValue(DBField field, Object value) { if (field.getID() == SchemaConstants.PersonaNameField) { if (debug) { System.err.println("adminPersonaCustom.finalizeSetValue(): setting persona name, refreshing label"); } return refreshLabelField((String) value, null, null); } if (field.getID() == SchemaConstants.PersonaAssocUser) { if (debug) { System.err.println("adminPersonaCustom.finalizeSetValue(): setting persona user, refreshing label"); } // Hide the associated user field if we are looking at the // supergash or monitor persona objects. if ((field.getID() == SchemaConstants.PersonaAssocUser) && (getID() <= 2)) { // "It is not permitted (or necessary) to set an associated user on the supergash or monitor persona objects." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("finalizeSetValue.restricted_persona")); } return refreshLabelField(null, (Invid) value, null); } return null; } /** * This private method is used to keep the hidden label field up-to-date. */ public ReturnVal refreshLabelField(String descrip, Invid userInvid, String newName) { if (descrip == null) { StringDBField nameField = getStringField(SchemaConstants.PersonaNameField); if (nameField != null) { descrip = (String) nameField.getValueLocal(); } } if ((userInvid == null) && (newName == null)) { InvidDBField assocUserField = getInvidField(SchemaConstants.PersonaAssocUser); if (assocUserField != null) { userInvid = (Invid) assocUserField.getValueLocal(); } } // if we are messing with the supergash or monitor persona // objects, don't try to mess around with the associated user. if (getID() <= 2) { return setFieldValueLocal(SchemaConstants.PersonaLabelField, descrip); } if ((newName == null && userInvid == null) || descrip == null) { return setFieldValueLocal(SchemaConstants.PersonaLabelField, null); } if (newName == null) { newName = this.getGSession().getDBSession().getObjectLabel(userInvid); } if (debug) { System.err.println("Trying to set label to " + newName + ":" + descrip); } return setFieldValueLocal(SchemaConstants.PersonaLabelField, newName + ":" + descrip); } /** * <p>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.</p> * * <p>If there is no caching key, this method will return null.</p> */ @Override public Object obtainChoicesKey(DBField field) { // by default, we return a Short containing the base // id for the field's target if (field.getID() != SchemaConstants.PersonaPrivs) { return super.obtainChoicesKey(field); } return null; // not going to cache PersonaPrivs field } /** * <p>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.</p> * * <p>This method will provide a reasonable default for targetted * invid fields.</p> */ @Override public QueryResult obtainChoiceList(DBField field) throws NotLoggedInException { if (debug) { System.err.println("Entering adminPersona obtainChoiceList for field " + field.getName()); } if (field.getID() != SchemaConstants.PersonaPrivs) { return super.obtainChoiceList(field); } if (debug) { System.err.println("Returning adminPersona restricted list"); } if (field.isEditable() && (field instanceof InvidDBField) && !field.isEditInPlace()) { DBObjectBaseField fieldDef; short baseId; /* -- */ fieldDef = field.getFieldDef(); baseId = fieldDef.getTargetBase(); if (baseId < 0) { return null; } if (Ganymede.internalSession == null) { return null; } // We don't want the Default Role to be shown as a valid // choice for this.. everyone has Default implicitly, no point // in showing it. QueryNode root = new QueryNotNode(new QueryDataNode(QueryDataNode.INVIDVAL, QueryDataNode.EQUALS, Invid.createInvid(SchemaConstants.RoleBase, SchemaConstants.RoleDefaultObj))); // note that the query we are submitting here *will* be filtered by the // current visibilityFilterInvid field in GanymedeSession. return editset.getDBSession().getGSession().query(new Query(baseId, root, true)); } return null; } /** * <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>To be overridden 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) { // we want to return a failure if there is no role set and if the // persona is not a member of the supergash owner set, which would // make roles superfluous Vector<Invid> roles = (Vector<Invid>) object.getFieldValuesLocal(SchemaConstants.PersonaPrivs); Vector<Invid> ownerSets = (Vector<Invid>) object.getFieldValuesLocal(SchemaConstants.PersonaGroupsField); Invid supergashPersona = Invid.createInvid(SchemaConstants.PersonaBase, SchemaConstants.PersonaSupergashObj); Invid monitor = Invid.createInvid(SchemaConstants.PersonaBase, SchemaConstants.PersonaMonitorObj); if (supergashPersona.equals(object.getInvid()) || monitor.equals(object.getInvid())) { return null; } Invid supergashOwner = Invid.createInvid(SchemaConstants.OwnerBase, SchemaConstants.OwnerSupergash); if (roles.size() == 0 && !ownerSets.contains(supergashOwner)) { // "Persona object "{0}" is incomplete. Personas must either have // a role defined or be a member of the supergash owner set." return Ganymede.createErrorDialog(this.getGSession(), null, ts.l("consistencyCheck.role_needed", this.getLabel())); } return null; } /** * <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 in DBEditObject subclasses.</p> * * <p><b>*PSEUDOSTATIC*</b></p> */ @Override public boolean fieldRequired(DBObject object, short fieldid) { switch (fieldid) { case SchemaConstants.PersonaAssocUser: // supergash and monitor don't have to have associated users // defined. if (object.getID() <= 2) { return false; } case SchemaConstants.PersonaNameField: case SchemaConstants.PersonaPasswordField: case SchemaConstants.PersonaLabelField: return true; } return false; } /** * <p>Customization method to verify whether the user should be able * to see a specific field in a given object. Instances of DBField * will wind up calling up to here to let us override the normal * visibility process.</p> * * <p>Note that it is permissible for session to be null, in which * case this method will always return the default visiblity for the * field in question.</p> * * <p>If field is not from an object of the same base as this * DBEditObject, an exception will be thrown.</p> * * <p>To be overridden in DBEditObject subclasses.</p> * * <p><b>*PSEUDOSTATIC*</b></p> */ @Override public boolean canSeeField(DBSession session, DBField field) { // hide the label field.. this cannot be changed by the client, // and should be treated as a 'behind-the-scenes' field used to // tie things together in the background. if (field.getID() == SchemaConstants.PersonaLabelField) { return false; } // Hide the associated user field if we are looking at the // supergash or monitor persona objects. if (field.getID() == SchemaConstants.PersonaAssocUser) { DBObject object = field.getOwner(); if (object.getID() <= 2) { return false; } } return super.canSeeField(session, field); } /** * <p>This method is used to control whether or not it is acceptable * to make a link to the given field in this DBObject type when the * user only has editing access for the source InvidDBField and not * the target.</p> * * @param object The object that the link is to be created in * @param fieldID The field that the link is to be created in */ @Override public boolean anonymousLinkOK(DBObject object, short fieldID) { // if they have permission to edit an owner group, who are // we to say no? if (fieldID == SchemaConstants.PersonaGroupsField) { return true; } return false; // by default, permission is denied } }