001: /*
002: * The contents of this file are subject to the
003: * Mozilla Public License Version 1.1 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
006: *
007: * Software distributed under the License is distributed on an "AS IS"
008: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
009: * See the License for the specific language governing rights and
010: * limitations under the License.
011: *
012: * The Initial Developer of the Original Code is Simulacra Media Ltd.
013: * Portions created by Simulacra Media Ltd are Copyright (C) Simulacra Media Ltd, 2004.
014: *
015: * All Rights Reserved.
016: *
017: * Contributor(s):
018: */
019: package org.openharmonise.rm.security.authorization;
020:
021: import java.util.*;
022: import java.util.logging.*;
023:
024: import org.openharmonise.commons.cache.*;
025: import org.openharmonise.commons.dsi.AbstractDataStoreInterface;
026: import org.openharmonise.rm.DataAccessException;
027: import org.openharmonise.rm.factory.*;
028: import org.openharmonise.rm.metadata.*;
029: import org.openharmonise.rm.resources.*;
030: import org.openharmonise.rm.resources.lifecycle.*;
031: import org.openharmonise.rm.resources.metadata.properties.*;
032: import org.openharmonise.rm.resources.metadata.values.Value;
033: import org.openharmonise.rm.resources.users.User;
034:
035: /**
036: * Utility class that provides methods to handle the role based security
037: * validations within Harmonise.
038: *
039: * @author Michael Bell
040: * @version $Revision: 1.3 $
041: *
042: */
043: public class AuthorizationValidator implements EditEventListener,
044: CacheListener {
045: public static final String ATTRIB_IS_VIEWABLE = "isViewable";
046: public static final String PROP_ROLE = "ROLE";
047: public static final String COMMAND_VIEW = "View";
048: public static final String BROWSE_ROLE = "BROWSER";
049: private static CachePointer m_role_ptr = null;
050: private static CachePointer m_view_ptr = null;
051:
052: private static AuthorizationValidator m_instance = null;
053:
054: private static Map m_object_security_cache = new Hashtable();
055:
056: private static Map m_instance_security_cache = Collections
057: .synchronizedMap(new Hashtable());
058:
059: /**
060: * Logger for this class.
061: */
062: private static final Logger m_logger = Logger
063: .getLogger(AuthorizationValidator.class.getName());
064:
065: /**
066: * Private constructor.
067: *
068: */
069: private AuthorizationValidator() {
070: }
071:
072: /**
073: * Returns the singleton instance of the validator.
074: *
075: * @return
076: */
077: public static AuthorizationValidator getInstance() {
078: if (m_instance == null) {
079: m_instance = new AuthorizationValidator();
080: }
081:
082: return m_instance;
083: }
084:
085: /**
086: * Compares the secuirty profile of the user against that of the profiled
087: * object to check if the object is visible to the user.
088: *
089: * @param usr User to view the object
090: * @param pobj Object to be viewed
091: * @return
092: * @throws Exception
093: */
094: public static boolean isVisible(User usr,
095: AbstractProfiledObject pobj) throws AuthorizationException {
096: return isCommandAvailable(usr, pobj, COMMAND_VIEW);
097: }
098:
099: /**
100: * Compares the User's profile against the profiled object's profile to find
101: * if the command is available to the user
102: *
103: * @param usr User to perform the command
104: * @param profObj AbstractProfiledObject command will be performed on
105: * @param sCommand Command name to perform
106: * @return
107: */
108: public static boolean isCommandAvailable(User usr, Class objClass,
109: String sCommand) throws AuthorizationException {
110: boolean bIsAvail = false;
111:
112: if (objClass != null) {
113:
114: if (isSuperUser(usr) == true) {
115: bIsAvail = true;
116: } else {
117:
118: Profile usrProf = null;
119:
120: try {
121: usrProf = usr
122: .getProfile(AuthorityProfile.SECURITY_PROFILE_NAME);
123: } catch (DataAccessException e) {
124: throw new AuthorizationException(
125: "Error occured accessing user's security profile:",
126: e);
127: }
128:
129: if (usrProf != null) {
130: try {
131: ChildObjectPropertyInstance roleInst = (ChildObjectPropertyInstance) usrProf
132: .getPropertyInstance(PROP_ROLE);
133:
134: if (roleInst != null) {
135: Value roleVal = (Value) roleInst.getValue();
136:
137: bIsAvail = isCommandAvailableToObject(
138: roleVal, objClass, sCommand);
139: }
140: } catch (DataAccessException e) {
141: throw new AuthorizationException(
142: "Data access exception:", e);
143: } catch (InvalidPropertyInstanceException e) {
144: throw new AuthorizationException(e
145: .getLocalizedMessage(), e);
146: }
147: }
148: }
149:
150: if (m_logger.isLoggable(Level.FINE)) {
151: m_logger.logp(Level.FINE, AuthorizationValidator.class
152: .getName(), "isCommandAvailable",
153: "Validating availablity of " + sCommand
154: + " to " + objClass.getName()
155: + " for user " + usr.getId() + " - "
156: + bIsAvail);
157: }
158: }
159:
160: return bIsAvail;
161: }
162:
163: /**
164: * Compares the User's profile against the profiled object's profile to find
165: * if the command is available to the user.
166: *
167: * @param usr User to perform the command
168: * @param profObj AbstractProfiledObject command will be performed on
169: * @param sCommand Command name to perform
170: * @return
171: */
172: public static boolean isCommandAvailable(User usr,
173: AbstractEditableObject edObj, String sCommand)
174: throws AuthorizationException {
175: boolean bIsAvail = false;
176:
177: if (edObj != null && usr != null) {
178:
179: if (isSuperUser(usr) == true) {
180: bIsAvail = true;
181: } else {
182:
183: Profile usrProf = null;
184:
185: try {
186: usrProf = usr
187: .getProfile(AuthorityProfile.SECURITY_PROFILE_NAME);
188: } catch (DataAccessException e) {
189: throw new AuthorizationException(
190: "Error occured accessing user's security profile:",
191: e);
192: }
193:
194: if (usrProf != null) {
195: try {
196: ChildObjectPropertyInstance roleInst = (ChildObjectPropertyInstance) usrProf
197: .getPropertyInstance(PROP_ROLE);
198:
199: if (roleInst != null) {
200: Value roleVal = (Value) roleInst.getValue();
201:
202: if (isCommandAvailableToObject(roleVal,
203: edObj.getClass(), sCommand) == true) {
204: if (edObj instanceof AbstractProfiledObject
205: && edObj.exists() == true) {
206: bIsAvail = isCommandAvailableToInstance(
207: roleVal,
208: (AbstractProfiledObject) edObj,
209: sCommand);
210: } else {
211: bIsAvail = true;
212: }
213: }
214: }
215: } catch (DataAccessException e) {
216: throw new AuthorizationException(
217: "Data access exception:", e);
218: } catch (InvalidPropertyInstanceException e) {
219: throw new AuthorizationException(e
220: .getLocalizedMessage(), e);
221: }
222: }
223: }
224:
225: if (m_logger.isLoggable(Level.FINE)) {
226: m_logger.logp(Level.FINE, AuthorizationValidator.class
227: .getName(), "isCommandAvailable",
228: "Validating availablity of " + sCommand
229: + " to " + edObj.getClass().getName()
230: + " " + edObj.getId() + " for user "
231: + usr.getId() + " - " + bIsAvail);
232: }
233: }
234:
235: return bIsAvail;
236: }
237:
238: /**
239: * Merges the secondary profile with the primary profile, ensuring that the
240: * resultant is at least as restrictive as the secondary profile.
241: *
242: * @param primaryProfile Primary security profile, the one to be altered
243: * @param referenceProfile Secondary security profile, the profile to merged
244: * in to the primary security profile
245: */
246: public static void mergeSecurityProfiles(Profile primaryProfile,
247: Profile referenceProfile) throws AuthorizationException {
248:
249: List primPropInsts = null;
250: try {
251: primPropInsts = primaryProfile.getPropertyInstances();
252: } catch (DataAccessException e) {
253: throw new AuthorizationException(
254: "Error occured accessing property instances", e);
255: }
256:
257: Iterator iter = primPropInsts.iterator();
258:
259: //loop through command property instances and check them against the reference profile
260: //if the property isn't in the ref profile then remove the property instance
261: //if the property is in the ref profile ensure that all values held in the
262: //primary profile are valid in the ref profile
263: while (iter.hasNext()) {
264: AbstractPropertyInstance propInst = (AbstractPropertyInstance) iter
265: .next();
266: AbstractPropertyInstance refPropInst = null;
267: Property prop = null;
268:
269: try {
270: prop = propInst.getProperty();
271:
272: refPropInst = referenceProfile
273: .getPropertyInstance(prop);
274: } catch (DataAccessException e) {
275: throw new AuthorizationException(
276: "Error occured getting property instance", e);
277: } catch (InvalidPropertyInstanceException e) {
278: throw new AuthorizationException(
279: "Error occured getting property instance", e);
280: }
281:
282: if (refPropInst == null) {
283: try {
284: primaryProfile.removeProperty(prop);
285: } catch (ProfileException e) {
286: throw new AuthorizationException(
287: "Error occured removing prop inst", e);
288: }
289: } else {
290: List refVals = refPropInst.getValues();
291: List instVals = propInst.getValues();
292:
293: Iterator valIter = instVals.iterator();
294:
295: while (valIter.hasNext()) {
296: Object val = iter.next();
297:
298: if (instVals.contains(val) == false) {
299: propInst.removeValue(val);
300: }
301: }
302: }
303: }
304:
305: }
306:
307: /**
308: * Returns <code>true</code> if the given user is a super user.
309: *
310: * @param usr
311: * @return
312: */
313: static public boolean isSuperUser(User usr)
314: throws AuthorizationException {
315: boolean bIsSuper = false;
316:
317: if (usr != null) {
318: try {
319: bIsSuper = usr.isSuper();
320: } catch (DataAccessException e) {
321: throw new AuthorizationException(
322: "Error occured accessing property instance", e);
323: }
324: }
325:
326: return bIsSuper;
327: }
328:
329: /**
330: * Returns list of security properties.
331: *
332: * @param dsi
333: * @return
334: * @throws DataAccessException
335: */
336: public static List getSecurityProperties(
337: AbstractDataStoreInterface dsi) throws DataAccessException {
338: List rbs_props = new ArrayList();
339:
340: try {
341:
342: if (m_role_ptr == null) {
343: Property role = PropertyFactory.getPropertyFromName(
344: dsi, AuthorizationValidator.PROP_ROLE);
345: if (role != null) {
346: m_role_ptr = CacheHandler.getInstance(dsi)
347: .getCachePointer(role);
348: }
349:
350: }
351:
352: rbs_props.add(m_role_ptr.getObject());
353:
354: } catch (HarmoniseFactoryException e) {
355: throw new DataAccessException(e.getLocalizedMessage(), e);
356: } catch (CacheException e) {
357: throw new DataAccessException(e.getLocalizedMessage(), e);
358: }
359:
360: return rbs_props;
361: }
362:
363: /* (non-Javadoc)
364: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectSaved(org.openharmonise.rm.resources.lifecycle.EditEvent)
365: */
366: public void workflowObjectSaved(EditEvent event) {
367: //nothing to do
368:
369: }
370:
371: /* (non-Javadoc)
372: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectStatusChanged(org.openharmonise.rm.resources.lifecycle.EditEvent)
373: */
374: public void workflowObjectStatusChanged(EditEvent event) {
375: Editable editObj = event.getResult();
376: Editable srcObj = (Editable) event.getSource();
377: handleObjectChanged(editObj);
378: }
379:
380: /* (non-Javadoc)
381: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectArchived(org.openharmonise.rm.resources.lifecycle.EditEvent)
382: */
383: public void workflowObjectArchived(EditEvent event) {
384: Editable editObj = event.getResult();
385: Editable srcObj = (Editable) event.getSource();
386: handleObjectChanged(editObj);
387:
388: }
389:
390: /* (non-Javadoc)
391: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectReactivated(org.openharmonise.rm.resources.lifecycle.EditEvent)
392: */
393: public void workflowObjectReactivated(EditEvent event) {
394: //nothing to do
395:
396: }
397:
398: /* (non-Javadoc)
399: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectLocked(org.openharmonise.rm.resources.lifecycle.EditEvent)
400: */
401: public void workflowObjectLocked(EditEvent event) {
402: //nothing to do
403:
404: }
405:
406: /* (non-Javadoc)
407: * @see org.openharmonise.rm.resources.lifecycle.EditEventListener#workflowObjectUnlocked(org.openharmonise.rm.resources.lifecycle.EditEvent)
408: */
409: public void workflowObjectUnlocked(EditEvent event) {
410: //nothing to do
411:
412: }
413:
414: /*----------------------------------------------------------------------------
415: Private methods
416: -----------------------------------------------------------------------------*/
417:
418: /**
419: * Tests whether the role roleVal is allowed to execute the command sCommand on the
420: * class edClass.
421: *
422: *
423: * @param roleVal
424: * @param edObj
425: * @param sCommand
426: * @return
427: * @throws DataAccessException
428: */
429: static private boolean isCommandAvailableToObject(Value roleVal,
430: Class edClass, String sCommand) throws DataAccessException {
431: boolean bIsAvail = false;
432:
433: if (roleVal != null) {
434:
435: String sObjClassname = edClass.getName();
436:
437: String sObjName = sObjClassname.substring(sObjClassname
438: .lastIndexOf(".") + 1);
439:
440: String cacheKey = getCacheKey(roleVal.getName(), sCommand,
441: edClass);
442:
443: Boolean boolIsAvail = (Boolean) m_object_security_cache
444: .get(cacheKey);
445:
446: if (boolIsAvail == null) {
447:
448: Profile valProf = roleVal.getProfile();
449:
450: if (valProf != null) {
451:
452: try {
453: ChildObjectPropertyInstance objInst = (ChildObjectPropertyInstance) valProf
454: .getPropertyInstance(sObjName);
455:
456: if (objInst != null) {
457:
458: List allowedCommands = objInst.getValues();
459: Value val = null;
460:
461: //if the list contains the string rep of the command it's available
462: //to the user
463: for (Iterator iter = allowedCommands
464: .iterator(); iter.hasNext();) {
465: val = (Value) iter.next();
466: // get the next value
467:
468: // now check if the names match
469: if (val.getName().equals(sCommand) == true) {
470: bIsAvail = true;
471:
472: //break the loop
473: break;
474: }
475: }
476: }
477: } catch (InvalidPropertyInstanceException e) {
478: throw new DataAccessException(e
479: .getLocalizedMessage(), e);
480: }
481: }
482:
483: roleVal.addEditEventListener(AuthorizationValidator
484: .getInstance());
485:
486: m_object_security_cache.put(cacheKey, new Boolean(
487: bIsAvail));
488: } else {
489: bIsAvail = boolIsAvail.booleanValue();
490: }
491: }
492:
493: return bIsAvail;
494: }
495:
496: /**
497: * Tests whether the command is available to the role roleVal on the object profObj.
498: *
499: * @param roleVal
500: * @param profObj
501: * @param sCommand
502: * @return
503: * @throws DataAccessException
504: */
505: static private boolean isCommandAvailableToInstance(Value roleVal,
506: AbstractProfiledObject profObj, String sCommand)
507: throws DataAccessException {
508: boolean bIsAvail = false;
509: Profile objProf = null;
510:
511: String cacheKey = getCacheKey(roleVal.getName(), sCommand,
512: profObj);
513:
514: Boolean boolIsAvail = (Boolean) m_instance_security_cache
515: .get(cacheKey);
516:
517: if (boolIsAvail == null) {
518:
519: objProf = profObj
520: .getProfile(AuthorityProfile.SECURITY_PROFILE_NAME);
521:
522: if (objProf != null) {
523:
524: try {
525: AbstractPropertyInstance cmndInst = objProf
526: .getPropertyInstance(sCommand);
527:
528: if (cmndInst != null) {
529:
530: List cmndRoleVals = cmndInst.getValues();
531:
532: //if roleVal is included in list of values for the command property instance
533: //then it's allowed/available
534: if (cmndRoleVals.contains(roleVal) == true) {
535: bIsAvail = true;
536: }
537: } else {
538: //assumption here is that if command property doesn't exist in the
539: //security profile then it's not restricted
540: bIsAvail = true;
541: }
542: } catch (InvalidPropertyInstanceException e) {
543: throw new DataAccessException(e
544: .getLocalizedMessage(), e);
545: }
546:
547: if (bIsAvail == true) {
548: if (profObj instanceof AbstractChildObject) {
549: AbstractChildObject child = (AbstractChildObject) profObj;
550:
551: AbstractParentObject parent = child
552: .getRealParent();
553:
554: if (parent != null) {
555: bIsAvail = isCommandAvailableToInstance(
556: roleVal, parent, sCommand);
557: }
558: }
559: }
560: } else {
561: bIsAvail = true;
562: }
563: m_instance_security_cache.put(cacheKey, new Boolean(
564: bIsAvail));
565:
566: //add the event listener to the object
567: profObj.addEditEventListener(AuthorizationValidator
568: .getInstance());
569:
570: } else {
571: bIsAvail = boolIsAvail.booleanValue();
572: }
573:
574: return bIsAvail;
575: }
576:
577: /**
578: * Returns a String cache key to be used for the combination of role, command
579: * and object instance.
580: *
581: * @param roleName
582: * @param sCommand
583: * @param profObj
584: * @return
585: * @throws DataAccessException
586: */
587: private static String getCacheKey(String roleName, String sCommand,
588: AbstractProfiledObject profObj) throws DataAccessException {
589: StringBuffer sbuf = new StringBuffer();
590:
591: sbuf.append(profObj.getClass().getName());
592:
593: if (profObj instanceof AbstractChildObject) {
594: AbstractChildObject child = (AbstractChildObject) profObj;
595:
596: sbuf.append(child.getFullPath());
597: } else {
598: sbuf.append(profObj.getName());
599: }
600:
601: sbuf.append(roleName).append(sCommand);
602:
603: return sbuf.toString();
604: }
605:
606: /**
607: * Returns a cache key based on the combination of role, command and class.
608: *
609: * @param roleName
610: * @param sCommand
611: * @param clss
612: * @return
613: * @throws DataAccessException
614: */
615: private static String getCacheKey(String roleName, String sCommand,
616: Class clss) throws DataAccessException {
617: StringBuffer sbuf = new StringBuffer();
618:
619: sbuf.append(roleName).append(sCommand).append(clss.getName());
620:
621: return sbuf.toString();
622: }
623:
624: /**
625: * Removes any cached security validations which match the <code>AbstractProfiledObject</code>
626: * profObj or its descendants.
627: *
628: * @param profObj
629: * @throws DataAccessException
630: */
631: private void removeFromInstanceSecurityCache(
632: AbstractProfiledObject profObj) throws DataAccessException {
633: Set keys = m_instance_security_cache.keySet();
634:
635: StringBuffer sbuf = new StringBuffer();
636: sbuf.append(profObj.getClass().getName());
637:
638: if (profObj instanceof AbstractChildObject) {
639: AbstractChildObject child = (AbstractChildObject) profObj;
640: sbuf.append(child.getFullPath());
641: } else {
642: sbuf.append(profObj.getName());
643: }
644:
645: String cacheKeyPrefix = sbuf.toString();
646: String sKey = null;
647: boolean bKeyFound = false;
648:
649: for (Iterator iter = keys.iterator(); iter.hasNext()
650: && bKeyFound == false;) {
651: sKey = (String) iter.next();
652:
653: if (sKey.startsWith(cacheKeyPrefix) == true) {
654: bKeyFound = true;
655: }
656: }
657:
658: if (bKeyFound == true) {
659: m_instance_security_cache.remove(sKey);
660: }
661: }
662:
663: private void removeFromObjectSecurityCache(Value roleVal)
664: throws DataAccessException {
665: String roleName = roleVal.getName();
666:
667: Iterator iter = m_object_security_cache.keySet().iterator();
668: List keysToRemove = new ArrayList();
669:
670: while (iter.hasNext()) {
671: String sKey = (String) iter.next();
672: if (sKey.startsWith(roleName)) {
673: keysToRemove.add(sKey);
674: }
675: }
676:
677: for (iter = keysToRemove.iterator(); iter.hasNext();) {
678: String sKeyToRemove = (String) iter.next();
679: m_object_security_cache.remove(sKeyToRemove);
680: }
681:
682: }
683:
684: /**
685: * Returns <code>true</code> if user is a browser.
686: *
687: * @param usr
688: * @return
689: */
690: public static boolean isBrowser(User usr)
691: throws AuthorizationException {
692: boolean bIsBrowse = false;
693:
694: if (usr != null) {
695: try {
696: Profile prof = usr
697: .getProfile(AuthorityProfile.SECURITY_PROFILE_NAME);
698:
699: if (prof != null) {
700: AbstractPropertyInstance roleInst = prof
701: .getPropertyInstance(PROP_ROLE);
702:
703: if (roleInst != null) {
704: Value roleValue = (Value) roleInst.getValue();
705:
706: if (roleValue != null
707: && roleValue.getName().equals(
708: BROWSE_ROLE) == true) {
709: bIsBrowse = true;
710: }
711: }
712:
713: }
714: } catch (DataAccessException e) {
715: throw new AuthorizationException(
716: "Error occured accessing property instance", e);
717: } catch (InvalidPropertyInstanceException e) {
718: throw new AuthorizationException(e
719: .getLocalizedMessage(), e);
720: }
721: }
722:
723: return bIsBrowse;
724: }
725:
726: public static List getUserRoles(User usr)
727: throws InvalidPropertyInstanceException,
728: DataAccessException {
729: List roles = null;
730:
731: Profile security = usr
732: .getProfile(AuthorityProfile.SECURITY_PROFILE_NAME);
733:
734: ChildObjectPropertyInstance roleInst = (ChildObjectPropertyInstance) security
735: .getPropertyInstance(AuthorizationValidator.PROP_ROLE);
736:
737: if (roleInst != null) {
738: roles = roleInst.getValues();
739: } else {
740: roles = new ArrayList();
741: }
742:
743: return roles;
744: }
745:
746: /* (non-Javadoc)
747: * @see org.openharmonise.commons.cache.CacheListener#objectRemovedFromCache(org.openharmonise.commons.cache.CacheableObject)
748: */
749: public void objectRemovedFromCache(CacheableObject obj) {
750: handleObjectChanged(obj);
751: }
752:
753: /**
754: * Clear references to security caches if necessary.
755: *
756: * @param obj object to process
757: */
758: private void handleObjectChanged(Object obj) {
759: if (obj instanceof AbstractProfiledObject) {
760: AbstractProfiledObject profObj = (AbstractProfiledObject) obj;
761: try {
762: if (profObj.getStatus() == Status.APPROVED) {
763:
764: removeFromInstanceSecurityCache(profObj);
765:
766: }
767: } catch (DataAccessException e) {
768: m_logger.log(Level.WARNING, e.getLocalizedMessage(), e);
769:
770: //don't want to throw an exception - so log and clear the cache
771:
772: m_instance_security_cache.clear();
773: }
774: }
775:
776: if (obj instanceof Value) {
777: try {
778: //assume it's a role
779: removeFromObjectSecurityCache((Value) obj);
780: } catch (DataAccessException e) {
781: //just clear the whole cache
782: m_object_security_cache.clear();
783: }
784: }
785: }
786: }
|