001: /*
002: * JBoss, Home of Professional Open Source
003: * Copyright 2005, JBoss Inc., and individual contributors as indicated
004: * by the @authors tag. See the copyright.txt in the distribution for a
005: * full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.portal.cms.impl.jcr.command;
023:
024: import org.jboss.portal.cms.impl.jcr.JCRCommand;
025: import org.jboss.portal.cms.impl.jcr.composite.NewFileCommand;
026: import org.jboss.portal.cms.impl.jcr.composite.UpdateFileCommand;
027: import org.jboss.portal.cms.security.AuthorizationManager;
028: import org.jboss.portal.cms.security.Criteria;
029: import org.jboss.portal.cms.security.Permission;
030: import org.jboss.portal.cms.security.PortalCMSSecurityContext;
031: import org.jboss.portal.cms.workflow.ApprovePublish;
032: import org.jboss.portal.identity.Role;
033: import org.jboss.portal.identity.User;
034:
035: import java.util.ArrayList;
036: import java.util.Collection;
037: import java.util.HashSet;
038: import java.util.Iterator;
039: import java.util.List;
040: import java.util.Set;
041: import java.util.StringTokenizer;
042:
043: /**
044: * ACLEnforcer checks proper access privileges for actions before the Command objects are allowed to execute and do
045: * their job on the CMS
046: *
047: * @author Sohil Shah - sohil.shah@jboss.com - Nov 28, 2006
048: */
049: public class ACLEnforcer {
050: private Collection readCommands = new ArrayList(); //a list of commands that perform read action on the cms
051: private Collection writeCommands = new ArrayList();//a list of commands that perform write action on the cms
052: private Collection manageCommands = new ArrayList();//a list of commands that perform manage action on the cms
053:
054: private static final int read = 0;
055: private static final int write = 1;
056: private static final int manage = 2;
057: // private static final int manageWorkflow = 3;
058:
059: private AuthorizationManager authorizationManager = null;
060:
061: /**
062: *
063: *
064: */
065: public ACLEnforcer(AuthorizationManager authorizationManager) {
066: super ();
067: this .authorizationManager = authorizationManager;
068:
069: String packageName = "org.jboss.portal.cms.impl.jcr.command.";
070:
071: //load the read related commands
072: readCommands.add(packageName + "FolderGetListCommand");
073: readCommands.add(packageName + "FolderGetCommand");
074: readCommands.add(packageName + "FileGetListCommand");
075: readCommands.add(packageName + "FileGetCommand");
076:
077: //load the write related commands
078: writeCommands.add(packageName + "ContentCreateCommand");
079: writeCommands.add(packageName + "FileCreateCommand");
080: writeCommands.add(packageName + "FolderCreateCommand");
081: writeCommands.add(packageName + "FileUpdateCommand");
082: writeCommands.add(packageName + "StoreArchiveCommand");
083: writeCommands
084: .add("org.jboss.portal.cms.impl.jcr.composite.NewFileCommand");
085: writeCommands
086: .add("org.jboss.portal.cms.impl.jcr.composite.UpdateFileCommand");
087:
088: //load the manage related commands
089: manageCommands.add(packageName + "CopyCommand");
090: manageCommands.add(packageName + "DeleteCommand");
091: manageCommands.add(packageName + "MoveCommand");
092: }
093:
094: /**
095: * @param securityContext
096: * @return
097: */
098: public boolean hasAccess(PortalCMSSecurityContext cmsSecurityContext) {
099: boolean hasAccess = true;
100: User loggedInUser = (User) cmsSecurityContext.getIdentity();
101: JCRCommand command = (JCRCommand) cmsSecurityContext
102: .getAttribute("command");
103:
104: //get the action code of the action being protected
105: int actionCode = -1;
106: if (command != null) {
107: actionCode = this .getActionCode(command);
108: }
109:
110: switch (actionCode) {
111: case read:
112: hasAccess = this .hasReadAccess(loggedInUser, command);
113: break;
114:
115: case write:
116: hasAccess = this .hasWriteAccess(loggedInUser, command);
117: break;
118:
119: case manage:
120: hasAccess = this .hasManageAccess(loggedInUser, command);
121: break;
122:
123: default:
124: //check if a filter needs to be applied here......
125: //only show resources that the user has write or more access to
126: if (cmsSecurityContext.getAttribute("applyFilter") != null) {
127: String path = (String) cmsSecurityContext
128: .getAttribute("applyFilter");
129: hasAccess = this .computeToolAccess(loggedInUser, path);
130: } else if (cmsSecurityContext.getAttribute("path") != null) {
131: String path = (String) cmsSecurityContext
132: .getAttribute("path");
133: hasAccess = this .computeAccess(loggedInUser, path,
134: "read");
135: }
136: //check if workflow management protection needs to be enforced
137: else if (cmsSecurityContext.getAttribute("manageWorkflow") != null) {
138: ApprovePublish service = (ApprovePublish) cmsSecurityContext
139: .getAttribute("approvePublish");
140: hasAccess = this .computeWorkflowManagementAccess(
141: loggedInUser, service.getManagers());
142: }
143: break;
144: }
145:
146: return hasAccess;
147: }
148:
149: /**
150: * @param command
151: * @return
152: */
153: private int getActionCode(JCRCommand command) {
154: int actionCode = -1;
155:
156: if (this .readCommands.contains(command.getClass().getName())) {
157: actionCode = read;
158: } else if (this .writeCommands.contains(command.getClass()
159: .getName())) {
160: actionCode = write;
161: } else if (this .manageCommands.contains(command.getClass()
162: .getName())) {
163: actionCode = manage;
164: }
165: return actionCode;
166: }
167:
168: //---------------------------------------------------------------------------------------------------------------------------------------
169: /**
170: * @param user
171: * @param command
172: * @return
173: */
174: private boolean hasReadAccess(User user, JCRCommand command) {
175: boolean hasReadAccess = false;
176:
177: String path = null;
178: if (command instanceof FolderGetListCommand) {
179: path = ((FolderGetListCommand) command).sFolderPath;
180: } else if (command instanceof FolderGetCommand) {
181: path = ((FolderGetCommand) command).msPath;
182: } else if (command instanceof FileGetCommand) {
183: path = ((FileGetCommand) command).path;
184: } else if (command instanceof FileGetListCommand) {
185: path = ((FileGetListCommand) command).sFilePath;
186: }
187:
188: hasReadAccess = this .computeAccess(user, path, "read");
189: if (!hasReadAccess) {
190: //make sure implied write is not available
191: hasReadAccess = this .computeAccess(user, path, "write");
192: if (!hasReadAccess) {
193: //make sure implied manage is not available
194: hasReadAccess = this
195: .computeAccess(user, path, "manage");
196: }
197: }
198:
199: return hasReadAccess;
200: }
201:
202: //-------------------------------------------------------------------------------------------------------------------------------------------
203: /**
204: * @param user
205: * @param command
206: * @return
207: */
208: private boolean hasWriteAccess(User user, JCRCommand command) {
209: boolean hasWriteAccess = false;
210:
211: String path = null;
212: if (command instanceof ContentCreateCommand) {
213: path = ((ContentCreateCommand) command).mFile.getBasePath();
214: } else if (command instanceof FileCreateCommand) {
215: path = ((FileCreateCommand) command).mFile.getBasePath();
216: } else if (command instanceof FolderCreateCommand) {
217: path = ((FolderCreateCommand) command).mFolder
218: .getBasePath();
219: } else if (command instanceof FileUpdateCommand) {
220: path = ((FileUpdateCommand) command).mFile.getBasePath();
221: } else if (command instanceof StoreArchiveCommand) {
222: path = ((StoreArchiveCommand) command).msRootPath;
223: } else if (command instanceof NewFileCommand) {
224: path = ((NewFileCommand) command).getPath();
225: } else if (command instanceof UpdateFileCommand) {
226: path = ((UpdateFileCommand) command).getPath();
227: }
228:
229: hasWriteAccess = this .computeAccess(user, path, "write");
230: if (!hasWriteAccess) {
231: //make sure implied manage is not available
232: hasWriteAccess = this .computeAccess(user, path, "manage");
233: }
234:
235: return hasWriteAccess;
236: }
237:
238: //-----------------------------------------------------------------------------------------------------------------------------------------
239: /**
240: * @param user
241: * @param command
242: * @return
243: */
244: private boolean hasManageAccess(User user, JCRCommand command) {
245: boolean hasManageAccess = false;
246:
247: String path = null;
248: if (command instanceof CopyCommand) {
249: path = ((CopyCommand) command).msFromPath;
250: hasManageAccess = this .computeAccess(user, path, "manage");
251: if (hasManageAccess) {
252: path = ((CopyCommand) command).msToPath;
253: hasManageAccess = this .computeAccess(user, path,
254: "manage");
255: }
256: } else if (command instanceof DeleteCommand) {
257: path = ((DeleteCommand) command).msPath;
258: hasManageAccess = this .computeAccess(user, path, "manage");
259: } else if (command instanceof MoveCommand) {
260: path = ((MoveCommand) command).msFromPath;
261: hasManageAccess = this .computeAccess(user, path, "manage");
262: if (hasManageAccess) {
263: path = ((MoveCommand) command).msToPath;
264: hasManageAccess = this .computeAccess(user, path,
265: "manage");
266: }
267: }
268:
269: return hasManageAccess;
270: }
271:
272: //-----------------------------------------------------------------------------------------------------------------------------------------
273: /**
274: *
275: */
276: private boolean computeAccess(User user, String path, String action) {
277: boolean hasAccess = false;
278:
279: //to prevent any administration issues, if the user is the 'cmsRootUser'
280: //treat him like a super user with access to everything in the cms
281: User root = this .authorizationManager.getProvider().getRoot();
282: if (user != null && user.getUserName() != null
283: && user.getUserName().equals(root.getUserName())) {
284: return true;
285: }
286:
287: //get the permissions available for the user in question
288: Collection userPermissions = this .getPermissions(user);
289:
290: //check against permissions that are explicitly specified on this node (file or folder)
291: Collection specificPermissions = this .getPermissions(path);
292: for (Iterator itr = specificPermissions.iterator(); itr
293: .hasNext();) {
294: Permission specificPermission = (Permission) itr.next();
295: if (specificPermission.getService().equals("cms")
296: && specificPermission.getAction().equals(action)) {
297: for (Iterator itr2 = userPermissions.iterator(); itr2
298: .hasNext();) {
299: Permission userPermission = (Permission) itr2
300: .next();
301: if (userPermission.getService().equals("cms")
302: && userPermission.getAction()
303: .equals(action)) {
304: String pathCriteria = userPermission
305: .findCriteriaValue("path");
306: if (pathCriteria.equals(path)) {
307: //this means this user has read access to this path
308: hasAccess = true;
309: }
310: }
311: }
312: }
313: }
314:
315: if (specificPermissions != null
316: && !specificPermissions.isEmpty()) {
317: //explicit permissions on this node have been specified....
318: //which override any permissions that could be inherited via the path hierarchy
319: return hasAccess;
320: }
321:
322: //check against the full path of this resource and make sure,
323: //there aren't any specific node permissions specified on any node along the path
324: //that excludes this user from having access for this action
325: StringTokenizer st = new StringTokenizer(path, "/");
326: StringBuffer buffer = new StringBuffer("/");
327: List list = new ArrayList();
328: list.add(new String(buffer.toString()));
329: while (st.hasMoreTokens()) {
330: String token = st.nextToken();
331:
332: buffer.append(token);
333: list.add(buffer.toString());
334:
335: //Make sure only path leading up to the resource is checked against.
336: //Not on the full path to the resource...
337: //Because if that was the case, the specificPermissions would have been applied
338: //in earlier checks...This is to check the recursive application of permissions
339: //to the resource in question
340: if (st.hasMoreTokens()) {
341: buffer.append("/");
342: } else {
343: continue;
344: }
345: }
346:
347: boolean explicitPermissionsFound = false;
348: Iterator it = list.iterator();
349: while (it.hasNext()) {
350: String currentPath = (String) it.next();
351: Collection permissions = this .getPermissions(currentPath);
352:
353: //perform processing for permissions explicitly set on this node
354: //in the path hierarchy
355: if (permissions != null && !permissions.isEmpty()) {
356: explicitPermissionsFound = true;
357:
358: //specific node permissions found on one of the nodes in the path...
359: //make sure the current user is listed to have access to this.
360: boolean accessFound = false;
361: for (Iterator itr = permissions.iterator(); itr
362: .hasNext();) {
363: Permission nodePermission = (Permission) itr.next();
364: if (nodePermission.getService().equals("cms")
365: && nodePermission.getAction()
366: .equals(action)) {
367: for (Iterator itr2 = userPermissions.iterator(); itr2
368: .hasNext();) {
369: Permission userPermission = (Permission) itr2
370: .next();
371: if (userPermission.getService().equals(
372: "cms")
373: && this .isActionImplied(
374: userPermission.getAction(),
375: action)) {
376: String pathCriteria = userPermission
377: .findCriteriaValue("path");
378: if (pathCriteria.equals(currentPath)) {
379: //this means this user has read access to this path
380: accessFound = true;
381: }
382: }
383: }
384: }
385: if (accessFound) {
386: break;
387: }
388: }
389: if (!accessFound) {
390: //the user does not have access through the path hierarchy
391: return false;
392: }
393: }
394: }
395:
396: //if i am here the user has access to this node via path hierarchy inheritance
397: if (explicitPermissionsFound) {
398: //and without the hierarchy access *not being overriden* by any *explicit permissions*
399: //on nodes in the hierarchy
400: hasAccess = true;
401: } else {
402: //there were no permissions found anywhere throughout the resource's path hierarchy
403: hasAccess = false;
404: }
405:
406: return hasAccess;
407: }
408:
409: /**
410: * This is used to filter out cms resources in the CMS Admin tool, so that the user can see only the resources that
411: * he has write/manage access to
412: *
413: * @param user
414: * @param path
415: * @return
416: */
417: private boolean computeToolAccess(User user, String path) {
418: boolean toolAccess = false;
419:
420: //to prevent any administration issues, if the user is the 'cmsRoot'
421: //treat him like a super user with access to everything in the cms
422: User root = this .authorizationManager.getProvider().getRoot();
423: if (user != null && user.getUserName() != null
424: && user.getUserName().equals(root.getUserName())) {
425: return true;
426: }
427:
428: //get the permissions available for the user in question
429: Collection userPermissions = this .getPermissions(user);
430:
431: //check against permissions that are explicitly specified on this node (file or folder)
432: Collection specificPermissions = this .getPermissions(path);
433: for (Iterator itr = specificPermissions.iterator(); itr
434: .hasNext();) {
435: Permission specificPermission = (Permission) itr.next();
436: if ((specificPermission.getService().equals("cms"))
437: && (specificPermission.getAction().equals("write") || specificPermission
438: .getAction().equals("manage"))) {
439: for (Iterator itr2 = userPermissions.iterator(); itr2
440: .hasNext();) {
441: Permission userPermission = (Permission) itr2
442: .next();
443: if ((userPermission.getService().equals("cms"))
444: && (userPermission.getAction().equals(
445: "write") || userPermission
446: .getAction().equals("manage"))) {
447: String pathCriteria = userPermission
448: .findCriteriaValue("path");
449: if (pathCriteria.equals(path)) {
450: //this means this user has read access to this path
451: toolAccess = true;
452: }
453: }
454: }
455: }
456: }
457:
458: if (specificPermissions != null
459: && !specificPermissions.isEmpty()) {
460: //explicit permissions on this node have been specified....
461: //which override any permissions that could be inherited via the path hierarchy
462: return toolAccess;
463: }
464:
465: //if i am here...calculate based on permissions inherited via path hierarchy
466: Collection writeOrMoreCriteria = this
467: .getWriteOrMore(userPermissions);
468: for (Iterator itr = writeOrMoreCriteria.iterator(); itr
469: .hasNext();) {
470: Criteria cour = (Criteria) itr.next();
471: if (this .doesPathMatchPattern(path, cour.getValue())) {
472:
473: toolAccess = true;
474: break;
475: }
476: }
477:
478: return toolAccess;
479: }
480:
481: /**
482: * @param user
483: * @return
484: */
485: private boolean computeWorkflowManagementAccess(User user,
486: Set managerRoles) {
487: if (managerRoles == null || managerRoles.isEmpty()) {
488: return false;
489: }
490:
491: //now check to see if the currently logged in user has workflow management access
492: try {
493: boolean hasAccess = false;
494:
495: Set userRoles = this .authorizationManager.getProvider()
496: .getMembershipModule().getRoles(user);
497:
498: if (userRoles != null) {
499: for (Iterator itr = userRoles.iterator(); itr.hasNext();) {
500: Role userRole = (Role) itr.next();
501: String userRoleName = userRole.getName();
502: if (managerRoles.contains(userRoleName)) {
503: hasAccess = true;
504: break;
505: }
506: }
507: }
508:
509: return hasAccess;
510: } catch (Exception e) {
511: throw new RuntimeException(e);
512: }
513: }
514:
515: //----------------------------------------------------------------------------------------------------------------------------------------------
516: /**
517: * @param user
518: * @return
519: */
520: private Collection getPermissions(User user) {
521: Collection permissions = null;
522:
523: if (user != null) {
524: //this is not an anonymous access
525: String userId = user.getUserName();
526: String uri = this .authorizationManager.getProvider()
527: .getUserURI(userId);
528: permissions = this .authorizationManager.getProvider()
529: .getSecurityBindings(uri);
530: } else {
531: //this is an anonymous access
532: String uri = this .authorizationManager.getProvider()
533: .getRoleURI(AuthorizationManager.Anonymous);
534: permissions = this .authorizationManager.getProvider()
535: .getSecurityBindings(uri);
536: }
537: return permissions;
538: }
539:
540: /**
541: * @param user
542: * @return
543: */
544: private Collection getPermissions(String path) {
545: Criteria criteria = new Criteria("path", path);
546:
547: String uri = this .authorizationManager
548: .getProvider()
549: .getCriteriaURI(criteria.getName(), criteria.getValue());
550:
551: return this .authorizationManager.getProvider()
552: .getSecurityBindings(uri);
553: }
554:
555: /**
556: * @param allPermissions
557: * @return
558: */
559: private Collection getWriteOrMore(Collection allPermissions) {
560: Collection writeOrMore = new HashSet();
561:
562: if (allPermissions != null) {
563: for (Iterator itr = allPermissions.iterator(); itr
564: .hasNext();) {
565: Permission cour = (Permission) itr.next();
566: if ((cour.getService().equals("cms"))
567: && (cour.getAction().equals("write") || cour
568: .getAction().equals("manage"))) {
569: writeOrMore.addAll(cour.getCriteria());
570: }
571: }
572: }
573:
574: return writeOrMore;
575: }
576:
577: /**
578: * @param path
579: * @param pattern
580: * @return
581: */
582: private boolean doesPathMatchPattern(String path, String pattern) {
583: boolean match = true;
584:
585: //format the path first before starting to match it with the specified pattern
586: if (!path.startsWith("/")) {
587: path = "/" + path;
588: }
589: if (!path.endsWith("/")) {
590: path = path + "/";
591: }
592:
593: StringTokenizer patternTokenizer = new StringTokenizer(pattern,
594: "/");
595: StringTokenizer pathTokenizer = new StringTokenizer(path, "/");
596: StringBuffer pathMatched = new StringBuffer("/");
597: StringBuffer patternMatched = new StringBuffer();
598: if (pattern.startsWith("/")) {
599: patternMatched.append("/");
600: }
601: while (patternTokenizer.hasMoreTokens()
602: && pathTokenizer.hasMoreTokens()) {
603: String patternToken = patternTokenizer.nextToken();
604: String pathToken = pathTokenizer.nextToken();
605:
606: //setup token tracking
607: pathMatched.append(pathToken + "/");
608: if (patternTokenizer.hasMoreTokens()) {
609: patternMatched.append(patternToken + "/");
610: } else {
611: patternMatched.append(patternToken);
612: }
613:
614: //perform token matching
615: if (!match) {
616: continue;
617: }
618: int wildCardIndex = patternToken.indexOf('*');
619: //if wildCard is not relevant
620: if (wildCardIndex <= 0) {
621: //if wildCardIndex == 0 then this token matches...
622: if (wildCardIndex != 0
623: && !pathToken.equals(patternToken)) {
624: match = false;
625: }
626: } else {
627: String wildPath = pathToken.substring(0, wildCardIndex);
628: String wildPattern = patternToken.substring(0,
629: wildCardIndex);
630: if (!wildPath.equals(wildPattern)) {
631: match = false;
632: }
633: }
634: }
635:
636: return match;
637: }
638:
639: /**
640: * @param action
641: * @param impliedTarget
642: * @return
643: */
644: private boolean isActionImplied(String action, String impliedTarget) {
645: boolean implied = false;
646:
647: if (impliedTarget.equalsIgnoreCase("read")) {
648: if (action.equalsIgnoreCase("read")
649: || action.equalsIgnoreCase("write")
650: || action.equalsIgnoreCase("manage")) {
651: implied = true;
652: }
653: } else if (impliedTarget.equalsIgnoreCase("write")) {
654: if (action.equalsIgnoreCase("write")
655: || action.equalsIgnoreCase("manage")) {
656: implied = true;
657: }
658: } else if (impliedTarget.equalsIgnoreCase("manage")) {
659: if (action.equalsIgnoreCase("manage")) {
660: implied = true;
661: }
662: }
663:
664: return implied;
665: }
666: }
|