1 17 package org.alfresco.repo.security.authentication.ldap; 18 19 import java.io.BufferedWriter ; 20 import java.io.File ; 21 import java.io.FileWriter ; 22 import java.io.IOException ; 23 import java.io.Writer ; 24 import java.util.Collection ; 25 import java.util.HashMap ; 26 import java.util.HashSet ; 27 28 import javax.naming.NamingEnumeration ; 29 import javax.naming.NamingException ; 30 import javax.naming.directory.Attribute ; 31 import javax.naming.directory.Attributes ; 32 import javax.naming.directory.InitialDirContext ; 33 import javax.naming.directory.SearchControls ; 34 import javax.naming.directory.SearchResult ; 35 36 import org.alfresco.model.ContentModel; 37 import org.alfresco.repo.importer.ExportSource; 38 import org.alfresco.repo.importer.ExportSourceImporterException; 39 import org.alfresco.service.namespace.NamespaceService; 40 import org.alfresco.service.namespace.QName; 41 import org.alfresco.util.ApplicationContextHelper; 42 import org.alfresco.util.EqualsHelper; 43 import org.alfresco.util.GUID; 44 import org.apache.commons.logging.Log; 45 import org.apache.commons.logging.LogFactory; 46 import org.dom4j.io.OutputFormat; 47 import org.dom4j.io.XMLWriter; 48 import org.springframework.beans.factory.InitializingBean; 49 import org.springframework.context.ApplicationContext; 50 import org.xml.sax.SAXException ; 51 import org.xml.sax.helpers.AttributesImpl ; 52 53 public class LDAPGroupExportSource implements ExportSource, InitializingBean 54 { 55 private static Log s_logger = LogFactory.getLog(LDAPGroupExportSource.class); 56 57 private String groupQuery = "(objectclass=groupOfNames)"; 58 59 private String searchBase; 60 61 private String groupIdAttributeName = "cn"; 62 63 private String userIdAttributeName = "uid"; 64 65 private String groupType = "groupOfNames"; 66 67 private String personType = "inetOrgPerson"; 68 69 private LDAPInitialDirContextFactory ldapInitialContextFactory; 70 71 private NamespaceService namespaceService; 72 73 private String memberAttribute = "member"; 74 75 private boolean errorOnMissingMembers = false; 76 77 private QName viewRef; 78 79 private QName viewId; 80 81 private QName viewAssociations; 82 83 private QName childQName; 84 85 private QName viewValueQName; 86 87 private QName viewIdRef; 88 89 public LDAPGroupExportSource() 90 { 91 super(); 92 } 93 94 public void setGroupIdAttributeName(String groupIdAttributeName) 95 { 96 this.groupIdAttributeName = groupIdAttributeName; 97 } 98 99 public void setGroupQuery(String groupQuery) 100 { 101 this.groupQuery = groupQuery; 102 } 103 104 public void setGroupType(String groupType) 105 { 106 this.groupType = groupType; 107 } 108 109 public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory) 110 { 111 this.ldapInitialContextFactory = ldapInitialDirContextFactory; 112 } 113 114 public void setMemberAttribute(String memberAttribute) 115 { 116 this.memberAttribute = memberAttribute; 117 } 118 119 public void setNamespaceService(NamespaceService namespaceService) 120 { 121 this.namespaceService = namespaceService; 122 } 123 124 public void setPersonType(String personType) 125 { 126 this.personType = personType; 127 } 128 129 public void setSearchBase(String searchBase) 130 { 131 this.searchBase = searchBase; 132 } 133 134 public void setUserIdAttributeName(String userIdAttributeName) 135 { 136 this.userIdAttributeName = userIdAttributeName; 137 } 138 139 public void setErrorOnMissingMembers(boolean errorOnMissingMembers) 140 { 141 this.errorOnMissingMembers = errorOnMissingMembers; 142 } 143 144 public void generateExport(XMLWriter writer) 145 { 146 HashSet <Group> rootGroups = new HashSet <Group>(); 147 HashMap <String , Group> lookup = new HashMap <String , Group>(); 148 HashSet <SecondaryLink> secondaryLinks = new HashSet <SecondaryLink>(); 149 150 buildGroupsAndRoots(rootGroups, lookup, secondaryLinks); 151 152 buildXML(rootGroups, lookup, secondaryLinks, writer); 153 154 } 155 156 private void buildXML(HashSet <Group> rootGroups, HashMap <String , Group> lookup, 157 HashSet <SecondaryLink> secondaryLinks, XMLWriter writer) 158 { 159 160 Collection <String > prefixes = namespaceService.getPrefixes(); 161 QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); 162 163 try 164 { 165 AttributesImpl attrs = new AttributesImpl (); 166 attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName 167 .toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); 168 169 writer.startDocument(); 170 171 for (String prefix : prefixes) 172 { 173 if (!prefix.equals("xml")) 174 { 175 String uri = namespaceService.getNamespaceURI(prefix); 176 writer.startPrefixMapping(prefix, uri); 177 } 178 } 179 180 writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", 181 NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl ()); 182 183 185 for (Group group : rootGroups) 186 { 187 addRootGroup(lookup, group, writer); 188 } 189 190 192 for (SecondaryLink sl : secondaryLinks) 193 { 194 addSecondarylink(lookup, sl, writer); 195 } 196 197 for (String prefix : prefixes) 198 { 199 if (!prefix.equals("xml")) 200 { 201 writer.endPrefixMapping(prefix); 202 } 203 } 204 205 writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX 206 + ":" + "view"); 207 208 writer.endDocument(); 209 } 210 catch (SAXException e) 211 { 212 throw new ExportSourceImporterException("Failed to create file for import.", e); 213 } 214 215 } 216 217 private void addSecondarylink(HashMap <String , Group> lookup, SecondaryLink sl, XMLWriter writer) 218 throws SAXException 219 { 220 221 String fromId = lookup.get(sl.from).guid; 222 String toId = lookup.get(sl.to).guid; 223 224 AttributesImpl attrs = new AttributesImpl (); 225 attrs.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, fromId); 226 227 writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), 228 viewRef.toPrefixString(namespaceService), attrs); 229 230 writer.startElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations 231 .toPrefixString(namespaceService), new AttributesImpl ()); 232 233 writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), 234 ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService), new AttributesImpl ()); 235 236 AttributesImpl attrsRef = new AttributesImpl (); 237 attrsRef.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, toId); 238 attrsRef.addAttribute(childQName.getNamespaceURI(), childQName.getLocalName(), childQName.toPrefixString(), 239 null, QName.createQName(ContentModel.USER_MODEL_URI, sl.to).toPrefixString(namespaceService)); 240 241 writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), 242 viewRef.toPrefixString(namespaceService), attrsRef); 243 244 writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService)); 245 246 writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), 247 ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService)); 248 249 writer.endElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations 250 .toPrefixString(namespaceService)); 251 252 writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService)); 253 254 } 255 256 private void addRootGroup(HashMap <String , Group> lookup, Group group, XMLWriter writer) throws SAXException 257 { 258 259 AttributesImpl attrs = new AttributesImpl (); 260 attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName 261 .toPrefixString(), null, QName.createQName(ContentModel.USER_MODEL_URI, group.gid).toPrefixString( 262 namespaceService)); 263 attrs.addAttribute(viewId.getNamespaceURI(), viewId.getLocalName(), viewId 264 .toPrefixString(), null, group.guid); 265 266 writer.startElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(), 267 ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER 268 .toPrefixString(namespaceService), attrs); 269 270 writer.startElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME 271 .getLocalName(), ContentModel.PROP_AUTHORITY_NAME.toPrefixString(namespaceService), 272 new AttributesImpl ()); 273 274 writer.characters(group.gid.toCharArray(), 0, group.gid.length()); 275 276 writer.endElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME 277 .getLocalName(), ContentModel.PROP_AUTHORITY_NAME.toPrefixString(namespaceService)); 278 279 if (group.members.size() > 0) 280 { 281 writer.startElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(), 282 ContentModel.PROP_MEMBERS.toPrefixString(namespaceService), new AttributesImpl ()); 283 284 for (String member : group.members) 285 { 286 writer.startElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName 287 .toPrefixString(namespaceService), new AttributesImpl ()); 288 289 writer.characters(member.toCharArray(), 0, member.length()); 290 291 writer.endElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName 292 .toPrefixString(namespaceService)); 293 } 294 295 writer.endElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(), 296 ContentModel.PROP_MEMBERS.toPrefixString(namespaceService)); 297 } 298 299 for (Group child : group.children) 300 { 301 addgroup(lookup, child, writer); 302 } 303 304 writer.endElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(), 305 ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER 306 .toPrefixString(namespaceService)); 307 308 } 309 310 private void addgroup(HashMap <String , Group> lookup, Group group, XMLWriter writer) throws SAXException 311 { 312 AttributesImpl attrs = new AttributesImpl (); 313 314 writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), 315 ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService), attrs); 316 317 addRootGroup(lookup, group, writer); 318 319 writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), 320 ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService)); 321 } 322 323 private void buildGroupsAndRoots(HashSet <Group> rootGroups, HashMap <String , Group> lookup, 324 HashSet <SecondaryLink> secondaryLinks) 325 { 326 InitialDirContext ctx = null; 327 try 328 { 329 ctx = ldapInitialContextFactory.getDefaultIntialDirContext(); 330 331 SearchControls userSearchCtls = new SearchControls (); 332 userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); 333 334 NamingEnumeration searchResults = ctx.search(searchBase, groupQuery, userSearchCtls); 335 while (searchResults.hasMoreElements()) 336 { 337 SearchResult result = (SearchResult ) searchResults.next(); 338 Attributes attributes = result.getAttributes(); 339 Attribute gidAttribute = attributes.get(groupIdAttributeName); 340 if(gidAttribute == null) 341 { 342 throw new ExportSourceImporterException("Group returned by group search does not have mandatory group id attribute "+attributes); 343 } 344 String gid = (String ) gidAttribute.get(0); 345 346 Group group = lookup.get(gid); 347 if (group == null) 348 { 349 group = new Group(gid); 350 lookup.put(group.gid, group); 351 rootGroups.add(group); 352 } 353 Attribute memAttribute = attributes.get(memberAttribute); 354 if (memAttribute != null) 356 { 357 for (int i = 0; i < memAttribute.size(); i++) 358 { 359 String attribute = (String ) memAttribute.get(i); 360 if (attribute != null) 361 { 362 group.distinguishedNames.add(attribute); 363 } 364 } 365 } 366 } 367 368 if (s_logger.isDebugEnabled()) 369 { 370 s_logger.debug("Found " + lookup.size()); 371 } 372 373 for (Group group : lookup.values()) 374 { 375 if (s_logger.isDebugEnabled()) 376 { 377 s_logger.debug("Linking " + group.gid); 378 } 379 for (String dn : group.distinguishedNames) 380 { 381 if (s_logger.isDebugEnabled()) 382 { 383 s_logger.debug("... " + dn); 384 } 385 String id; 386 Boolean isGroup = null; 387 388 SearchControls memberSearchCtls = new SearchControls (); 389 memberSearchCtls.setSearchScope(SearchControls.OBJECT_SCOPE); 390 NamingEnumeration memberSearchResults; 391 try 392 { 393 memberSearchResults = ctx.search(dn, "(objectClass=*)", memberSearchCtls); 394 } 395 catch (NamingException e) 396 { 397 if (errorOnMissingMembers) 398 { 399 throw e; 400 } 401 s_logger.warn("Failed to resolve distinguished name: " + dn); 402 continue; 403 } 404 while (memberSearchResults.hasMoreElements()) 405 { 406 id = null; 407 408 SearchResult result; 409 try 410 { 411 result = (SearchResult ) memberSearchResults.next(); 412 } 413 catch (NamingException e) 414 { 415 if (errorOnMissingMembers) 416 { 417 throw e; 418 } 419 s_logger.warn("Failed to resolve distinguished name: " + dn); 420 continue; 421 } 422 Attributes attributes = result.getAttributes(); 423 Attribute objectclass = attributes.get("objectclass"); 424 if(objectclass == null) 425 { 426 throw new ExportSourceImporterException("Failed to find attribute objectclass for DN "+dn); 427 } 428 for (int i = 0; i < objectclass.size(); i++) 429 { 430 String testType; 431 try 432 { 433 testType = (String ) objectclass.get(i); 434 } 435 catch (NamingException e) 436 { 437 if (errorOnMissingMembers) 438 { 439 throw e; 440 } 441 s_logger.warn("Failed to resolve object class attribute for distinguished name: " + dn); 442 continue; 443 } 444 if (testType.equals(groupType)) 445 { 446 isGroup = true; 447 try 448 { 449 Attribute groupIdAttribute = attributes.get(groupIdAttributeName); 450 if(groupIdAttribute == null) 451 { 452 throw new ExportSourceImporterException("Group missing group id attribute DN ="+dn + " att = "+groupIdAttributeName); 453 } 454 id = (String ) groupIdAttribute.get(0); 455 } 456 catch (NamingException e) 457 { 458 if (errorOnMissingMembers) 459 { 460 throw e; 461 } 462 s_logger.warn("Failed to resolve group identifier " 463 + groupIdAttributeName + " for distinguished name: " + dn); 464 id = "Unknown sub group"; 465 } 466 break; 467 } 468 else if (testType.equals(personType)) 469 { 470 isGroup = false; 471 try 472 { 473 Attribute userIdAttribute = attributes.get(userIdAttributeName); 474 if(userIdAttribute == null) 475 { 476 throw new ExportSourceImporterException("User missing user id attribute DN ="+dn + " att = "+userIdAttributeName); 477 } 478 id = (String ) userIdAttribute.get(0); 479 } 480 catch (NamingException e) 481 { 482 if (errorOnMissingMembers) 483 { 484 throw e; 485 } 486 s_logger.warn("Failed to resolve group identifier " 487 + userIdAttributeName + " for distinguished name: " + dn); 488 id = "Unknown member"; 489 } 490 break; 491 } 492 } 493 494 if (id != null) 495 { 496 if (isGroup == null) 497 { 498 throw new ExportSourceImporterException("Type not recognised for DN"+dn); 499 } 500 else if (isGroup) 501 { 502 if (s_logger.isDebugEnabled()) 503 { 504 s_logger.debug("... is sub group"); 505 } 506 Group child = lookup.get("GROUP_" + id); 507 if (child == null) 508 { 509 throw new ExportSourceImporterException("Failed to find child group " + id); 510 } 511 if (rootGroups.contains(child)) 512 { 513 if (s_logger.isDebugEnabled()) 514 { 515 s_logger.debug("... Primary created from " 516 + group.gid + " to " + child.gid); 517 } 518 group.children.add(child); 519 rootGroups.remove(child); 520 } 521 else 522 { 523 if (s_logger.isDebugEnabled()) 524 { 525 s_logger.debug("... Secondary created from " 526 + group.gid + " to " + child.gid); 527 } 528 secondaryLinks.add(new SecondaryLink(group.gid, child.gid)); 529 } 530 } 531 else 532 { 533 if (s_logger.isDebugEnabled()) 534 { 535 s_logger.debug("... is member"); 536 } 537 group.members.add(id); 538 } 539 } 540 } 541 } 542 } 543 if (s_logger.isDebugEnabled()) 544 { 545 s_logger.debug("Top " + rootGroups.size()); 546 s_logger.debug("Secondary " + secondaryLinks.size()); 547 } 548 } 549 catch (NamingException e) 550 { 551 throw new ExportSourceImporterException("Failed to import people.", e); 552 } 553 finally 554 { 555 if (ctx != null) 556 { 557 try 558 { 559 ctx.close(); 560 } 561 catch (NamingException e) 562 { 563 throw new ExportSourceImporterException("Failed to import people.", e); 564 } 565 } 566 } 567 } 568 569 private static class Group 570 { 571 String gid; 572 573 String guid = GUID.generate(); 574 575 HashSet <Group> children = new HashSet <Group>(); 576 577 HashSet <String > members = new HashSet <String >(); 578 579 HashSet <String > distinguishedNames = new HashSet <String >(); 580 581 private Group(String gid) 582 { 583 this.gid = "GROUP_" + gid; 584 } 585 586 @Override 587 public boolean equals(Object o) 588 { 589 if (this == o) 590 { 591 return true; 592 } 593 if (!(o instanceof Group)) 594 { 595 return false; 596 } 597 Group g = (Group) o; 598 return this.gid.equals(g.gid); 599 } 600 601 @Override 602 public int hashCode() 603 { 604 return gid.hashCode(); 605 } 606 } 607 608 private static class SecondaryLink 609 { 610 String from; 611 612 String to; 613 614 private SecondaryLink(String from, String to) 615 { 616 this.from = from; 617 this.to = to; 618 } 619 620 @Override 621 public boolean equals(Object o) 622 { 623 if (this == o) 624 { 625 return true; 626 } 627 if (!(o instanceof Group)) 628 { 629 return false; 630 } 631 SecondaryLink l = (SecondaryLink) o; 632 return EqualsHelper.nullSafeEquals(this.from, l.from) && EqualsHelper.nullSafeEquals(this.to, l.to); 633 } 634 635 @Override 636 public int hashCode() 637 { 638 int hashCode = 0; 639 if (from != null) 640 { 641 hashCode = hashCode * 37 + from.hashCode(); 642 } 643 if (to != null) 644 { 645 hashCode = hashCode * 37 + to.hashCode(); 646 } 647 return hashCode; 648 } 649 } 650 651 public static void main(String [] args) throws IOException 652 { 653 ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); 654 ExportSource source = (ExportSource) ctx.getBean("ldapGroupExportSource"); 655 656 File file = new File (args[0]); 657 Writer writer = new BufferedWriter (new FileWriter (file)); 658 XMLWriter xmlWriter = createXMLExporter(writer); 659 source.generateExport(xmlWriter); 660 xmlWriter.close(); 661 662 } 663 664 private static XMLWriter createXMLExporter(Writer writer) 665 { 666 OutputFormat format = OutputFormat.createPrettyPrint(); 668 format.setNewLineAfterDeclaration(false); 669 format.setIndentSize(3); 670 format.setEncoding("UTF-8"); 671 672 674 XMLWriter xmlWriter = new XMLWriter(writer, format); 675 return xmlWriter; 676 } 677 678 public void afterPropertiesSet() throws Exception 679 { 680 viewRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "reference", namespaceService); 681 viewId = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "id", namespaceService); 682 viewIdRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "idref", namespaceService); 683 viewAssociations = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "associations", namespaceService); 684 childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); 685 viewValueQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "value", namespaceService); 686 687 } 688 } 689 | Popular Tags |