1 package de.webman.sync.ldap ; 2 3 import javax.xml.parsers.DocumentBuilderFactory ; 4 import javax.xml.parsers.DocumentBuilder ; 5 import javax.xml.parsers.ParserConfigurationException ; 6 import org.w3c.dom.Node ; 7 import org.w3c.dom.Document ; 8 import org.w3c.dom.Element ; 9 import org.xml.sax.SAXException ; 10 import org.xml.sax.InputSource ; 11 import java.util.*; 12 import javax.naming.*; 13 import javax.naming.directory.*; 14 import java.io.File ; 15 import java.io.IOException ; 16 import java.io.InputStream ; 17 import java.io.FileInputStream ; 18 import java.io.BufferedInputStream ; 19 import de.webman.sync.User; 20 import de.webman.sync.SyncException; 21 import de.webman.sync.ACLAdaptor; 22 import de.webman.sync.ldap.worker .*; 23 import de.webman.sync.Worker; 24 import de.webman.util.security.EncryptUtil; 25 import org.apache.log4j.Category; 26 27 28 138 public class LDAPAdaptor 139 implements ACLAdaptor 140 { 141 142 143 146 private static Category cat = Category.getInstance(LDAPAdaptor.class); 147 148 149 152 protected String host = "localhost"; 153 154 157 protected int port = 389; 158 159 162 protected String basedn = null; 163 164 167 protected boolean authenticate = false; 168 169 172 protected String principal = null; 173 174 178 protected boolean appendBaseDNToPrincipal = false; 179 180 183 protected String password = null; 184 185 188 protected boolean useSSL = false; 189 190 191 192 193 196 protected String userFilter = "(webmangroup=*)"; 197 198 201 protected String changedUserFilter = "(&(webmangroup=*)(dirtyflag=yes))"; 202 203 206 protected String userSearchDN = ""; 207 208 209 212 protected int userScope = SearchControls.ONELEVEL_SCOPE; 213 214 218 protected int timeout = 30 * 1000; 219 220 223 protected ArrayList workers = new ArrayList(); 224 225 226 229 protected HashSet ignoreUsers = new HashSet(); 230 231 234 public static final int ATTRIBUTE_YES_VALUE = 0; 235 238 public static final int ATTRIBUTE_NO_VALUE = 1; 239 242 public static final int ATTRIBUTE_DIRTY_FLAG = 2; 243 246 public static final int ATTRIBUTE_WEBMAN_GROUP = 3; 247 250 public static final int ATTRIBUTE_DISPLAY_NAME = 4; 251 256 public static final int ATTRIBUTE_LOGIN = 5; 257 260 public static final int ATTRIBUTES_COUNT = 6; 261 262 267 String [] attributes = null; 268 269 270 273 protected DirContext ctx = null; 274 275 276 288 public LDAPAdaptor(String _host, int _port, String _basedn, 289 String _user, String _passwd, boolean _appendbasedn, boolean _useSSL) 290 throws NamingException 291 { 292 if (_basedn == null) 293 throw new IllegalArgumentException ("basedn mustn't be null"); 294 295 init(); 296 297 host = _host != null ? _host : "localhost"; 298 port = _port > 0 ? _port : 389; 299 basedn = _basedn; 300 principal = _user; 301 password = _passwd; 302 appendBaseDNToPrincipal = _appendbasedn; 303 useSSL = _useSSL; 304 305 authenticate = (_user != null && _passwd != null); 306 307 ctx = newContext(); 308 309 cat.debug ("successfully connected to ldap server"); 310 } 311 312 321 public LDAPAdaptor(File configfile) 322 throws IOException , ConfigException, NamingException 323 { 324 init(); 325 readXMLStream(new FileInputStream (configfile)); 326 ctx = newContext(); 327 } 328 329 333 public LDAPAdaptor(InputStream in) 334 throws IOException , ConfigException, NamingException 335 { 336 init(); 337 readXMLStream(in); 338 ctx = newContext(); 339 } 340 341 342 345 private void init() { 346 attributes = new String [ATTRIBUTES_COUNT]; 347 attributes[ATTRIBUTE_YES_VALUE] = "yes"; 348 attributes[ATTRIBUTE_NO_VALUE] = "no"; 349 attributes[ATTRIBUTE_DIRTY_FLAG] = "dirtyflag"; 350 attributes[ATTRIBUTE_DISPLAY_NAME] = "sn"; 351 attributes[ATTRIBUTE_LOGIN] = null; 352 attributes[ATTRIBUTE_WEBMAN_GROUP] = "webmangroup"; 353 } 354 355 356 360 public DirContext getContext() 361 { 362 return ctx; 363 } 364 365 370 protected DirContext newContext() 371 throws NamingException 372 { 373 cat.info("Host = '" + host + "'"); 374 cat.info("Port = '" + port + "'"); 375 cat.info("Use ssl? = '" + useSSL + "'"); 376 cat.info("Base-dn = '" + basedn + "'"); 377 cat.info("Authenticate? = '" + authenticate + "'"); 378 cat.info("User-dn = '" + principal + "'"); 379 cat.info("Password = '" + "*********'"); 380 cat.info("Append base-dn? = '" + appendBaseDNToPrincipal + "'"); 381 cat.info("Dirty-flag attr = '" + attributes[ATTRIBUTE_DIRTY_FLAG] + "'"); 382 cat.info(" with yes = '" + attributes[ATTRIBUTE_YES_VALUE] + "'"); 383 cat.info(" with no = '" + attributes[ATTRIBUTE_NO_VALUE] + "'"); 384 cat.info("Webman group attr = '" + attributes[ATTRIBUTE_WEBMAN_GROUP] + "'"); 385 cat.info("Display name attr = '" + attributes[ATTRIBUTE_DISPLAY_NAME] + "'"); 386 cat.info("Login attr = '" + attributes[ATTRIBUTE_LOGIN] + "'"); 387 cat.info("filter = '" + userFilter + "'"); 388 cat.info("changed filter = '" + changedUserFilter + "'"); 389 cat.info("user search dn = '" + userSearchDN + "'"); 390 cat.info("search scope = '" + userScope + "'"); 391 cat.info("search timeout = '" + timeout + "'"); 392 393 Hashtable env = new Hashtable(); 394 395 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 396 397 398 env.put(Context.PROVIDER_URL, "ldap://" + host + ":" + port + "/" + basedn); 399 400 401 if (useSSL) 402 env.put(Context.SECURITY_PROTOCOL, "ssl"); 403 404 405 if (authenticate) { 406 env.put(Context.SECURITY_AUTHENTICATION, "simple"); 407 408 if (appendBaseDNToPrincipal) 409 env.put(Context.SECURITY_PRINCIPAL, principal + "," + basedn); 410 else 411 env.put(Context.SECURITY_PRINCIPAL, principal); 412 413 if (password != null) 414 env.put(Context.SECURITY_CREDENTIALS, password); 415 } 416 417 return new InitialDirContext(env); 418 } 419 420 421 422 423 426 432 public List getWorkers() { 433 return workers; 434 437 } 438 439 445 public List getAllUsers() 446 throws SyncException 447 { 448 try { 449 return getUsers(userScope, userSearchDN, userFilter); 450 } 451 catch (NamingException ne) { 452 throw new SyncException(ne); 453 } 454 } 455 456 464 public List getChangedUsers() 465 throws SyncException 466 { 467 try { 468 return getUsers(userScope, userSearchDN, changedUserFilter); 469 } 470 catch (NamingException ne) { 471 throw new SyncException(ne); 472 } 473 } 474 475 484 public List getUsers(int scope, String searchdn, String filter) 485 throws NamingException 486 { 487 SearchControls ctls = new SearchControls(); 488 ctls.setSearchScope(scope); 489 ctls.setTimeLimit(timeout); 490 491 NamingEnumeration answer = ctx.search(searchdn, filter, ctls); 493 494 ArrayList list = new ArrayList(); 495 while (answer.hasMore()) { 496 SearchResult sr = (SearchResult)answer.next(); 497 498 User user = new LDAPUser(sr, attributes); 499 500 list.add(user); 501 } 502 503 return list; 504 } 505 506 507 513 public void setDirtyFlagForUser(User user, boolean value) 514 throws SyncException 515 { 516 try { 517 ModificationItem[] mods = new ModificationItem[1]; 518 mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, 519 new BasicAttribute(attributes[ATTRIBUTE_DIRTY_FLAG], 520 attributes[ATTRIBUTE_NO_VALUE])); 521 String name = user.getID() + "," + userSearchDN; 522 ctx.modifyAttributes(name, mods); 523 cat.info("reset successfully dirtyflag of '" + name + "'"); 524 } 525 catch (NamingException ne) { 526 cat.error("failed to reset dirty flag for '" + user.getID() + "' (" + ne + ")"); 527 } 528 } 529 530 531 532 537 public Set ignoreUsers() { 538 return ignoreUsers; 539 } 540 541 542 545 548 private void readXMLStream(InputStream _in) 549 throws IOException , ConfigException 550 { 551 BufferedInputStream in = null; 552 if (!(_in instanceof BufferedInputStream )) 553 in = new BufferedInputStream (_in); 554 else 555 in = (BufferedInputStream )_in; 556 557 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 558 factory.setNamespaceAware(false); 559 factory.setValidating(false); 560 561 Document doc = null; 562 try { 563 DocumentBuilder builder = factory.newDocumentBuilder(); 564 doc = builder.parse(new InputSource (in)); 565 } 566 catch (ParserConfigurationException pce) { 567 throw new ConfigException(pce); 568 } 569 catch (SAXException se) { 570 throw new ConfigException(se); 571 } 572 573 Node c0 = doc.getDocumentElement(); 574 if (!isNode("acl-adaptor", c0)) 575 throw new ConfigException("expected 'acl-adaptor' element"); 576 577 for (Node c1 = c0.getFirstChild(); c1 != null; c1 = c1.getNextSibling()) { 578 if (isElementNode(c1)) { 579 if (isNode("ldap-properties", c1)) { 580 for (Node c2 = c1.getFirstChild(); c2 != null; c2 = c2.getNextSibling()) { 581 if (isElementNode(c2)) { 582 if (isNode("host", c2)) { 583 host = getTextData(c2); 584 } 585 else if (isNode("port", c2)) { 586 String ps = getTextData(c2); 587 588 try { 589 port = Integer.parseInt(ps); 590 } 591 catch (NumberFormatException nfe) { 592 throw new ConfigException("bad port number for ldap: '" + ps + "'"); 593 } 594 } 595 else if (isNode("base-dn", c2)) 596 basedn = getTextData(c2); 597 else if (isNode("auth", c2)) { 598 authenticate = true; 599 600 String use_ssl = getNodeAttr(c2, "use-ssl"); 601 useSSL = "true".equals(use_ssl); 602 603 for (Node c3 = c2.getFirstChild(); c3 != null; c3 = c3.getNextSibling()) { 604 if (isElementNode(c3)) { 605 if (isNode("user-dn", c3)) { 606 principal = getTextData(c3); 607 608 String appbasedn = getNodeAttr(c3, "append-base-dn"); 609 appendBaseDNToPrincipal = "true".equals(appbasedn); 610 } 611 else if (isNode("password", c3)) { 612 String encAttr = ((Element )c3).getAttribute("encrypted"); 613 boolean enc = "true".equals(encAttr); 614 615 password = getTextData(c3); 616 617 if (enc) 618 password = EncryptUtil.decrypt(password); 619 } 620 else 621 throw new ConfigException("unknown element (1): '" + 622 c3.getNodeName() + "'"); 623 } 624 } 625 } 626 else if (isNode("user-search", c2)) { 627 String scopeattr = getNodeAttr(c2, "scope"); 628 if ("onelevel".equals(scopeattr)) 629 userScope = SearchControls.ONELEVEL_SCOPE; 630 else if ("onelevel".equals(scopeattr)) 631 userScope = SearchControls.SUBTREE_SCOPE; 632 else 633 throw new ConfigException("bad search user scope keyword: '" + 634 scopeattr + "'"); 635 636 for (Node c3 = c2.getFirstChild(); c3 != null; c3 = c3.getNextSibling()) { 637 if (isElementNode(c3)) { 638 if (isNode("filter", c3)) 639 userFilter = getTextData(c3); 640 else if (isNode("search-dn", c3)) 641 userSearchDN = getTextData(c3); 642 else if (isNode("changed-filter", c3)) 643 changedUserFilter = getTextData(c3); 644 else if (isNode("timeout", c3)) { 645 String ps = getTextData(c3); 646 if (ps != null) 647 ps = ps.trim(); 648 649 try { 650 timeout = Integer.parseInt(ps); 651 } 652 catch (NumberFormatException nfe) { 653 throw new ConfigException("bad timeout setting: " + nfe); 654 } 655 } 656 else 657 throw new ConfigException("unknown element (2): '" + 658 c3.getNodeName() + "'"); 659 } 660 } 661 } 662 else if (isNode("attributes", c2)) { 663 for (Node c3 = c2.getFirstChild(); c3 != null; c3 = c3.getNextSibling()) { 664 if (isElementNode(c3)) { 665 if (isNode("dirty-flag-attr", c3)) { 666 String t = getTextData(c3); 667 String y = ((Element )c3).getAttribute("true").trim(); 668 String n = ((Element )c3).getAttribute("false").trim(); 669 670 if (y.length() > 0) 671 attributes[ATTRIBUTE_YES_VALUE] = y; 672 if (n.length() > 0) 673 attributes[ATTRIBUTE_NO_VALUE] = n; 674 if (t != null) 675 attributes[ATTRIBUTE_DIRTY_FLAG] = t.trim(); 676 } 677 else if (isNode("group-attr", c3)) { 678 String t = getTextData(c3); 679 if (t != null) 680 attributes[ATTRIBUTE_WEBMAN_GROUP] = t.trim(); 681 } 682 else if (isNode("login-attr", c3)) { 683 String t = getTextData(c3); 684 if (t != null) 685 attributes[ATTRIBUTE_LOGIN] = t.trim(); 686 } 687 else if (isNode("display-name-attr", c3)) { 688 String t = getTextData(c3); 689 if (t != null) 690 attributes[ATTRIBUTE_DISPLAY_NAME] = t.trim(); 691 } 692 else 693 throw new ConfigException("unknown element (4): '" + 694 c3.getNodeName() + "'"); 695 } 696 } 697 } 698 else 699 throw new ConfigException("unknown element (3): '" + c2.getNodeName() + "'"); 700 } 701 } 702 } 703 else if (isNode("process-order", c1)) { 704 for (Node c2 = c1.getFirstChild(); c2 != null; c2 = c2.getNextSibling()) { 705 if (isElementNode(c2)) { 706 if (isNode("process", c2)) { 707 String workerClass = getNodeAttr(c2, "worker-class"); 708 registerWorkerClass(workerClass); 709 } 710 else 711 throw new ConfigException("unknown element (4): '" + 712 c2.getNodeName() + "'"); 713 } 714 } 715 } 716 else if (isNode("ignore-users", c1)) { 717 ignoreUsers = new HashSet(); 718 for (Node c2 = c1.getFirstChild(); c2 != null; c2 = c2.getNextSibling()) { 719 if (isElementNode(c2)) { 720 if (isNode("login", c2)) { 721 String val = getTextData(c2); 722 if (val != null) 723 val = val.trim(); 724 if (val.length() > 0) 725 ignoreUsers.add(val); 726 } 727 else 728 throw new ConfigException("unknown element (4): '" + 729 c2.getNodeName() + "'"); 730 } 731 } 732 } 733 else 734 throw new ConfigException("unknown element (5): '" + c1.getNodeName() + "'"); 735 } 736 } 737 } 738 739 742 private String getTextData(Node cntx) { 743 cntx.normalize(); 744 745 for (Node n = cntx.getFirstChild(); n != null; n = n.getNextSibling()) { 746 if (n.getNodeType() == Node.TEXT_NODE) { 747 return n.getNodeValue(); 748 } 749 else if (n.getNodeType() == Node.CDATA_SECTION_NODE) { 750 return n.getNodeValue(); 751 } 752 } 753 return null; 754 } 755 756 759 private boolean isNode(String nodename, Node nd) { 760 return nodename.equals(nd.getNodeName()); 761 } 762 763 766 private boolean isElementNode(Node nd) { 767 return (nd.getNodeType() == Node.ELEMENT_NODE); 768 } 769 770 773 private String getNodeAttr(Node nd, String key) { 774 return ((Element )nd).getAttribute(key); 775 } 776 777 778 785 private void registerWorkerClass(String workerClass) 786 { 787 try { 788 Class wc = Class.forName(workerClass); 789 Worker worker = (Worker)wc.newInstance(); 790 791 cat.debug("register LDAP worker: '" + workerClass + "'"); 792 workers.add(worker); 793 } 794 catch (Exception e) { 795 cat.error("Can't load worker class: '" + workerClass + "' (" + e + ")"); 796 } 797 } 798 } 799 | Popular Tags |