0001: /* ====================================================================
0002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
0003: *
0004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions
0008: * are met:
0009: *
0010: * 1. Redistributions of source code must retain the above copyright
0011: * notice, this list of conditions and the following disclaimer.
0012: *
0013: * 2. Redistributions in binary form must reproduce the above copyright
0014: * notice, this list of conditions and the following disclaimer in
0015: * the documentation and/or other materials provided with the
0016: * distribution.
0017: *
0018: * 3. The end-user documentation included with the redistribution,
0019: * if any, must include the following acknowledgment:
0020: * "This product includes software developed by Jcorporate Ltd.
0021: * (http://www.jcorporate.com/)."
0022: * Alternately, this acknowledgment may appear in the software itself,
0023: * if and wherever such third-party acknowledgments normally appear.
0024: *
0025: * 4. "Jcorporate" and product names such as "Expresso" must
0026: * not be used to endorse or promote products derived from this
0027: * software without prior written permission. For written permission,
0028: * please contact info@jcorporate.com.
0029: *
0030: * 5. Products derived from this software may not be called "Expresso",
0031: * or other Jcorporate product names; nor may "Expresso" or other
0032: * Jcorporate product names appear in their name, without prior
0033: * written permission of Jcorporate Ltd.
0034: *
0035: * 6. No product derived from this software may compete in the same
0036: * market space, i.e. framework, without prior written permission
0037: * of Jcorporate Ltd. For written permission, please contact
0038: * partners@jcorporate.com.
0039: *
0040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
0044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
0046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0051: * SUCH DAMAGE.
0052: * ====================================================================
0053: *
0054: * This software consists of voluntary contributions made by many
0055: * individuals on behalf of the Jcorporate Ltd. Contributions back
0056: * to the project(s) are encouraged when you make modifications.
0057: * Please send them to support@jcorporate.com. For more information
0058: * on Jcorporate Ltd. and its products, please see
0059: * <http://www.jcorporate.com/>.
0060: *
0061: * Portions of this software are based upon other open source
0062: * products and are subject to their respective licenses.
0063: */
0064: package com.jcorporate.expresso.core.dbobj;
0065:
0066: import com.jcorporate.expresso.core.db.DBConnection;
0067: import com.jcorporate.expresso.core.db.DBException;
0068: import com.jcorporate.expresso.core.registry.MutableRequestRegistry;
0069: import com.jcorporate.expresso.core.registry.RequestRegistry;
0070: import com.jcorporate.expresso.core.security.User;
0071: import com.jcorporate.expresso.services.dbobj.RowGroupPerms;
0072: import com.jcorporate.expresso.services.dbobj.RowPermissions;
0073: import com.jcorporate.expresso.services.dbobj.UserGroup;
0074:
0075: import java.util.ArrayList;
0076: import java.util.Collection;
0077: import java.util.Iterator;
0078: import java.util.List;
0079:
0080: /**
0081: * subclass this for support of row-level Authorization. <br>
0082: * <br>
0083: * Typically, you construct RowSecuredDBObject passing in userId or the
0084: * controller request to set the requesting identity. Or, after construction, call
0085: * setRequestingUid() so that user of this DBObject is known. Otherwise,
0086: * security checks will always return false (and access methods will throw
0087: * SecurityException). <br>
0088: * <br>
0089: * LIMITATION: the primary key for a given row is persisted, along with the
0090: * name of the table, to identify the permissions for that row. In other
0091: * words, the primary key for permissions is the row's table name plus the
0092: * row's primary key: permissionPK = targetTable + targetPrimKey.
0093: * Each database vendor (MySQL,
0094: * Oracle, etc.) has its own limit for the longest field that can be indexed
0095: * as a primary key. If a target row has a very long primary key, the database may
0096: * not be able to accommodate the primary key for its permissions. In that
0097: * case, a runtime exception is thrown when trying to persist the permissions.
0098: * If (length(permissionPK) > MAXIMUM for database) { throw runtime exception}
0099: * In most cases, the primary key is an integer or some other short field, so
0100: * this limitation is not a problem.
0101: * <p/>
0102: * LIMITATION: all permissions are stored in a two tables: RowPermissions & RowGroupPerms.
0103: * If your application
0104: * has many tables (dbobjects) which all subclass RowSecuredDBObject,
0105: * and also has many rows in these tables, you could run into scaling issues
0106: * first with permissions. Use RowSecuredDBObject subclasses for only the
0107: * important objects. Roll your own security scheme if you expect a very large
0108: * database... and tell us how you did it. :-)
0109: *
0110: * @author larry hamel, CodeGuild, Inc.
0111: * @see com.jcorporate.expresso.services.dbobj.RowPermissions
0112: * @see com.jcorporate.expresso.services.dbobj.RowGroupPerms
0113: */
0114: public class RowSecuredDBObject extends SecuredDBObject {
0115:
0116: /**
0117: * Flag for getPermission() if it must create new.
0118: */
0119: private static final String CREATED_NEW = "CREATED_NEW";
0120:
0121: /**
0122: * Constructor without parameters. This constructor will attempt to use servlet filter
0123: * to set data context and user ID from expresso v.5.6. However, if you
0124: * have not set up this filter in web.xml, be sure to set dbname and user id
0125: * after constructing.
0126: *
0127: * @throws DBException upon database communication error
0128: * @todo after expresso v.5.7, change impl to throw exception if servlet
0129: * filter is not found. Introducing this in v5.6, so legacy
0130: * does not want exceptions
0131: */
0132: public RowSecuredDBObject() throws DBException {
0133: try {
0134: User user = RequestRegistry.getUser();
0135: try {
0136: user.getUid();
0137: this .setRequestingUid(user.getUid());
0138: } catch (Throwable e) { // npe if user is null
0139: getLogger()
0140: .warn(
0141: "Cannot get user from request registry for class: '"
0142: + getClass().getName()
0143: + "'. No user has been associated with this thread yet. (see "
0144: + MutableRequestRegistry.class
0145: .getName() + ")");
0146:
0147: // by default, the superclass sets requesting user ID
0148: // to 'system' == superuser,
0149: // so reset the requesting user here
0150: super .setRequestingUid(User.getIdFromLogin(
0151: User.UNKNOWN_USER, getDataContext()));
0152: }
0153:
0154: } catch (Exception ex) {
0155:
0156: // by default, the superclass sets requesting user ID
0157: // to 'system' == superuser,
0158: // so reset the requesting user here
0159: super .setRequestingUid(User.getIdFromLogin(
0160: User.UNKNOWN_USER, getDataContext()));
0161:
0162: // no error for now if filter not found.
0163: // @todo after expresso v.5.7, change impl to throw exception
0164: // if servlet filter is not found. Introducing this in v5.6,
0165: // so legacy does not want exceptions
0166: getLogger().warn(
0167: "Problem automatically setting user ID: "
0168: + getClass().getName() + ", err: "
0169: + ex.getClass().getName() + ", msg: "
0170: + ex.getMessage());
0171: }
0172:
0173: // warn if key of this row may be too long
0174: checkKeyLength();
0175: }
0176:
0177: /**
0178: * Constructor: Specify a DB connection AND user id.
0179: *
0180: * @param theConnection A DBConnection that this object should use to
0181: * connect to the database
0182: * @param theUser User name attempting to use this object. If this is
0183: * "SYSTEM", then full permissions are granted. Note that you
0184: * cannot log in as "SYSTEM", it can only be used from within a
0185: * method.
0186: * @throws DBException If the object cannot be created
0187: */
0188: public RowSecuredDBObject(DBConnection theConnection, int theUser)
0189: throws DBException {
0190: super (theConnection, theUser);
0191:
0192: // check that key is not too long
0193: checkKeyLength();
0194: }
0195:
0196: /**
0197: * Creates a new RowSecuredDBObject object.
0198: * This constructor will attempt to use servlet filter
0199: * to set data context--from expresso v.5.6. However, if you
0200: * have not set up this filter in web.xml, be sure to set dbname and user id
0201: * after constructing.
0202: *
0203: * @param theUser requesting user
0204: * @throws DBException upon database communication error
0205: * @todo after expresso v.5.7, change impl to throw exception if servlet filter is not found. Introducing this in v5.6, so legacy does not want exceptions *
0206: */
0207: public RowSecuredDBObject(int theUser) throws DBException {
0208: super (theUser);
0209:
0210: try {
0211: setDataContext(RequestRegistry.getDataContext());
0212: } catch (IllegalStateException e) {
0213: // no error for now if filter not found.
0214: // @todo after expresso v.5.7, change impl to throw exception if servlet filter is not found. Introducing this in v5.6, so legacy does not want exceptions
0215: }
0216:
0217: // check that key is not too long
0218: checkKeyLength();
0219: }
0220:
0221: /**
0222: * Creates a new RowSecuredDBObject object.
0223: *
0224: * @param request context for using this object
0225: * @throws DBException upon database communication error
0226: */
0227: public RowSecuredDBObject(RequestContext request)
0228: throws DBException {
0229: super (request);
0230:
0231: // check that key is not too long
0232: checkKeyLength();
0233: }
0234:
0235: /**
0236: * @return list of a group names (strings) which have permission to change
0237: * permission
0238: * @throws DBException upon database communication error
0239: */
0240: public List getAdministrateGroups() throws DBException {
0241: List allGroups = this .getGroups();
0242:
0243: if (allGroups.size() == 0) {
0244: return allGroups;
0245: }
0246:
0247: List permGroups = new ArrayList();
0248:
0249: for (Iterator iterator = allGroups.iterator(); iterator
0250: .hasNext();) {
0251: RowGroupPerms perms = (RowGroupPerms) iterator.next();
0252:
0253: if (perms.canGroupAdministrate()) {
0254: permGroups.add(perms.group());
0255: }
0256: }
0257:
0258: return permGroups;
0259: }
0260:
0261: /**
0262: * find any existing permission groups for this object. Typically,
0263: * subclasses will call getReadGroups and getWriteGroups instead of this
0264: * method
0265: *
0266: * @return list of RowGroupPerms
0267: * @throws DBException upon database communication error
0268: */
0269: public List getGroups() throws DBException {
0270: String primeKey = getKey();
0271:
0272: RowGroupPerms rowGroupPerms = new RowGroupPerms(
0273: getJDBCMetaData().getTargetTable(), primeKey);
0274: rowGroupPerms.setDataContext(getDataContext());
0275:
0276: if (getLocalConnection() != null) {
0277: rowGroupPerms.setConnection(getLocalConnection());
0278: }
0279:
0280: return rowGroupPerms.searchAndRetrieveList();
0281: }
0282:
0283: /**
0284: * set the group and permissions for this object; owner id is taken from
0285: * getRequestingUid() before permissions can be set, caller's permission
0286: * to change permissions is tested
0287: *
0288: * @param group name of group
0289: * @param perm value of permissions
0290: * @throws DBException upon database communication error
0291: */
0292: public void setPermissions(String group, int perm)
0293: throws DBException {
0294: if (!canRequesterAdministrate()) {
0295: throw new SecurityException(
0296: "cannot set permissions by user_id: "
0297: + getRequestingUid()
0298: + " for db object in table: "
0299: + getJDBCMetaData().getTargetTable()
0300: + " with primary key: " + getKey());
0301: }
0302:
0303: setPermissions(getRequestingUid(), group, perm);
0304: }
0305:
0306: /**
0307: * set the permissions for this object; group bits are ignored; only owner
0308: * & "other" permissions apply with this method owner id is taken from
0309: * getRequestingUid()
0310: *
0311: * @param perm permissions to set
0312: * @throws DBException upon database communication error
0313: */
0314: public void setPermissions(int perm) throws DBException {
0315: setPermissions(getRequestingUid(), null, perm);
0316: }
0317:
0318: /**
0319: * finds row permissions for the target row of this DBObject. if no row
0320: * permissions are already persisted, the returned permissions object will
0321: * be constructed and keyed to generating object, but all permissions will
0322: * be false
0323: *
0324: * @return RowPermissions of the current perms
0325: * @throws DBException if keys are not set on this object
0326: */
0327: public RowPermissions getPermissions() throws DBException {
0328: if (!this .haveAllKeys()) {
0329: throw new DBException(
0330: "cannot get/set permissions on item before item's primary key(s) are available.");
0331: }
0332:
0333: String primeKey = getKey();
0334: RowPermissions rowPermissions = new RowPermissions(
0335: getJDBCMetaData().getTargetTable(), primeKey);
0336: rowPermissions.setDataContext(getDataContext());
0337: if (getLocalConnection() != null) {
0338: rowPermissions.setConnection(getLocalConnection());
0339: }
0340:
0341: boolean found = rowPermissions.find();
0342:
0343: // set owner if this is a new set of permissions
0344: if (!found) {
0345: rowPermissions.setAttribute(CREATED_NEW, "1");
0346: rowPermissions.owner(getRequestingUid());
0347: }
0348:
0349: return rowPermissions;
0350: }
0351:
0352: /**
0353: * @return list of group names (strings) which have read permission
0354: * @throws DBException upon database communication error
0355: */
0356: public List getReadGroups() throws DBException {
0357: List allGroups = this .getGroups();
0358:
0359: if (allGroups.size() == 0) {
0360: return allGroups;
0361: }
0362:
0363: List readGroups = new ArrayList();
0364:
0365: for (Iterator iterator = allGroups.iterator(); iterator
0366: .hasNext();) {
0367: RowGroupPerms perms = (RowGroupPerms) iterator.next();
0368:
0369: if (perms.canGroupRead()) {
0370: readGroups.add(perms.group());
0371: }
0372: }
0373:
0374: return readGroups;
0375: }
0376:
0377: /**
0378: * determine if this function is allowed for this requesting user
0379: *
0380: * @param requestedFunction code for function -- Add, Update, Delete, Search (read)
0381: * @return true if this function is allowed for this requesting user
0382: * @throws SecurityException (unchecked) if not allowed
0383: * @throws DBException for other data-related errors.
0384: */
0385: public boolean isRowAllowed(String requestedFunction)
0386: throws DBException {
0387: boolean result = false;
0388:
0389: if (SEARCH.equals(requestedFunction)) {
0390: result = canRequesterRead();
0391: } else if (UPDATE.equals(requestedFunction)
0392: || DELETE.equals(requestedFunction)) {
0393: result = canRequesterWrite();
0394: } else if (ADD.equals(requestedFunction)) {
0395: // we do not test "add", since the row isn't written yet!
0396: // instead, superclass will test as part of update call
0397: result = true;
0398: }
0399:
0400: if (!result) {
0401: throw new SecurityException(
0402: "User '"
0403: + User.getLoginFromId(getRequestingUid(),
0404: getDataContext())
0405: + "' does not have permission to perform function '"
0406: + requestedFunction
0407: + "' on database object in table '"
0408: + getJDBCMetaData().getTargetTable()
0409: + "', row: '"
0410: + getKey()
0411: + "' in db/context '"
0412: + getDataContext()
0413: + "'. Please contact your system administrator if you feel this is incorrect.");
0414: }
0415:
0416: return result;
0417: }
0418:
0419: /**
0420: * iterate through collection, testing each row's privileges remove any row
0421: * which does not have privileges; (do not throw security exception, just
0422: * remove row)
0423: *
0424: * @param requestedFunction code for function -- Add, Update, Delete, Search (read)
0425: * @param items is a collection of RowSecuredDBObjects
0426: * @return true if AT LEAST ONE item in collectino is allowed; also manipulates input list, removing unallowed items
0427: * @throws DBException upon database communication error
0428: */
0429: public boolean isRowAllowed(String requestedFunction,
0430: Collection items) throws DBException {
0431: if (items.size() == 0) {
0432: return false;
0433: }
0434:
0435: boolean result = false;
0436: List goodItems = new ArrayList();
0437:
0438: for (Iterator iterator = items.iterator(); iterator.hasNext();) {
0439: RowSecuredDBObject rowSecureDBObject = (RowSecuredDBObject) iterator
0440: .next();
0441:
0442: // key point: this is not set by superclass
0443: rowSecureDBObject.setRequestingUid(getRequestingUid());
0444:
0445: try {
0446: if (rowSecureDBObject.isRowAllowed(requestedFunction)) {
0447: result = true; // at least one row is allowed
0448: goodItems.add(rowSecureDBObject);
0449: }
0450: } catch (SecurityException e) {
0451: if (getLogger().isDebugEnabled()) {
0452: getLogger()
0453: .debug(
0454: "Security exception checking for isRowAllowed: denying a row with key: "
0455: + getKey()
0456: + " , removing it from list of approved.");
0457: }
0458: // do nothing; this Row will be removed
0459: }
0460: }
0461:
0462: if (result) {
0463: // keep only good items
0464: items.retainAll(goodItems);
0465:
0466: /**
0467: * @todo does this retain ordering?
0468: */
0469: } else {
0470: items.clear();
0471: }
0472:
0473: return result;
0474: }
0475:
0476: /**
0477: * @return list of group names (strings) which have write permission
0478: * @throws DBException upon database communication error
0479: */
0480: public List getWriteGroups() throws DBException {
0481: List allGroups = this .getGroups();
0482:
0483: if (allGroups.size() == 0) {
0484: return allGroups;
0485: }
0486:
0487: List writeGroups = new ArrayList();
0488:
0489: for (Iterator iterator = allGroups.iterator(); iterator
0490: .hasNext();) {
0491: RowGroupPerms perms = (RowGroupPerms) iterator.next();
0492:
0493: if (perms.canGroupWrite()) {
0494: writeGroups.add(perms.group());
0495: }
0496: }
0497:
0498: return writeGroups;
0499: }
0500:
0501: /**
0502: * we override not to check permissions (which is done at the table level
0503: * by superclass) but rather to add default permissions
0504: *
0505: * @throws DBException upon database communication error
0506: * @see #add(String group, int permissions ) for a way to add() with more
0507: * specific permissions
0508: */
0509: public void add() throws DBException {
0510: super .add();
0511: setDefaultPermissions();
0512: }
0513:
0514: /* add() */
0515:
0516: /*
0517: * add row with given permissions, with group permissions applying
0518: to provided group.
0519: * @param group the name of the group that has group privileges
0520: * @param permissions for owner, group & others
0521: * @see com.jcorporate.expresso.services.dbobj.RowPermissions
0522: * @throws DBException upon database communication error
0523: */
0524: public synchronized void add(String group, int permissions)
0525: throws DBException {
0526: super .add();
0527: setPermissions(getRequestingUid(), group, permissions);
0528: }
0529:
0530: /* add() */
0531:
0532: /**
0533: * add permissions for a group; will only ADD permissions, not replace will
0534: * add row or update existing row (logical OR of bits) as necessary
0535: *
0536: * @param group to be added
0537: * @param perm to be added
0538: * @throws DBException upon database communication error
0539: */
0540: public void addGroupPerm(String group, int perm) throws DBException {
0541: if (group == null) {
0542: throw new DBException("null group name");
0543: }
0544:
0545: // test if group is kosher
0546: UserGroup grp = new UserGroup();
0547: grp.setDataContext(getDataContext());
0548: grp.setField("GroupName", group);
0549:
0550: if (!grp.find()) {
0551: throw new DBException("cannot find group: " + group);
0552: }
0553:
0554: RowGroupPerms rowGroupPerms = new RowGroupPerms(
0555: getJDBCMetaData().getTargetTable(), getKey());
0556: rowGroupPerms.setDataContext(getDataContext());
0557: if (getLocalConnection() != null) {
0558: rowGroupPerms.setConnection(getLocalConnection());
0559: }
0560: rowGroupPerms.group(group);
0561:
0562: if (rowGroupPerms.find()) {
0563: // careful just to ADD permissions, which means bitwise OR
0564: int addperm = perm | rowGroupPerms.permissions();
0565:
0566: if (addperm != rowGroupPerms.permissions()) {
0567: rowGroupPerms.permissions(addperm);
0568: rowGroupPerms.update();
0569: }
0570: } else {
0571: rowGroupPerms.permissions(perm);
0572: rowGroupPerms.add();
0573: }
0574: }
0575:
0576: /**
0577: * @return true if requesting id has permission to administrate (change
0578: * permissions)
0579: * @throws DBException upon database communication error
0580: */
0581: public boolean canRequesterAdministrate() throws DBException {
0582: boolean result = false;
0583:
0584: int userId = this .getRequestingUid();
0585:
0586: if (userId == SYSTEM_ACCOUNT) {
0587: return true;
0588: }
0589:
0590: User user = new User();
0591: user.setUid(userId);
0592: user.setDataContext(this .getDataContext());
0593:
0594: if (user.isAdmin()) {
0595: return true;
0596: }
0597:
0598: if (!user.find()) {
0599: throw new DBException("cannot find requesting user.");
0600: }
0601:
0602: RowPermissions rowPermissions = this .getPermissions();
0603:
0604: // do tests by cpu cheapness: easiest tests first;
0605: // we're finished at first "true"
0606: // public readability?
0607: result = rowPermissions.canOthersAdministrate()
0608: || ((userId == rowPermissions.owner()) && rowPermissions
0609: .canOwnerAdministrate());
0610:
0611: if (!result) {
0612: // check groups
0613: result = doUsersGroupsIntersect(user,
0614: getAdministrateGroups());
0615: }
0616:
0617: return result;
0618: }
0619:
0620: /**
0621: * determine if getRequestingUid has rights to read this row
0622: *
0623: * @return true if getRequestingUid has rights to read this row
0624: * @throws DBException upon database communication error
0625: */
0626: public boolean canRequesterRead() throws DBException {
0627: boolean result = false;
0628:
0629: int userId = this .getRequestingUid();
0630:
0631: if (userId == SYSTEM_ACCOUNT) {
0632: return true;
0633: }
0634:
0635: User user = new User();
0636: user.setUid(userId);
0637: user.setDataContext(this .getDataContext());
0638:
0639: if (user.isAdmin()) {
0640: return true;
0641: }
0642:
0643: if (!user.find()) {
0644: throw new DBException("cannot find requesting user.");
0645: }
0646:
0647: RowPermissions rowPermissions = this .getPermissions();
0648:
0649: // do tests by cpu cheapness: easiest tests first;
0650: // we're finished at first "true"
0651: // public readability?
0652: result = rowPermissions.canOthersRead()
0653: || ((userId == rowPermissions.owner()) && rowPermissions
0654: .canOwnerRead());
0655:
0656: if (!result) {
0657: // check groups
0658: /**
0659: * @todo rewrite as a multidbobject join
0660: */
0661: result = doUsersGroupsIntersect(user, getReadGroups());
0662: }
0663:
0664: return result;
0665: }
0666:
0667: private boolean doUsersGroupsIntersect(User user, List groups)
0668: throws DBException {
0669: boolean result = false;
0670: for (Iterator iterator = user.getGroups().iterator(); iterator
0671: .hasNext();) {
0672: String userGrpname = (String) iterator.next();
0673: for (Iterator iterator1 = groups.iterator(); iterator1
0674: .hasNext();) {
0675: String readGrpName = (String) iterator1.next();
0676: result = readGrpName.equals(userGrpname);
0677: if (result)
0678: return true;
0679: }
0680: }
0681: return false;
0682: }
0683:
0684: /**
0685: * @return true if requesting id has permission to write
0686: * @throws DBException upon database communication error
0687: */
0688: public boolean canRequesterWrite() throws DBException {
0689: boolean result = false;
0690:
0691: int userId = this .getRequestingUid();
0692:
0693: if (userId == SYSTEM_ACCOUNT) {
0694: return true;
0695: }
0696:
0697: User user = new User();
0698: user.setUid(userId);
0699: user.setDataContext(this .getDataContext());
0700:
0701: if (user.isAdmin()) {
0702: return true;
0703: }
0704:
0705: if (!user.find()) {
0706: throw new DBException("cannot find requesting user.");
0707: }
0708:
0709: RowPermissions rowPermissions = this .getPermissions();
0710:
0711: // do tests by cpu cheapness: easiest tests first;
0712: // we're finished at first "true"
0713: // public readability?
0714: result = rowPermissions.canOthersWrite()
0715: || ((userId == rowPermissions.owner()) && rowPermissions
0716: .canOwnerWrite());
0717:
0718: if (!result) {
0719: // check groups
0720: result = doUsersGroupsIntersect(user, getWriteGroups());
0721: }
0722:
0723: return result;
0724: }
0725:
0726: /**
0727: * @return the default group for this requesting user; null if user has no
0728: * groups
0729: * @throws DBException upon database communication error
0730: */
0731: public String defaultGroup() throws DBException {
0732: User user = new User();
0733: user.setUid(getRequestingUid());
0734: user.setDataContext(getDataContext());
0735:
0736: if (!user.find()) {
0737: return null;
0738: } else {
0739: return user.getPrimaryGroup();
0740: }
0741: }
0742:
0743: /**
0744: * override this to change default
0745: *
0746: * @return the default permissions
0747: * @todo move this into ConfigManager
0748: */
0749: public int defaultPermissions() {
0750: return RowPermissions.DEFAULT_PERMISSIONS;
0751: }
0752:
0753: /**
0754: * delete row. always delete permission records too
0755: *
0756: * @param deleteDetails set to true if related details should be deleted also
0757: * @throws DBException upon database communication error
0758: */
0759: public synchronized void delete(boolean deleteDetails)
0760: throws DBException {
0761: isRowAllowed(DELETE);
0762: super .delete(deleteDetails);
0763:
0764: // delete all permissions associated with this row
0765: RowPermissions rowPermissions = getPermissions();
0766:
0767: if (rowPermissions.find()) {
0768: rowPermissions.delete();
0769: }
0770:
0771: List groups = getGroups();
0772:
0773: for (Iterator iterator = groups.iterator(); iterator.hasNext();) {
0774: RowGroupPerms rowGroupPerms = (RowGroupPerms) iterator
0775: .next();
0776: rowGroupPerms.delete();
0777: }
0778: }
0779:
0780: /* delete() */
0781:
0782: /**
0783: * check that all objects can be deleted; must retrieve all objects to
0784: * check individually
0785: *
0786: * @throws DBException upon database communication error
0787: */
0788: public synchronized void deleteAll() throws DBException {
0789: List list = searchAndRetrieveList();
0790:
0791: for (Iterator iter = list.iterator(); iter.hasNext();) {
0792: RowSecuredDBObject object = (RowSecuredDBObject) iter
0793: .next();
0794: object.isRowAllowed(DELETE);
0795: }
0796:
0797: super .deleteAll();
0798: }
0799:
0800: /* deleteAll() */
0801:
0802: /**
0803: * find object on criteria provided in fields
0804: *
0805: * @return true if found AND allowed
0806: * @throws DBException if user does not have rights to read found item
0807: */
0808: public boolean find() throws DBException {
0809: boolean result = super .find();
0810:
0811: // must test AFTER search, since we do not necessarily have row keys yet
0812: // to find permissions
0813: if (result == true) {
0814: isRowAllowed(SEARCH);
0815: }
0816:
0817: return result;
0818: }
0819:
0820: /**
0821: * @return uid of owner of this object
0822: * @throws DBException upon database communication error
0823: */
0824: public int ownerID() throws DBException {
0825: // here is the inserted part of override
0826: RowPermissions perms = this .getPermissions();
0827:
0828: return perms.getFieldInt(RowPermissions.OWNER_ID);
0829: }
0830:
0831: /**
0832: * remove a permissions group
0833: *
0834: * @param group to be removed
0835: * @throws DBException upon database communication error
0836: */
0837: public void removeGroup(String group) throws DBException {
0838: if (group == null) {
0839: throw new DBException("null group name");
0840: }
0841:
0842: RowGroupPerms rowGroupPerms = new RowGroupPerms(
0843: getJDBCMetaData().getTargetTable(), getKey());
0844: rowGroupPerms.setDataContext(getDataContext());
0845: rowGroupPerms.group(group);
0846:
0847: if (getLocalConnection() != null) {
0848: rowGroupPerms.setConnection(getLocalConnection());
0849: }
0850:
0851: if (rowGroupPerms.find()) {
0852: rowGroupPerms.delete();
0853: }
0854: }
0855:
0856: /**
0857: * retrieve object on criteria provided in fields
0858: *
0859: * @throws DBException upon database communication error
0860: */
0861: public void retrieve() throws DBException {
0862: super .retrieve();
0863: this .isRowAllowed(SEARCH); // test second in case we have a new object which will not have any permissions yet, and therefore always be disallowed
0864: }
0865:
0866: /**
0867: * search on criteria provided in fields, and after search phase iterate
0868: * through collection, testing each row's privileges remove any row which
0869: * does not have privileges; (do not throw security exception, just remove
0870: * row)
0871: *
0872: * @return list of RowSecuredDBObject's which are both found AND allowed for this requester
0873: * @throws DBException upon database communication error
0874: */
0875: public synchronized ArrayList searchAndRetrieveList()
0876: throws DBException {
0877: //return super.searchAndRetrieveList();
0878: ArrayList result = super .searchAndRetrieveList();
0879: this .isRowAllowed(SEARCH, result);
0880:
0881: return result;
0882: }
0883:
0884: /**
0885: * search on criteria provided in fields, and after search phase iterate
0886: * through collection, testing each row's privileges remove any row which
0887: * does not have privileges; (do not throw security exception, just remove
0888: * row) sort results by sortKeys
0889: *
0890: * @param sortKeys sort field(s)
0891: * @return list of RowSecuredDBObject's which are both found AND allowed for this requester
0892: * @throws DBException upon database communication error
0893: */
0894: public synchronized ArrayList searchAndRetrieveList(String sortKeys)
0895: throws DBException {
0896: ArrayList result = super .searchAndRetrieveList(sortKeys);
0897: this .isRowAllowed(SEARCH, result);
0898:
0899: return result;
0900: }
0901:
0902: /* searchAndRetrieve(String) */
0903:
0904: /**
0905: * before allowing update, check permission
0906: *
0907: * @throws DBException upon database communication error
0908: */
0909: public void update() throws DBException {
0910: isRowAllowed(UPDATE);
0911: super .update();
0912: }
0913:
0914: /* update() */
0915:
0916: /**
0917: * check delete privilege for all detail records; different than superclass
0918: * because we must set uid
0919: *
0920: * @param obj Object to be checked for deletion of detail records
0921: * @throws DBException upon database communication error
0922: */
0923: protected void checkDeleteDetailPerm(DBObject obj)
0924: throws DBException {
0925: // here is the inserted part of override
0926: if (obj instanceof RowSecuredDBObject) {
0927: RowSecuredDBObject row = (RowSecuredDBObject) obj;
0928: row.setRequestingUid(getRequestingUid());
0929: }
0930:
0931: super .checkDeleteDetailPerm(obj);
0932: }
0933:
0934: /**
0935: * // warn if key of this row may be too long
0936: *
0937: * @throws DBException upon error
0938: * @todo test this impl and uncomment
0939: */
0940: protected void checkKeyLength() throws DBException {
0941: // int totalLength = 0;
0942: // Iterator iter = getDef().getAllKeysIterator();
0943: // while (iter.hasNext()) {
0944: // DBField field = (DBField) iter.next();
0945: // totalLength += field.getLengthInt();
0946: // }
0947: //
0948: // if ( totalLength > RowPermissions.getMaxKeyLen() ) {
0949: // getLogger().debug("objects in table: " + getTargetTable()
0950: // + " have a potential key length of: " + totalLength
0951: // + " which is greater than the maximum that can be stored for "
0952: // + RowSecuredDBObject.class.getName()
0953: // + " so consider subclassing just SecuredDBObject instead." );
0954: // }
0955: }
0956:
0957: /**
0958: * called by add() does NOT check as to whether caller has permission-admin
0959: * rights--assumes that caller is only calling add(), so therefore caller
0960: * has rights on the new row.
0961: *
0962: * @throws DBException upon database communication error
0963: */
0964: private void setDefaultPermissions() throws DBException {
0965: RowPermissions rowPermissions = getPermissions();
0966: rowPermissions.owner(getRequestingUid());
0967: rowPermissions.permissions(defaultPermissions());
0968: rowPermissions.addOrUpdate();
0969:
0970: // default group may be null
0971: String group = defaultGroup();
0972:
0973: if ((group != null) && (group.length() > 0)) {
0974: addGroupPerm(group, defaultPermissions());
0975: }
0976: }
0977:
0978: /**
0979: * set permissions as indicated and persist, either calling db actions add() or update()
0980: *
0981: * @param owner new owner
0982: * @param group new group for group perms
0983: * @param perm permissions to set
0984: * @throws DBException upon database communication error
0985: */
0986: private void setPermissions(int owner, String group, int perm)
0987: throws DBException {
0988: RowPermissions rowPermissions = getPermissions();
0989: rowPermissions.owner(owner);
0990: rowPermissions.permissions(perm);
0991: if (rowPermissions.getAttribute(CREATED_NEW) != null) {
0992: rowPermissions.add();
0993: rowPermissions.removeAttribute(CREATED_NEW);
0994: } else {
0995: rowPermissions.update();
0996: }
0997:
0998: if (group != null) {
0999: addGroupPerm(group, perm);
1000: }
1001: }
1002: }
|