001: /*
002: * Copyright 2001-2006 C:1 Financial Services GmbH
003: *
004: * This software is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License Version 2.1, as published by the Free Software Foundation.
007: *
008: * This software is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
011: * Lesser General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public
014: * License along with this library; if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
016: */
017:
018: package de.finix.contelligent.components.util;
019:
020: import java.io.IOException;
021: import java.io.Writer;
022: import java.text.ParseException;
023: import java.util.ArrayList;
024: import java.util.Collection;
025: import java.util.Collections;
026: import java.util.Iterator;
027: import java.util.LinkedList;
028: import java.util.List;
029: import java.util.Map;
030:
031: import de.finix.contelligent.CallData;
032: import de.finix.contelligent.Component;
033: import de.finix.contelligent.ComponentManager;
034: import de.finix.contelligent.ComponentPath;
035: import de.finix.contelligent.ExternalRelationSource;
036: import de.finix.contelligent.ModificationVetoException;
037: import de.finix.contelligent.components.Folder;
038: import de.finix.contelligent.core.ContelligentSession;
039: import de.finix.contelligent.core.security.ContelligentSecurityManager;
040: import de.finix.contelligent.core.security.Role;
041: import de.finix.contelligent.core.security.User;
042: import de.finix.contelligent.exception.ContelligentException;
043: import de.finix.contelligent.exception.ContelligentRuntimeException;
044: import de.finix.contelligent.logging.LoggingService;
045: import de.finix.contelligent.render.ParameterDescription;
046: import de.finix.contelligent.render.Renderable;
047: import de.finix.contelligent.render.Renderer;
048: import de.finix.contelligent.util.StringUtils;
049:
050: public class RoleDependendRenderer extends Folder implements
051: Renderable, Renderer, ExternalRelationSource {
052:
053: final static org.apache.log4j.Logger log = LoggingService
054: .getLogger(RoleDependendRenderer.class);
055:
056: public enum Operator {
057: AND, OR, XOR, NOT
058: };
059:
060: private String roles;
061:
062: private ComponentPath pathIfTrue = null;
063:
064: private ComponentPath pathIfFalse = null;
065:
066: private boolean advancedMode = false;
067:
068: private static final ContelligentSecurityManager securityManager = ContelligentSecurityManager
069: .getInstance();
070:
071: protected List roleList;
072:
073: protected Eval eval = null;
074:
075: public ComponentPath getPathIfFalse() {
076: return pathIfFalse;
077: }
078:
079: public void setPathIfFalse(ComponentPath pathIfFalse) {
080: this .pathIfFalse = pathIfFalse;
081: }
082:
083: public ComponentPath getPathIfTrue() {
084: return pathIfTrue;
085: }
086:
087: public void setPathIfTrue(ComponentPath pathIfTrue) {
088: this .pathIfTrue = pathIfTrue;
089: }
090:
091: public String getRoles() {
092: return roles;
093: }
094:
095: public void setRoles(String roles) {
096: this .roles = roles;
097: }
098:
099: public void setAdvancedMode(boolean advancedMode) {
100: this .advancedMode = advancedMode;
101: }
102:
103: public boolean getAdvancedMode() {
104: return advancedMode;
105: }
106:
107: public void postCreate() throws Exception {
108: super .postCreate();
109: if (!advancedMode) {
110: // Old style
111: List roleStringList = StringUtils.tokenizeCSV(roles, ',');
112: roleList = new ArrayList(roleStringList.size());
113: Iterator roleStrings = roleStringList.iterator();
114: while (roleStrings.hasNext()) {
115: roleList.add(securityManager
116: .getRole((String) roleStrings.next()));
117: }
118: } else {
119: // New style
120: try {
121: eval = parseExpr(null, roles);
122: } catch (ParseException pe) {
123: // On error, eval will remain null to force an error
124: // on rendering and thus prevent access to potentially
125: // security critical content.
126: log.error("Could not evaluate roles expression.", pe);
127: }
128: }
129: }
130:
131: /**
132: * Processes the boolean expression into a tree of Eval objects.
133: * These can then quickly evaluate the expression for each user
134: * calling this renderer. The 'initial' parameter can be used
135: * to externally specify the left operand, otherwise this is set
136: * to null.
137: */
138: private Eval parseExpr(Object initial, String expr)
139: throws ParseException {
140: int level = 0; // Subexpression level
141: int start = -1; // Stores the beginning of the currently handled token
142: Object token1 = initial;
143: Operator token2 = null;
144: Object token3 = null;
145: if (log.isDebugEnabled()) {
146: log.debug("Parsing [" + expr + "]");
147: }
148: int i;
149: for (i = 0; i < expr.length(); i++) {
150: char c = expr.charAt(i);
151: if (c == '(') {
152: // Start subexpression
153: if (level == 0) {
154: if ((token1 != null) && (token2 == null)) {
155: throw new ParseException(
156: "Found '(' where operator was expected.",
157: i);
158: }
159: if (start != -1) {
160: throw new ParseException(
161: "Found '(' without preceding space.", i);
162: }
163: start = i;
164: }
165: level++;
166: } else if (c == ')') {
167: level--;
168: if (level < 0) {
169: throw new ParseException(
170: "Found ')' without matching '('.", i);
171: } else if (level == 0) {
172: Eval subEval = parseExpr(null, expr.substring(
173: start + 1, i));
174: if ((token1 == null) && (token2 == null)) {
175: token1 = subEval;
176: } else if (token2 == null) {
177: throw new ParseException(
178: "Cant use a subexpression as operator.",
179: i);
180: } else if (token3 == null) {
181: token3 = subEval;
182: // Immediately exit the main loop once token3 is set
183: break;
184: } else {
185: throw new ParseException(
186: "Internal error: Subexpression found after last part of expression.",
187: i);
188: }
189: start = -1;
190: }
191: } else if ((c == ' ') || (i == expr.length() - 1)) {
192: if ((level == 0) && (start != -1)) {
193: String t;
194: // The trim() is here because the selected range grabs
195: // the following space if the token is terminated by
196: // space. This range is needed though in case the token
197: // is terminated by the end of the expression.
198: t = expr.substring(start, i + 1).trim();
199: if ((token1 == null) && (token2 == null)) {
200: // This is the first token
201: if (t.equalsIgnoreCase("not")) {
202: // Special case for our unary operator. Note
203: // that token1 remains null here.
204: token2 = Operator.NOT;
205: } else {
206: token1 = t;
207: }
208: start = -1;
209: } else if (token2 == null) {
210: // This is the second token, but not the last
211: if (t.equalsIgnoreCase("and")) {
212: token2 = Operator.AND;
213: } else if (t.equalsIgnoreCase("or")) {
214: token2 = Operator.OR;
215: } else if (t.equalsIgnoreCase("xor")) {
216: token2 = Operator.XOR;
217: } else {
218: throw new ParseException(
219: "Unknown operator: " + t, i);
220: }
221: start = -1;
222: } else {
223: if (t.equalsIgnoreCase("not")) {
224: // To handle an unary operator found for the
225: // right operand, we turn it into a subexpression.
226: int localLevel = 0;
227: for (int j = i + 1; j < expr.length(); j++) {
228: // Look ahead for end of NOT expression
229: char c2 = expr.charAt(j);
230: if (c2 == '(') {
231: localLevel++;
232: } else if (c2 == ')') {
233: localLevel--;
234: if (localLevel < 0) {
235: throw new ParseException(
236: "Found ')' without matching '('.",
237: i);
238: }
239: }
240: if (((c2 == ' ') || (j == expr.length() - 1))
241: && (localLevel == 0)) {
242: // End of NOT
243: token3 = parseExpr(null, expr
244: .substring(start, j + 1));
245: i = j;
246: break;
247: } else if (j == expr.length() - 1) {
248: throw new ParseException(
249: "Unclosed '(' detected.", j);
250: }
251: }
252: } else {
253: token3 = t;
254: }
255: // Immediately exit the main loop once token3 is set
256: break;
257: }
258: }
259: } else {
260: // Some non-whitespace, non-special character
261: if (start == -1) {
262: start = i;
263: }
264: }
265: if ((i == expr.length() - 1) && (start != -1)) {
266: throw new ParseException("Unclosed '(' detected.", i);
267: }
268: }
269:
270: // Parsing is complete. Now we either have our desired tokens
271: // or the expression was unparseable.
272: if (log.isDebugEnabled()) {
273: log.debug("Tokens: [" + token1 + "] [" + token2 + "] ["
274: + token3 + "]");
275: }
276: if ((token1 != null) && (token2 == null)) {
277: // To avoid further complication of the main parsing logic,
278: // the case of a single string without further tokens
279: // is handled as (token OR token).
280: return new Eval(token1, Operator.OR, token1);
281: } else if ((token1 == null) && (token2 == null)
282: && (token3 == null)) {
283: throw new ParseException("Empty string in expression.", 0);
284: } else if ((token2 != null) && (token3 == null)) {
285: // An operator was specified, but no operand followed it.
286: throw new ParseException("Expression missing final part.",
287: 0);
288: } else {
289: Eval e = new Eval(token1, token2, token3);
290: if (i == expr.length() - 1) {
291: return e;
292: } else {
293: return parseExpr(e, expr.substring(i + 1));
294: }
295: }
296: }
297:
298: /**
299: * Evaluates an expression or subexpression as true or false.
300: */
301: protected class Eval {
302: Operator type;
303: Object operand1;
304: Object operand2;
305:
306: /**
307: * The supplied operands may be either other Eval objects or
308: * Boolean objects or Strings specifying either a user, a role,
309: * 'true' or 'false'. For the special case of unary operators
310: * (currently only 'NOT'), use operand2 to store the operand;
311: * operand1 is ignored in that case and may be set to null.
312: */
313: protected Eval(Object operand1, Operator type, Object operand2) {
314: this .type = type;
315: this .operand1 = operand1;
316: this .operand2 = operand2;
317: }
318:
319: public boolean test(CallData callData) {
320: if (type == Operator.NOT) {
321: return !partTest(operand2, callData);
322: } else if (type == Operator.AND) {
323: return (partTest(operand1, callData) && partTest(
324: operand2, callData));
325: } else if (type == Operator.OR) {
326: return (partTest(operand1, callData) || partTest(
327: operand2, callData));
328: } else if (type == Operator.XOR) {
329: return (partTest(operand1, callData) ^ partTest(
330: operand2, callData));
331: } else {
332: throw new ContelligentRuntimeException(
333: "Internal error: Unsupported operator encountered.");
334: }
335: }
336:
337: protected boolean partTest(Object o, CallData callData) {
338: if (o instanceof Eval) {
339: return ((Eval) o).test(callData);
340: } else if (o instanceof String) {
341: String name = (String) o;
342: if (name.equalsIgnoreCase("true")) {
343: return true;
344: } else if (name.equalsIgnoreCase("false")) {
345: return false;
346: }
347: User user = callData.getContelligentSession().getUser();
348: if (user.toPrincipalString().equals(name)) {
349: return true;
350: } else {
351: try {
352: Role role = securityManager.getRole(name);
353: if (user.hasRole(role)) {
354: return true;
355: }
356: } catch (ContelligentException ce) {
357: log.error("Unable to retrieve role.", ce);
358: }
359: return false;
360: }
361: } else if (o instanceof Boolean) {
362: return ((Boolean) o).booleanValue();
363: } else {
364: throw new ContelligentRuntimeException(
365: "Internal error: Unsupported operand type encountered.");
366: }
367: }
368: }
369:
370: public ParameterDescription[] getParameterDescription() {
371: return null;
372: }
373:
374: public Collection getSensitiveCategories() {
375: return Collections.EMPTY_SET;
376: }
377:
378: public void render(Writer writer, Map parameterMap,
379: CallData callData) throws ContelligentException,
380: IOException {
381: ContelligentSession session = (ContelligentSession) callData
382: .getContelligentSession();
383: User caller = session.getUser();
384: boolean result = true;
385: if (!advancedMode) {
386: Collection userRoles = caller.getRoles();
387: Iterator rolesToCheck = roleList.iterator();
388: while (rolesToCheck.hasNext() && result) {
389: Role role = (Role) rolesToCheck.next();
390: if (!userRoles.contains(role))
391: result = false;
392: }
393: } else {
394: if (eval == null) {
395: throw new ContelligentException(
396: "Invalid role expression.");
397: }
398: result = eval.test(callData);
399: }
400:
401: ComponentManager manager = callData.getActualManager();
402: Component component = null;
403: if (result) {
404: if (pathIfTrue != null) {
405: if (log.isDebugEnabled())
406: log
407: .debug("'"
408: + this
409: + "':render() - user '"
410: + caller
411: + "' does have all required roles, rendering '"
412: + pathIfTrue + "' ...");
413: component = manager.getSubcomponent(this , pathIfTrue,
414: callData);
415: }
416: } else {
417: if (pathIfFalse != null) {
418: if (log.isDebugEnabled())
419: log
420: .debug("'"
421: + this
422: + "':render() - user '"
423: + caller
424: + "' does NOT have all required roles, rendering '"
425: + pathIfFalse + "' ...");
426: component = manager.getSubcomponent(this , pathIfFalse,
427: callData);
428: }
429: }
430:
431: if (component != null) {
432: if (component instanceof Renderable) {
433: ((Renderable) component).getRenderer().render(writer,
434: parameterMap, callData);
435: } else {
436: log.error("component '" + component
437: + "' is not renderable!");
438: }
439: }
440: return;
441: }
442:
443: public Renderer getRenderer() {
444: return this ;
445: }
446:
447: /*
448: * (non-Javadoc)
449: *
450: * @see de.finix.contelligent.ExternalRelationSource#relatedPaths()
451: */
452: public List relatedPaths() {
453: LinkedList list = new LinkedList();
454: list.addLast(getPathIfTrue());
455: list.addLast(getPathIfFalse());
456: return list;
457: }
458:
459: /*
460: * (non-Javadoc)
461: *
462: * @see de.finix.contelligent.ExternalRelationSource#relatedPaths(java.util.List)
463: */
464: public void relatedPaths(List newTargetPaths)
465: throws ModificationVetoException {
466: if (newTargetPaths == null || newTargetPaths.size() < 2
467: || newTargetPaths.size() > 2) {
468: throw new ModificationVetoException(
469: "illegal state: pathlist is'" + newTargetPaths
470: + "'");
471: }
472: setPathIfTrue((ComponentPath) newTargetPaths.get(0));
473: setPathIfFalse((ComponentPath) newTargetPaths.get(1));
474: }
475: }
|