1 10 package org.mmbase.security.implementation.context; 11 12 import org.mmbase.bridge.Query; 13 import org.mmbase.cache.Cache; 14 import org.mmbase.storage.search.*; 15 import java.util.*; 16 17 import org.w3c.dom.*; 18 import org.w3c.dom.traversal.NodeIterator; 19 import org.xml.sax.InputSource ; 20 import org.apache.xpath.XPathAPI; 21 22 import org.mmbase.module.core.MMObjectNode; 23 import org.mmbase.security.*; 24 import org.mmbase.security.SecurityException; import org.mmbase.util.logging.Logger; 27 import org.mmbase.util.logging.Logging; 28 29 40 public class ContextAuthorization extends Authorization { 41 private static final Logger log = Logging.getLoggerInstance(ContextAuthorization.class); 42 private Document document; 43 private ContextCache cache = new ContextCache(); 44 45 protected Cache allowingContextsCache = new Cache(200) { public String getName() { return "CS:AllowingContextsCache"; } 47 public String getDescription() { return "Links user id to a set of contexts"; } 48 }; 49 50 private int maxContextsInQuery = 50; 52 53 private Set globalAllowedOperations = new HashSet(); 54 55 private Map replaceNotFound = new HashMap(); 56 private Map userDefaultContexts = new HashMap(); 57 private SortedSet allContexts; 58 59 protected void load() { 60 log.debug("using: '" + configResource + "' as config file for authentication"); 61 try { 62 InputSource in = MMBaseCopConfig.securityLoader.getInputSource(configResource); 63 replaceNotFound.clear(); 65 allowingContextsCache.clear(); 66 userDefaultContexts.clear(); 68 document = org.mmbase.util.XMLBasicReader.getDocumentBuilder(this.getClass()).parse(in); 70 getGlobalAllowedOperations(); 71 setAllContexts(); 72 } catch(org.xml.sax.SAXException se) { 73 log.error("error parsing file :"+configResource); 74 String message = "error loading configfile :'" + configResource + "'("+se + "->"+se.getMessage()+"("+se.getMessage()+"))"; 75 log.error(message); 76 log.error(Logging.stackTrace(se)); 77 throw new SecurityException (message); 78 } catch(java.io.IOException ioe) { 79 log.error("error parsing file :"+configResource); 80 log.error(Logging.stackTrace(ioe)); 81 throw new SecurityException ("error loading configfile :'"+configResource+"'("+ioe+")" ); 82 } 83 log.debug("loaded: '" + configResource + "' as config file for authorization"); 84 } 85 86 public String getDefaultContext(UserContext user) throws SecurityException { 87 String defaultContext = (String )userDefaultContexts.get(user); 88 if (defaultContext == null) { 89 String xpath = "/contextconfig/accounts/user[@name='"+user.getIdentifier()+"']"; 90 Node found; 91 try { 92 log.debug("going to execute the query:" + xpath + " on file : " + configResource); 93 found = XPathAPI.selectSingleNode(document, xpath); 94 } catch(javax.xml.transform.TransformerException te) { 95 log.error("error executing query: '"+xpath+"' on file: '"+configResource+"'" ); 96 log.error( Logging.stackTrace(te)); 97 throw new SecurityException ("error executing query: '"+xpath+"' on file: '"+configResource+"'"); 98 } 99 if (found == null) { 100 throw new SecurityException ("Could not find user " + user.getIdentifier() + " in context security config file (" + configResource + ")") ; 101 } 102 103 NamedNodeMap nnm = found.getAttributes(); 104 Node contextNode = nnm.getNamedItem("context"); 105 defaultContext = contextNode.getNodeValue(); 106 userDefaultContexts.put(user,defaultContext); 107 } 108 if (log.isDebugEnabled()) { 109 log.debug("user with name: " + user + " has the default context: " + defaultContext); 110 } 111 return defaultContext; 112 } 113 114 public void create(UserContext user, int nodeNumber) throws SecurityException { 115 if (log.isDebugEnabled()) { 116 log.debug("create on node #" + nodeNumber + " by user: " + user); 117 } 118 String defaultContext = getDefaultContext(user); 119 setContext(user, nodeNumber, defaultContext); 120 } 121 122 public void update(UserContext user, int nodeNumber) throws SecurityException { 123 if (log.isDebugEnabled()) { 124 log.debug("update on node #" + nodeNumber+" by user: " + user); 125 } 126 } 127 128 public void remove(UserContext user, int nodeNumber) throws SecurityException { 129 if (log.isDebugEnabled()) { 130 log.debug("remove on node #" + nodeNumber + " by user: " + user); 131 } 132 } 133 134 public void setContext(UserContext user, int nodeNumber, String context) throws SecurityException { 135 if (log.isDebugEnabled()) { 137 log.debug("set context on node #"+nodeNumber+" by user: " + user + " to " + context ); 138 } 139 MMObjectNode node = getMMNode(nodeNumber); 141 if (node.getStringValue("owner").equals(context)) return; 142 143 Set possible = getPossibleContexts(user, nodeNumber); 145 if(!possible.contains(context)) { 146 String msg = "could not set the context to "+context+" for node #"+nodeNumber+" by user: " +user; 147 log.error(msg); 148 throw new SecurityException (msg); 149 } 150 151 verify(user, nodeNumber, Operation.CHANGE_CONTEXT); 153 154 node.setValue("owner", context); 156 node.commit(); 157 if (log.isDebugEnabled()) { 158 log.debug("changed context settings of node #"+nodeNumber+" to context: "+context+ " by user: " +user); 159 } 160 } 161 162 public String getContext(UserContext user, int nodeNumber) throws SecurityException { 163 if (log.isDebugEnabled()) { 165 log.debug("get context on node #" + nodeNumber + " by user: " + user); 166 } 167 168 verify(user, nodeNumber, Operation.READ); 170 171 MMObjectNode node = getMMNode(nodeNumber); 173 return node.getStringValue("owner"); 174 } 175 176 private void setAllContexts() throws SecurityException { 177 allContexts = new TreeSet(); 178 String xpath = "/contextconfig/contexts/context"; 179 log.trace("going to execute the query:" + xpath ); 180 NodeIterator found; 181 try { 182 found = XPathAPI.selectNodeIterator(document, xpath); 183 } catch(javax.xml.transform.TransformerException te) { 184 log.error("error executing query: '" + xpath + "' "); 185 log.error( Logging.stackTrace(te)); 186 throw new SecurityException ("error executing query: '" + xpath +"' "); 187 } 188 Node context; 189 for(context = found.nextNode(); context != null; context = found.nextNode()) { 190 NamedNodeMap nnm = context.getAttributes(); 191 Node contextNameNode = nnm.getNamedItem("name"); 192 allContexts.add(contextNameNode.getNodeValue()); 193 } 194 } 195 196 public Set getPossibleContexts(UserContext user, int nodeNumber) throws SecurityException { 197 if (log.isDebugEnabled()) { 198 log.debug("get possible context on node #" + nodeNumber + " by user: " + user); 199 } 200 201 verify(user, nodeNumber, Operation.READ); 204 205 String currentContext = getContext(user, nodeNumber); 207 synchronized(replaceNotFound) { 208 if(replaceNotFound.containsKey(currentContext)) { 209 currentContext = (String )replaceNotFound.get(currentContext); 210 } 211 } 212 213 Set list; 214 synchronized(cache) { 215 list = cache.contextGet(currentContext); 216 if(list != null) { 217 log.debug("cache hit"); 218 return list; 219 } 220 list = new HashSet(); 221 } 222 223 String xpath = "/contextconfig/contexts/context[@name='"+currentContext+"']/possible"; 225 log.debug("going to execute the query:" + xpath ); 226 NodeIterator found; 227 try { 228 found = XPathAPI.selectNodeIterator(document, xpath); 229 } catch(javax.xml.transform.TransformerException te) { 230 log.error("error executing query: '"+xpath+"' "); 231 log.error( Logging.stackTrace(te)); 232 throw new SecurityException ("error executing query: '"+xpath+"' "); 233 } 234 Node context; 235 for(context = found.nextNode(); context != null; context = found.nextNode()) { 236 NamedNodeMap nnm = context.getAttributes(); 237 Node contextNameNode = nnm.getNamedItem("context"); 238 list.add(contextNameNode.getNodeValue()); 239 if (log.isDebugEnabled()) { 240 log.debug("the context: "+contextNameNode.getNodeValue() +" is possible context for node #"+nodeNumber+" by user: " +user); 241 } 242 } 243 synchronized(cache) { 244 cache.contextAdd(currentContext, list); 245 } 246 return list; 247 } 248 249 public boolean check(UserContext user, int nodeNumber, Operation operation) throws SecurityException { 250 if (log.isDebugEnabled()) { 251 log.debug("check on node #" + nodeNumber + " by user: " + user + " for operation " + operation); 252 } 253 254 if(!manager.getAuthentication().isValid(user)) { 256 String msg = "the usercontext was expired"; 257 log.error(msg); 258 throw new java.lang.SecurityException (msg); 259 } 260 if(globalAllowedOperations.contains(operation)) { 262 log.debug("not retrieving the node, since operation:" + operation + " is granted to everyone"); 263 return true; 264 } 265 266 MMObjectNode node = getMMNode(nodeNumber); 268 String context = node.getStringValue("owner"); 269 270 return check(user, context, operation.toString()); 271 } 272 273 private boolean check(UserContext user, int nodeNumber, String operation) throws SecurityException { 274 return check(user, getContext(user, nodeNumber), operation); 275 } 276 277 private boolean check(UserContext user, String context, Operation operation) throws SecurityException { 278 return check(user, context, operation.toString()); 279 } 280 281 private boolean check(UserContext user, String context, String operation) throws SecurityException { 282 synchronized(cache) { 284 Boolean result = cache.rightGet(operation, context, user.getIdentifier()); 285 if(result != null) { 286 log.trace("cache hit"); 287 return result.booleanValue(); 288 } 289 } 290 291 String xpath; 292 xpath = "/contextconfig/contexts/context[@name='"+context+"']"; 293 Node found; 294 try { 295 if (log.isDebugEnabled()) { 296 log.trace("going to execute the query:" + xpath ); 297 } 298 found = XPathAPI.selectSingleNode(document, xpath); 299 300 if (found == null) { log.warn("context with name :'" + context + "' was not found in the configuration " + configResource ); 302 303 xpath = "/contextconfig/contexts/context[@name = ancestor::contexts/@default]"; 305 306 if (log.isDebugEnabled()) { 307 log.trace("going to execute the query:" + xpath + " on file : " + configResource); 308 } 309 310 found = XPathAPI.selectSingleNode(document, xpath); 311 312 if (found == null) { 313 throw new SecurityException ("Configuration error: Context " + context + " not found and no default context found either (change " + configResource + ")"); 314 } 315 316 NamedNodeMap nnm = found.getAttributes(); 318 Node defaultContextNode = nnm.getNamedItem("name"); 319 String defaultContext = defaultContextNode.getNodeValue(); 320 321 synchronized(replaceNotFound) { 322 replaceNotFound.put(context, defaultContext); 323 } 324 } 325 326 329 xpath = "operation[@type='" + operation + "']/grant"; 331 if (log.isDebugEnabled()) { 332 log.debug("going to execute the query:" + xpath + " On " + found.toString()); 333 } 334 NodeList grants = XPathAPI.selectNodeList(found, xpath); 335 336 if (log.isDebugEnabled()) { 337 log.debug("Found " + grants.getLength() + " grants on " + operation + " for context " + context) ; 338 } 339 340 Set allowedGroups = new HashSet(); 341 for(int currentNode = 0; currentNode < grants.getLength(); currentNode++) { 342 Node contains = grants.item(currentNode); 343 NamedNodeMap nnm = contains.getAttributes(); 344 Node groupNameNode = nnm.getNamedItem("group"); 345 if (groupNameNode == null) { 346 throw new SecurityException ("Configuration error: 'grant' element must contain attribute 'group'"); 347 } 348 allowedGroups.add(groupNameNode.getNodeValue()); 349 if (log.isDebugEnabled()) { 350 log.debug("the group "+groupNameNode.getNodeValue() +" is granted for context " + context); 351 } 352 } 353 354 boolean allowed = userInGroups(user.getIdentifier(), allowedGroups, new HashSet()); 355 if (log.isDebugEnabled()) { 356 if (allowed) { 357 log.debug("operation " + operation + " was permitted for user with id " + user); 358 } else { 359 log.debug("operation " + operation + " was NOT permitted for user with id " + user); 360 } 361 } 362 363 synchronized(cache) { 365 cache.rightAdd(operation, context, user.getIdentifier(), allowed); 366 } 367 368 return allowed; 369 370 } catch(javax.xml.transform.TransformerException te) { 371 log.error("Error executing query."); 372 log.error( Logging.stackTrace(te)); 373 throw new java.lang.SecurityException ("error executing query: '"+xpath+"' "); 374 } 375 376 } 377 378 379 380 private boolean userInGroups(String user, Set groups, Set done) { 381 if(groups.size() == 0) { 383 log.debug("entering userInGroups(recursive) with username: '"+user+"' without any groups, so user was not found.."); 384 return false; 385 } 386 log.debug("entering userInGroups(recursive) with username: '"+user+"' and look if the user is in the following groups:"); 387 388 if (log.isDebugEnabled()) { 389 Iterator di = groups.iterator(); 390 while (di.hasNext()) { 391 log.debug("\t -> group : " + di.next()); 392 } 393 } 394 395 Iterator i = groups.iterator(); 396 Set fetchedGroups = new HashSet(); 397 while(i.hasNext()) { 398 String groupname = (String )i.next(); 400 done.add(groupname); 402 log.debug("\tresearching group with name : "+groupname); 403 404 String xpath = "/contextconfig/groups/group[@name='"+groupname+"']/contains"; 406 log.debug("\tgoing to execute the query:" + xpath ); 407 NodeIterator found; 408 try { 409 found = XPathAPI.selectNodeIterator(document, xpath); 410 } catch(javax.xml.transform.TransformerException te) { 411 log.error("error executing query: '"+xpath+"' "); 412 log.error( Logging.stackTrace(te)); 413 throw new java.lang.SecurityException ("error executing query: '"+xpath+"' "); 414 } 415 for(Node contains = found.nextNode(); contains != null; contains = found.nextNode()) { 417 NamedNodeMap nnm = contains.getAttributes(); 418 String type = nnm.getNamedItem("type").getNodeValue(); 419 String named = nnm.getNamedItem("named").getNodeValue(); 420 log.debug("\t<contains type=\""+type+"\" named=\""+named+"\" />"); 421 if(type.equals("group")) { 422 if(!done.contains(named)) { 425 log.debug("\tfound a new group with name "+named+", which could contain our user, adding it to the to fetch list"); 426 fetchedGroups.add(named); 427 } 428 } else if(type.equals("user")) { 429 if(named.equals(user)){ 431 log.debug("found the user with name " + named + " thus allowed." ); 432 return true; 433 } 434 log.debug("\tdid found the user with name " + named + " but is not we are looking for."); 435 } else { 436 String msg = "dont know the type:" + type; 437 log.error(msg); 438 throw new SecurityException (msg); 439 } 440 } 441 } 442 return userInGroups(user, fetchedGroups, done); 443 } 444 445 public void verify(UserContext user, int nodeNumber, Operation operation) throws SecurityException { 446 if (log.isDebugEnabled()) { 447 if (operation.getInt() > Operation.READ_INT ) { 448 log.debug("assert on node #" + nodeNumber + " by user: " + user + " for operation " + operation); 449 } else { 450 log.trace("assert on node #" + nodeNumber +" by user: " + user + " for operation " + operation); 451 } 452 } 453 if (!check(user, nodeNumber, operation) ) { 454 throw new SecurityException ("Operation '" + operation + "' on " + nodeNumber + " was NOT permitted to " + user.getIdentifier()); 455 } 456 } 457 458 459 public boolean check(UserContext user, int nodeNumber, int srcNodeNumber, int dstNodeNumber, Operation operation) throws SecurityException { 460 if (operation == Operation.CREATE) { 461 return check(user, srcNodeNumber, "link") && check(user, dstNodeNumber, "link"); 463 } else if (operation == Operation.CHANGE_RELATION) { 464 return check(user, nodeNumber, Operation.WRITE.toString()) && 465 check(user, srcNodeNumber, "link") && check(user, dstNodeNumber, "link"); 466 } else { 467 throw new RuntimeException ("Called check with wrong operation " + operation); 468 } 469 } 470 471 public void verify(UserContext user, int nodeNumber, int srcNodeNumber, int dstNodeNumber, Operation operation) throws SecurityException { 472 if (operation == Operation.CREATE) { 473 if(!check(user, srcNodeNumber, "link")) { 475 String msg = "Operation 'link' on " + srcNodeNumber + " was NOT permitted to " + user.getIdentifier(); 476 log.error(msg); 477 throw new SecurityException (msg); 478 } 479 if (! check(user, dstNodeNumber, "link")) { 480 String msg = "Operation 'link' on " + dstNodeNumber + " was NOT permitted to " + user.getIdentifier(); 481 log.error(msg); 482 throw new SecurityException (msg); 483 } 484 } else if (operation == Operation.CHANGE_RELATION) { 485 if(!check(user, srcNodeNumber, "link")) { 486 String msg = "Operation 'link' on " + srcNodeNumber + " was NOT permitted to " + user.getIdentifier(); 487 log.error(msg); 488 throw new SecurityException (msg); 489 } 490 if (! check(user, dstNodeNumber, "link")) { 491 String msg = "Operation 'link' on " + dstNodeNumber + " was NOT permitted to " + user.getIdentifier(); 492 log.error(msg); 493 throw new SecurityException (msg); 494 } 495 verify(user, nodeNumber, Operation.WRITE); 496 } else { 497 throw new RuntimeException ("Called check with wrong operation " + operation); 498 } 499 } 500 501 private void getGlobalAllowedOperations() { 502 String xpath = "/contextconfig/global/allowed"; 504 log.debug("going to execute the query:" + xpath ); 505 NodeIterator found; 506 try { 507 found = XPathAPI.selectNodeIterator(document, xpath); 508 } catch(javax.xml.transform.TransformerException te) { 509 log.error("error executing query: '"+xpath+"' "); 510 log.error( Logging.stackTrace(te)); 511 throw new java.lang.SecurityException ("error executing query: '"+xpath+"' "); 512 } 513 Node allowed; 514 for(allowed = found.nextNode(); allowed != null; allowed = found.nextNode()) { 515 NamedNodeMap nnm = allowed.getAttributes(); 516 Node contextNameNode = nnm.getNamedItem("operation"); 517 Operation operation = Operation.getOperation(contextNameNode.getNodeValue()); 518 log.info("Everyone may do operation:" + operation); 519 if(globalAllowedOperations.contains(operation)) throw new java.lang.SecurityException ("operation:" + operation + " already in allowed list"); 520 globalAllowedOperations.add(operation); 521 } 522 } 523 524 private static org.mmbase.module.core.MMObjectBuilder builder = null; 525 526 private MMObjectNode getMMNode(int n) { 527 if(builder == null) { 528 org.mmbase.module.core.MMBase mmb = (org.mmbase.module.core.MMBase)org.mmbase.module.Module.getModule("mmbaseroot"); 529 builder = mmb.getMMObject("typedef"); 530 if(builder == null) { 531 String msg = "builder 'typedef' not found"; 532 log.error(msg); 533 throw new SecurityException (msg); 535 } 536 } 537 MMObjectNode node = builder.getNode(n); 538 if(node == null) { 539 String msg = "node " + n + " not found"; 540 log.error(msg); 541 throw new SecurityException (msg); 543 } 544 return node; 545 } 546 547 548 protected SortedSet getAllContexts() { 549 return allContexts; 550 } 551 552 protected SortedSet getDisallowingContexts(UserContext user, Operation operation) { 553 if (operation != Operation.READ) throw new UnsupportedOperationException ("Currently only implemented for READ"); 554 SortedSet set = new TreeSet(); 555 Iterator i = getAllContexts().iterator(); 556 while (i.hasNext()) { 557 String context = (String ) i.next(); 558 if (! check(user, context, operation)) { 559 set.add(context); 560 } 561 } 562 return set; 563 } 564 565 566 public QueryCheck check(UserContext userContext, Query query, Operation operation) { 567 if(globalAllowedOperations.contains(operation)) { 568 return COMPLETE_CHECK; 569 } else { 570 if (operation == Operation.READ) { 571 572 AllowingContexts ac = (AllowingContexts) allowingContextsCache.get(userContext.getIdentifier()); 573 if (ac == null) { 574 SortedSet disallowing = getDisallowingContexts(userContext, operation); 576 SortedSet contexts; 577 boolean inverse; 578 if (log.isDebugEnabled()) { 579 log.debug("disallowing: " + disallowing + " all " + getAllContexts()); 580 } 581 582 if (disallowing.size() < (getAllContexts().size() / 2)) { 584 contexts = disallowing; 585 inverse = true; 586 } else { 587 contexts = new TreeSet(getAllContexts()); 588 contexts.removeAll(disallowing); 589 inverse = false; 590 } 591 ac = new AllowingContexts(contexts, inverse); 592 allowingContextsCache.put(userContext.getIdentifier(), ac); 593 } 594 595 if (ac.contexts.size() == 0) { 596 if (ac.inverse) { 597 return COMPLETE_CHECK; 598 } else { 599 Constraint mayNothing = query.createConstraint(query.createStepField((Step) query.getSteps().get(0), "number"), new Integer (-1)); 601 return new Authorization.QueryCheck(true, mayNothing); 602 } 603 } 604 605 List steps = query.getSteps(); 606 if (steps.size() * ac.contexts.size() < maxContextsInQuery) { 607 Iterator i = steps.iterator(); 608 Constraint constraint = null; 609 while (i.hasNext()) { 610 Step step = (Step) i.next(); 611 StepField field = query.createStepField(step, "owner"); 612 Constraint newConstraint = query.createConstraint(field, ac.contexts); 613 if (ac.inverse) query.setInverse(newConstraint, true); 614 if (constraint == null) { 615 constraint = newConstraint; 616 } else { 617 constraint = query.createConstraint(constraint, CompositeConstraint.LOGICAL_AND, newConstraint); 618 } 619 } 620 return new Authorization.QueryCheck(true, constraint); 621 } else { return Authorization.NO_CHECK; 623 } 624 625 } else { 626 return Authorization.NO_CHECK; 628 } 629 } 630 } 631 632 private static class AllowingContexts { 633 SortedSet contexts; 634 boolean inverse; 635 AllowingContexts(SortedSet c, boolean i) { 636 contexts = c; 637 inverse = i; 638 } 639 640 } 641 } 642 | Popular Tags |