[libvt-ldap-java] 01/02: Imported Upstream version 3.3.7
Matthew Vernon
matthew at moszumanska.debian.org
Tue Jun 17 15:58:06 UTC 2014
This is an automated email from the git hooks/post-receive script.
matthew pushed a commit to annotated tag debian/3.3.7-2
in repository libvt-ldap-java.
commit 34f68ccea91eda4093cebe6340a5d34aa1c484b0
Author: Matthew Vernon <mcv21 at cam.ac.uk>
Date: Tue Jun 17 16:45:23 2014 +0100
Imported Upstream version 3.3.7
---
.project | 23 +
LICENSE.apache2.txt | 203 +++
LICENSE.lgpl.txt | 165 ++
README.txt | 10 +
bin/ldapauth | 28 +
bin/ldapauth.bat | 24 +
bin/ldapjaas | 31 +
bin/ldapjaas.bat | 26 +
bin/ldapsearch | 31 +
bin/ldapsearch.bat | 24 +
licenses/commons-cli.license | 202 ++
licenses/commons-codec.license | 202 ++
licenses/commons-logging.license | 202 ++
licenses/dom4j.license | 40 +
pom.xml | 386 ++++
properties/krb5_jaas.config | 11 +
properties/ldap.properties | 122 ++
properties/ldap_jaas.config | 8 +
src/main/assembly/vt-middleware.xml | 74 +
src/main/checkstyle/checkstyle_checks.xml | 199 ++
src/main/checkstyle/checkstyle_header | 13 +
src/main/checkstyle/suppressions.xml | 15 +
.../java/edu/vt/middleware/ldap/AbstractCli.java | 278 +++
.../java/edu/vt/middleware/ldap/AbstractLdap.java | 1166 ++++++++++++
.../edu/vt/middleware/ldap/AttributesFactory.java | 192 ++
src/main/java/edu/vt/middleware/ldap/BaseLdap.java | 52 +
src/main/java/edu/vt/middleware/ldap/Ldap.java | 747 ++++++++
src/main/java/edu/vt/middleware/ldap/LdapCli.java | 179 ++
.../java/edu/vt/middleware/ldap/LdapConfig.java | 1921 ++++++++++++++++++++
.../java/edu/vt/middleware/ldap/LdapConstants.java | 352 ++++
.../java/edu/vt/middleware/ldap/LdapSearch.java | 172 ++
src/main/java/edu/vt/middleware/ldap/LdapUtil.java | 222 +++
.../java/edu/vt/middleware/ldap/SearchFilter.java | 147 ++
.../ldap/auth/AbstractAuthenticator.java | 242 +++
.../edu/vt/middleware/ldap/auth/Authenticator.java | 366 ++++
.../vt/middleware/ldap/auth/AuthenticatorCli.java | 191 ++
.../middleware/ldap/auth/AuthenticatorConfig.java | 555 ++++++
.../ldap/auth/AuthorizationException.java | 49 +
.../middleware/ldap/auth/ConstructDnResolver.java | 114 ++
.../edu/vt/middleware/ldap/auth/DnResolver.java | 58 +
.../vt/middleware/ldap/auth/NoopDnResolver.java | 73 +
.../vt/middleware/ldap/auth/SearchDnResolver.java | 185 ++
.../handler/AbstractAuthenticationHandler.java | 56 +
.../ldap/auth/handler/AuthenticationCriteria.java | 102 ++
.../ldap/auth/handler/AuthenticationHandler.java | 62 +
.../auth/handler/AuthenticationResultHandler.java | 35 +
.../ldap/auth/handler/AuthorizationHandler.java | 46 +
.../auth/handler/BindAuthenticationHandler.java | 62 +
.../auth/handler/CompareAuthenticationHandler.java | 128 ++
.../auth/handler/CompareAuthorizationHandler.java | 124 ++
.../ldap/bean/AbstractLdapAttribute.java | 160 ++
.../ldap/bean/AbstractLdapAttributes.java | 216 +++
.../vt/middleware/ldap/bean/AbstractLdapBean.java | 73 +
.../vt/middleware/ldap/bean/AbstractLdapEntry.java | 123 ++
.../middleware/ldap/bean/AbstractLdapResult.java | 171 ++
.../edu/vt/middleware/ldap/bean/LdapAttribute.java | 84 +
.../vt/middleware/ldap/bean/LdapAttributes.java | 166 ++
.../vt/middleware/ldap/bean/LdapBeanFactory.java | 57 +
.../vt/middleware/ldap/bean/LdapBeanProvider.java | 107 ++
.../edu/vt/middleware/ldap/bean/LdapEntry.java | 79 +
.../edu/vt/middleware/ldap/bean/LdapResult.java | 124 ++
.../ldap/bean/OrderedLdapBeanFactory.java | 135 ++
.../ldap/bean/SortedLdapBeanFactory.java | 137 ++
.../ldap/bean/UnorderedLdapBeanFactory.java | 135 ++
.../edu/vt/middleware/ldap/dsml/AbstractDsml.java | 387 ++++
.../middleware/ldap/dsml/DsmlResultConverter.java | 174 ++
.../edu/vt/middleware/ldap/dsml/DsmlSearch.java | 112 ++
.../java/edu/vt/middleware/ldap/dsml/Dsmlv1.java | 247 +++
.../java/edu/vt/middleware/ldap/dsml/Dsmlv2.java | 148 ++
.../ldap/handler/AbstractConnectionHandler.java | 331 ++++
.../ldap/handler/AbstractResultHandler.java | 150 ++
.../middleware/ldap/handler/AttributeHandler.java | 24 +
.../ldap/handler/AttributesProcessor.java | 89 +
.../ldap/handler/BinaryAttributeHandler.java | 45 +
.../ldap/handler/BinarySearchResultHandler.java | 33 +
.../ldap/handler/CaseChangeAttributeHandler.java | 113 ++
.../handler/CaseChangeSearchResultHandler.java | 150 ++
.../middleware/ldap/handler/ConnectionHandler.java | 143 ++
.../ldap/handler/CopyAttributeHandler.java | 73 +
.../middleware/ldap/handler/CopyResultHandler.java | 46 +
.../ldap/handler/CopySearchResultHandler.java | 111 ++
.../ldap/handler/DefaultConnectionHandler.java | 153 ++
.../ldap/handler/EntryDnSearchResultHandler.java | 101 +
.../ldap/handler/ExtendedAttributeHandler.java | 45 +
.../ldap/handler/ExtendedSearchResultHandler.java | 45 +
.../ldap/handler/FqdnSearchResultHandler.java | 126 ++
.../ldap/handler/MergeSearchResultHandler.java | 137 ++
.../ldap/handler/RecursiveAttributeHandler.java | 187 ++
.../ldap/handler/RecursiveSearchResultHandler.java | 323 ++++
.../vt/middleware/ldap/handler/ResultHandler.java | 77 +
.../vt/middleware/ldap/handler/SearchCriteria.java | 186 ++
.../ldap/handler/SearchResultHandler.java | 43 +
.../ldap/handler/TlsConnectionHandler.java | 266 +++
.../middleware/ldap/jaas/AbstractLoginModule.java | 505 +++++
.../vt/middleware/ldap/jaas/JaasAuthenticator.java | 93 +
.../vt/middleware/ldap/jaas/LdapCredential.java | 93 +
.../ldap/jaas/LdapDnAuthorizationModule.java | 157 ++
.../vt/middleware/ldap/jaas/LdapDnPrincipal.java | 138 ++
.../edu/vt/middleware/ldap/jaas/LdapGroup.java | 120 ++
.../vt/middleware/ldap/jaas/LdapLoginModule.java | 205 +++
.../edu/vt/middleware/ldap/jaas/LdapPrincipal.java | 138 ++
.../java/edu/vt/middleware/ldap/jaas/LdapRole.java | 119 ++
.../ldap/jaas/LdapRoleAuthorizationModule.java | 191 ++
.../java/edu/vt/middleware/ldap/ldif/Ldif.java | 398 ++++
.../middleware/ldap/ldif/LdifResultConverter.java | 116 ++
.../edu/vt/middleware/ldap/ldif/LdifSearch.java | 69 +
.../middleware/ldap/pool/AbstractLdapFactory.java | 175 ++
.../vt/middleware/ldap/pool/AbstractLdapPool.java | 664 +++++++
.../vt/middleware/ldap/pool/BlockingLdapPool.java | 323 ++++
.../ldap/pool/BlockingTimeoutException.java | 65 +
.../middleware/ldap/pool/CloseLdapPassivator.java | 44 +
.../middleware/ldap/pool/CompareLdapValidator.java | 121 ++
.../middleware/ldap/pool/ConnectLdapActivator.java | 51 +
.../middleware/ldap/pool/ConnectLdapValidator.java | 50 +
.../middleware/ldap/pool/DefaultLdapFactory.java | 126 ++
.../ldap/pool/LdapActivationException.java | 65 +
.../edu/vt/middleware/ldap/pool/LdapActivator.java | 39 +
.../edu/vt/middleware/ldap/pool/LdapFactory.java | 75 +
.../vt/middleware/ldap/pool/LdapPassivator.java | 39 +
.../java/edu/vt/middleware/ldap/pool/LdapPool.java | 107 ++
.../vt/middleware/ldap/pool/LdapPoolConfig.java | 379 ++++
.../vt/middleware/ldap/pool/LdapPoolException.java | 65 +
.../ldap/pool/LdapPoolExhaustedException.java | 65 +
.../ldap/pool/LdapValidationException.java | 65 +
.../edu/vt/middleware/ldap/pool/LdapValidator.java | 39 +
.../ldap/pool/PoolInterruptedException.java | 65 +
.../edu/vt/middleware/ldap/pool/PrunePoolTask.java | 73 +
.../vt/middleware/ldap/pool/SharedLdapPool.java | 224 +++
.../vt/middleware/ldap/pool/SoftLimitLdapPool.java | 135 ++
.../vt/middleware/ldap/pool/ValidatePoolTask.java | 71 +
.../ldap/props/AbstractPropertyConfig.java | 143 ++
.../ldap/props/AbstractPropertyInvoker.java | 264 +++
.../edu/vt/middleware/ldap/props/ConfigParser.java | 143 ++
.../ldap/props/LdapConfigPropertyInvoker.java | 239 +++
.../vt/middleware/ldap/props/LdapProperties.java | 188 ++
.../vt/middleware/ldap/props/PropertyConfig.java | 72 +
.../ldap/props/SimplePropertyInvoker.java | 88 +
.../middleware/ldap/servlets/AttributeServlet.java | 240 +++
.../vt/middleware/ldap/servlets/CommonServlet.java | 108 ++
.../vt/middleware/ldap/servlets/LoginServlet.java | 215 +++
.../vt/middleware/ldap/servlets/LogoutServlet.java | 92 +
.../vt/middleware/ldap/servlets/SearchServlet.java | 254 +++
.../middleware/ldap/servlets/ServletConstants.java | 108 ++
.../servlets/session/DefaultSessionManager.java | 97 +
.../ldap/servlets/session/SessionManager.java | 92 +
.../ldap/ssl/AbstractCredentialReader.java | 111 ++
.../ldap/ssl/AbstractSSLContextInitializer.java | 55 +
.../ldap/ssl/AbstractTLSSocketFactory.java | 371 ++++
.../middleware/ldap/ssl/AggregateTrustManager.java | 99 +
.../ldap/ssl/CertificateHostnameVerifier.java | 37 +
.../vt/middleware/ldap/ssl/CredentialConfig.java | 43 +
.../ldap/ssl/CredentialConfigParser.java | 205 +++
.../vt/middleware/ldap/ssl/CredentialReader.java | 62 +
.../ldap/ssl/DefaultHostnameVerifier.java | 355 ++++
.../ldap/ssl/DefaultSSLContextInitializer.java | 74 +
.../ldap/ssl/HostnameVerifyingTrustManager.java | 99 +
.../ldap/ssl/KeyStoreCredentialConfig.java | 213 +++
.../ldap/ssl/KeyStoreCredentialReader.java | 74 +
.../ldap/ssl/KeyStoreSSLContextInitializer.java | 106 ++
.../ldap/ssl/PrivateKeyCredentialReader.java | 61 +
.../middleware/ldap/ssl/SSLContextInitializer.java | 66 +
.../ldap/ssl/SingletonTLSSocketFactory.java | 75 +
.../vt/middleware/ldap/ssl/TLSSocketFactory.java | 116 ++
.../ldap/ssl/ThreadLocalTLSSocketFactory.java | 143 ++
.../ldap/ssl/X509CertificateCredentialReader.java | 41 +
.../ldap/ssl/X509CertificatesCredentialReader.java | 51 +
.../middleware/ldap/ssl/X509CredentialConfig.java | 140 ++
.../ldap/ssl/X509SSLContextInitializer.java | 162 ++
.../resources/edu/vt/middleware/ldap/LdapCli.args | 24 +
.../edu/vt/middleware/ldap/LdapCli.examples | 19 +
.../vt/middleware/ldap/auth/AuthenticatorCli.args | 32 +
.../middleware/ldap/auth/AuthenticatorCli.examples | 18 +
.../vt/middleware/ldap/AnyHostnameVerifier.java | 72 +
.../java/edu/vt/middleware/ldap/LdapCliTest.java | 104 ++
.../edu/vt/middleware/ldap/LdapConfigTest.java | 92 +
.../vt/middleware/ldap/LdapConnStrategyTest.java | 70 +
.../java/edu/vt/middleware/ldap/LdapConnTest.java | 58 +
src/test/java/edu/vt/middleware/ldap/LdapTest.java | 1529 ++++++++++++++++
.../java/edu/vt/middleware/ldap/RetryLdap.java | 100 +
.../java/edu/vt/middleware/ldap/SpringTest.java | 58 +
src/test/java/edu/vt/middleware/ldap/TestUtil.java | 409 +++++
.../java/edu/vt/middleware/ldap/WikiCodeTest.java | 490 +++++
.../middleware/ldap/auth/AuthenticatorCliTest.java | 117 ++
.../ldap/auth/AuthenticatorLoadTest.java | 297 +++
.../vt/middleware/ldap/auth/AuthenticatorTest.java | 847 +++++++++
.../handler/TestAuthenticationResultHandler.java | 49 +
.../auth/handler/TestAuthorizationHandler.java | 69 +
.../vt/middleware/ldap/bean/LdapResultTest.java | 111 ++
.../java/edu/vt/middleware/ldap/dsml/DsmlTest.java | 207 +++
.../middleware/ldap/jaas/LdapLoginModuleTest.java | 771 ++++++++
.../middleware/ldap/jaas/TestCallbackHandler.java | 77 +
.../vt/middleware/ldap/jaas/TestLoginModule.java | 112 ++
.../java/edu/vt/middleware/ldap/ldif/LdifTest.java | 176 ++
.../edu/vt/middleware/ldap/pool/LdapPoolTest.java | 1109 +++++++++++
.../ldap/pool/commons/CommonsLdapPool.java | 46 +
.../commons/DefaultLdapPoolableObjectFactory.java | 64 +
.../ldap/servlets/AttributeServletTest.java | 161 ++
.../ldap/servlets/SearchServletTest.java | 211 +++
.../vt/middleware/ldap/servlets/SessionCheck.java | 60 +
.../ldap/servlets/session/SessionManagerTest.java | 144 ++
.../ldap/ssl/DefaultHostnameVerifierTest.java | 344 ++++
.../ldap/ssl/SunTLSHostnameVerifier.java | 69 +
.../middleware/ldap/ssl/TLSSocketFactoryTest.java | 208 +++
src/test/resources/ed.keystore | Bin 0 -> 5112 bytes
src/test/resources/ed.trust.crt | 42 +
src/test/resources/ed.truststore | Bin 0 -> 1905 bytes
.../edu/vt/middleware/ldap/binaryResults.ldif | 5 +
.../edu/vt/middleware/ldap/createGroupEntry-2.ldif | 6 +
.../edu/vt/middleware/ldap/createGroupEntry-3.ldif | 6 +
.../edu/vt/middleware/ldap/createGroupEntry-4.ldif | 6 +
.../edu/vt/middleware/ldap/createGroupEntry-5.ldif | 6 +
.../edu/vt/middleware/ldap/createGroupEntry-6.ldif | 6 +
.../edu/vt/middleware/ldap/createGroupEntry-7.ldif | 6 +
.../edu/vt/middleware/ldap/createGroupEntry-8.ldif | 6 +
.../edu/vt/middleware/ldap/createGroupEntry-9.ldif | 6 +
.../edu/vt/middleware/ldap/createLdapEntry-10.ldif | 16 +
.../edu/vt/middleware/ldap/createLdapEntry-11.ldif | 15 +
.../edu/vt/middleware/ldap/createLdapEntry-12.ldif | 15 +
.../edu/vt/middleware/ldap/createLdapEntry-2.ldif | 16 +
.../edu/vt/middleware/ldap/createLdapEntry-3.ldif | 16 +
.../edu/vt/middleware/ldap/createLdapEntry-4.ldif | 16 +
.../edu/vt/middleware/ldap/createLdapEntry-5.ldif | 16 +
.../edu/vt/middleware/ldap/createLdapEntry-6.ldif | 16 +
.../edu/vt/middleware/ldap/createLdapEntry-7.ldif | 24 +
.../edu/vt/middleware/ldap/createLdapEntry-8.ldif | 16 +
.../edu/vt/middleware/ldap/createLdapEntry-9.ldif | 16 +
.../edu/vt/middleware/ldap/dfisher.dsmlv1 | 74 +
.../edu/vt/middleware/ldap/dfisher.dsmlv2 | 77 +
.../resources/edu/vt/middleware/ldap/dfisher.ldif | 28 +
.../edu/vt/middleware/ldap/dfisher.sorted.dsmlv1 | 74 +
.../edu/vt/middleware/ldap/dfisher.sorted.dsmlv2 | 77 +
.../edu/vt/middleware/ldap/dfisher.sorted.ldif | 28 +
.../edu/vt/middleware/ldap/getSchemaResults.ldif | 12 +
.../resources/edu/vt/middleware/ldap/image.jpg | Bin 0 -> 371 bytes
.../vt/middleware/ldap/mergeDuplicateResults.ldif | 19 +
.../edu/vt/middleware/ldap/mergeResults.ldif | 13 +
.../edu/vt/middleware/ldap/multipleEntriesIn.ldif | 102 ++
.../edu/vt/middleware/ldap/multipleEntriesOut.ldif | 76 +
.../edu/vt/middleware/ldap/pagedResults.ldif | 32 +
.../ldap/recursiveAttributeHandlerResults.ldif | 9 +
.../ldap/recursiveSearchResultHandlerResults.ldif | 13 +
.../middleware/ldap/searchAttributesResults-2.ldif | 5 +
.../edu/vt/middleware/ldap/searchResults-10.ldif | 5 +
.../edu/vt/middleware/ldap/searchResults-12.ldif | 15 +
.../edu/vt/middleware/ldap/searchResults-2.ldif | 5 +
.../edu/vt/middleware/ldap/searchResults-3.ldif | 5 +
.../edu/vt/middleware/ldap/searchResults-4.ldif | 5 +
.../edu/vt/middleware/ldap/searchResults-5.ldif | 5 +
.../edu/vt/middleware/ldap/searchResults-6.ldif | 5 +
.../edu/vt/middleware/ldap/searchResults-7.ldif | 5 +
.../edu/vt/middleware/ldap/searchResults-8.ldif | 6 +
.../edu/vt/middleware/ldap/searchResults-9.ldif | 5 +
.../edu/vt/middleware/ldap/specialChars-2.ldif | 10 +
.../edu/vt/middleware/ldap/specialChars.ldif | 9 +
src/test/resources/krb5.keytab | Bin 0 -> 102 bytes
src/test/resources/ldap.conn.properties | 30 +
src/test/resources/ldap.cram-md5.properties | 18 +
src/test/resources/ldap.digest-md5.properties | 18 +
src/test/resources/ldap.gssapi.properties | 19 +
src/test/resources/ldap.null.properties | 52 +
src/test/resources/ldap.parser.properties | 50 +
src/test/resources/ldap.pool.properties | 10 +
src/test/resources/ldap.properties | 47 +
src/test/resources/ldap.sasl.properties | 31 +
src/test/resources/ldap.setup.properties | 17 +
src/test/resources/ldap.ssl.properties | 27 +
src/test/resources/ldap.tls.load.properties | 30 +
src/test/resources/ldap.tls.properties | 43 +
src/test/resources/ldap_jaas.config | 279 +++
src/test/resources/log4j.xml | 32 +
src/test/resources/spring-context.xml | 42 +
src/test/resources/spring-pool-context.xml | 60 +
src/test/resources/vt-ldap.truststore | Bin 0 -> 3484 bytes
src/test/resources/web.xml | 115 ++
src/test/testng/testng.xml | 355 ++++
275 files changed, 36911 insertions(+)
diff --git a/.project b/.project
new file mode 100644
index 0000000..a8b8d1d
--- /dev/null
+++ b/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>vt-ldap</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.maven.ide.eclipse.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.maven.ide.eclipse.maven2Nature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/LICENSE.apache2.txt b/LICENSE.apache2.txt
new file mode 100644
index 0000000..6b0b127
--- /dev/null
+++ b/LICENSE.apache2.txt
@@ -0,0 +1,203 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/LICENSE.lgpl.txt b/LICENSE.lgpl.txt
new file mode 100644
index 0000000..cca7fc2
--- /dev/null
+++ b/LICENSE.lgpl.txt
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..534c3ab
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,10 @@
+LDAP ${project.version} README
+
+ This is the ${project.version} release of the VT LDAP Java libraries.
+ It is dual licensed under both the LGPL and Apache 2.
+ If you have questions or comments about this library send e-mail to
+ vt-middleware-users at googlegroups.com.
+
+DOCUMENTATION
+ See the wiki: http://code.google.com/p/vt-middleware/wiki/vtldap
+
diff --git a/bin/ldapauth b/bin/ldapauth
new file mode 100755
index 0000000..16ac216
--- /dev/null
+++ b/bin/ldapauth
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+JAVA=java
+
+#KEYSTORE_OPTS="-Djavax.net.ssl.keyStore= -Djavax.net.ssl.keyStorePassword=changeit -Djavax.net.ssl.keyStoreType=BKS"
+
+#TRUSTSTORE_OPTS="-Djavax.net.ssl.trustStore= -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStoreType=BKS"
+
+# uncomment for debug logging
+#LOGGING_OPTS="-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog -Dorg.apache.commons.logging.simplelog.log.edu.vt.middleware.ldap=debug"
+
+JAVA_OPTS="${JAVA_OPTS} ${KEYSTORE_OPTS} ${TRUSTSTORE_OPTS} ${LOGGING_OPTS}"
+
+if [ "x$VTLDAP_HOME" = "x" ]; then
+ PREFIX=`dirname $0`/..
+else
+ PREFIX="$VTLDAP_HOME"
+fi
+
+CLASSPATH="${PREFIX}/jars/vt-ldap-${project.version}.jar"
+CLASSPATH=${CLASSPATH}:${PREFIX}/properties
+for JAR in `ls ${PREFIX}/lib/*.jar` ; do
+ CLASSPATH=${CLASSPATH}:$JAR
+done
+
+${JAVA} ${JAVA_OPTS} -cp ${CLASSPATH} \
+ edu.vt.middleware.ldap.auth.AuthenticatorCli $@
+
diff --git a/bin/ldapauth.bat b/bin/ldapauth.bat
new file mode 100644
index 0000000..67c141e
--- /dev/null
+++ b/bin/ldapauth.bat
@@ -0,0 +1,24 @@
+ at echo off
+if "%OS%" == "Windows_NT" setlocal
+
+if not defined JAVA_HOME goto no_java_home
+if not defined VTLDAP_HOME goto no_vtldap_home
+
+set JAVA=%JAVA_HOME%\bin\java
+
+set LDAP_JAR=%VTLDAP_HOME%\jars\vt-ldap-${project.version}.jar
+set LIBDIR=%VTLDAP_HOME%\lib
+
+set CLASSPATH=%LIBDIR%\commons-cli-1.2.jar;%LIBDIR%\commons-codec-1.4.jar;%LIBDIR%\commons-logging-1.1.3.jar;%LIBDIR%\dom4j-1.6.1.jar;%LDAP_JAR%
+
+call "%JAVA%" -cp "%CLASSPATH%" edu.vt.middleware.ldap.auth.AuthenticatorCli %*
+goto end
+
+:no_vtldap_home
+echo ERROR: VTLDAP_HOME environment variable must be set to VT Ldap install path.
+goto end
+
+:no_java_home
+echo ERROR: JAVA_HOME environment variable must be set to JRE/JDK install path.
+
+:end
diff --git a/bin/ldapjaas b/bin/ldapjaas
new file mode 100755
index 0000000..62da670
--- /dev/null
+++ b/bin/ldapjaas
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+JAVA=java
+
+# sample options for jaas
+JAAS_OPTS="-Djava.security.auth.login.config=properties/ldap_jaas.config"
+
+#KEYSTORE_OPTS="-Djavax.net.ssl.keyStore= -Djavax.net.ssl.keyStorePassword=changeit -Djavax.net.ssl.keyStoreType=BKS"
+
+#TRUSTSTORE_OPTS="-Djavax.net.ssl.trustStore= -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStoreType=BKS"
+
+# uncomment for debug logging
+#LOGGING_OPTS="-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog -Dorg.apache.commons.logging.simplelog.log.edu.vt.middleware.ldap=debug"
+
+JAVA_OPTS="${JAVA_OPTS} ${JAAS_OPTS} ${KEYSTORE_OPTS} ${TRUSTSTORE_OPTS} ${LOGGING_OPTS}"
+
+if [ "x$VTLDAP_HOME" = "x" ]; then
+ PREFIX=`dirname $0`/..
+else
+ PREFIX="$VTLDAP_HOME"
+fi
+
+CLASSPATH="${PREFIX}/jars/vt-ldap-${project.version}.jar"
+CLASSPATH=${CLASSPATH}:${PREFIX}/properties
+for JAR in `ls ${PREFIX}/lib/*.jar` ; do
+ CLASSPATH=${CLASSPATH}:$JAR
+done
+
+${JAVA} ${JAVA_OPTS} -cp ${CLASSPATH} \
+ edu.vt.middleware.ldap.jaas.LdapLoginModule $@
+
diff --git a/bin/ldapjaas.bat b/bin/ldapjaas.bat
new file mode 100644
index 0000000..0c9d0e1
--- /dev/null
+++ b/bin/ldapjaas.bat
@@ -0,0 +1,26 @@
+ at echo off
+if "%OS%" == "Windows_NT" setlocal
+
+if not defined JAVA_HOME goto no_java_home
+if not defined VTLDAP_HOME goto no_vtldap_home
+
+set JAVA=%JAVA_HOME%\bin\java
+
+set LDAP_JAR=%VTLDAP_HOME%\jars\vt-ldap-${project.version}.jar
+set LIBDIR=%VTLDAP_HOME%\lib
+
+set JAAS_OPTS=-Djava.security.auth.login.config=%VTLDAP_HOME%\properties\ldap_jaas.config
+
+set CLASSPATH=%LIBDIR%\commons-cli-1.2.jar;%LIBDIR%\commons-codec-1.4.jar;%LIBDIR%\commons-logging-1.1.3.jar;%LIBDIR%\dom4j-1.6.1.jar;%LDAP_JAR%
+
+call "%JAVA%" "%JAAS_OPTS%" -cp "%CLASSPATH%" edu.vt.middleware.ldap.jaas.LdapLoginModule %*
+goto end
+
+:no_vtldap_home
+echo ERROR: VTLDAP_HOME environment variable must be set to VT Ldap install path.
+goto end
+
+:no_java_home
+echo ERROR: JAVA_HOME environment variable must be set to JRE/JDK install path.
+
+:end
diff --git a/bin/ldapsearch b/bin/ldapsearch
new file mode 100755
index 0000000..710f127
--- /dev/null
+++ b/bin/ldapsearch
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+JAVA=java
+
+# sample options for kerberos
+#KRB5_OPTS="-Djava.security.auth.login.config=properties/krb5_jaas.config -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.krb5.realm=VT.EDU -Djava.security.krb5.kdc=directory.vt.edu"
+
+#KEYSTORE_OPTS="-Djavax.net.ssl.keyStore= -Djavax.net.ssl.keyStorePassword=changeit -Djavax.net.ssl.keyStoreType=BKS"
+
+#TRUSTSTORE_OPTS="-Djavax.net.ssl.trustStore= -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStoreType=BKS"
+
+# uncomment for debug logging
+#LOGGING_OPTS="-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog -Dorg.apache.commons.logging.simplelog.log.edu.vt.middleware.ldap=debug"
+
+JAVA_OPTS="${JAVA_OPTS} ${KRB5_OPTS} ${KEYSTORE_OPTS} ${TRUSTSTORE_OPTS} ${LOGGING_OPTS}"
+
+if [ "x$VTLDAP_HOME" = "x" ]; then
+ PREFIX=`dirname $0`/..
+else
+ PREFIX="$VTLDAP_HOME"
+fi
+
+CLASSPATH="${PREFIX}/jars/vt-ldap-${project.version}.jar"
+CLASSPATH=${CLASSPATH}:${PREFIX}/properties
+for JAR in `ls ${PREFIX}/lib/*.jar` ; do
+ CLASSPATH=${CLASSPATH}:$JAR
+done
+
+${JAVA} ${JAVA_OPTS} -cp ${CLASSPATH} \
+ edu.vt.middleware.ldap.LdapCli $@
+
diff --git a/bin/ldapsearch.bat b/bin/ldapsearch.bat
new file mode 100755
index 0000000..2e58dac
--- /dev/null
+++ b/bin/ldapsearch.bat
@@ -0,0 +1,24 @@
+ at echo off
+if "%OS%" == "Windows_NT" setlocal
+
+if not defined JAVA_HOME goto no_java_home
+if not defined VTLDAP_HOME goto no_vtldap_home
+
+set JAVA=%JAVA_HOME%\bin\java
+
+set LDAP_JAR=%VTLDAP_HOME%\jars\vt-ldap-${project.version}.jar
+set LIBDIR=%VTLDAP_HOME%\lib
+
+set CLASSPATH=%LIBDIR%\commons-cli-1.2.jar;%LIBDIR%\commons-codec-1.4.jar;%LIBDIR%\commons-logging-1.1.3.jar;%LIBDIR%\dom4j-1.6.1.jar;%LDAP_JAR%
+
+call "%JAVA%" -cp "%CLASSPATH%" edu.vt.middleware.ldap.LdapCli %*
+goto end
+
+:no_vtldap_home
+echo ERROR: VTLDAP_HOME environment variable must be set to VT Ldap install path.
+goto end
+
+:no_java_home
+echo ERROR: JAVA_HOME environment variable must be set to JRE/JDK install path.
+
+:end
diff --git a/licenses/commons-cli.license b/licenses/commons-cli.license
new file mode 100644
index 0000000..75b5248
--- /dev/null
+++ b/licenses/commons-cli.license
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/licenses/commons-codec.license b/licenses/commons-codec.license
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/commons-codec.license
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/licenses/commons-logging.license b/licenses/commons-logging.license
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licenses/commons-logging.license
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/licenses/dom4j.license b/licenses/dom4j.license
new file mode 100644
index 0000000..a90650a
--- /dev/null
+++ b/licenses/dom4j.license
@@ -0,0 +1,40 @@
+Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
+
+Redistribution and use of this software and associated documentation
+("Software"), with or without modification, are permitted provided
+that the following conditions are met:
+
+1. Redistributions of source code must retain copyright
+ statements and notices. Redistributions must also contain a
+ copy of this document.
+
+2. Redistributions in binary form must reproduce the
+ above copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+3. The name "DOM4J" must not be used to endorse or promote
+ products derived from this Software without prior written
+ permission of MetaStuff, Ltd. For written permission,
+ please contact dom4j-info at metastuff.com.
+
+4. Products derived from this Software may not be called "DOM4J"
+ nor may "DOM4J" appear in their names without prior written
+ permission of MetaStuff, Ltd. DOM4J is a registered
+ trademark of MetaStuff, Ltd.
+
+5. Due credit should be given to the DOM4J Project -
+ http://www.dom4j.org
+
+THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..f928eeb
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,386 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>edu.vt.middleware</groupId>
+ <artifactId>vt-ldap</artifactId>
+ <packaging>jar</packaging>
+ <version>3.3.7</version>
+ <name>VT LDAP Libraries</name>
+ <description>Library for performing common LDAP operations</description>
+ <url>http://code.google.com/p/vt-middleware/wiki/vtldap</url>
+ <issueManagement>
+ <system>Google Code</system>
+ <url>http://code.google.com/p/vt-middleware/issues/list</url>
+ </issueManagement>
+ <mailingLists>
+ <mailingList>
+ <name>vt-middleware-users</name>
+ <subscribe>vt-middleware-users+subscribe at googlegroups.com</subscribe>
+ <unsubscribe>vt-middleware-users+unsubscribe at googlegroups.com</unsubscribe>
+ <post>vt-middleware-users at googlegroups.com</post>
+ <archive>http://groups.google.com/group/vt-middleware-users</archive>
+ </mailingList>
+ <mailingList>
+ <name>vt-middleware-dev</name>
+ <subscribe>vt-middleware-dev+subscribe at googlegroups.com</subscribe>
+ <unsubscribe>vt-middleware-dev+unsubscribe at googlegroups.com</unsubscribe>
+ <post>vt-middleware-dev at googlegroups.com</post>
+ <archive>http://groups.google.com/group/vt-middleware-dev</archive>
+ </mailingList>
+ </mailingLists>
+ <licenses>
+ <license>
+ <name>Apache 2</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ </license>
+ <license>
+ <name>GNU Lesser General Public License</name>
+ <url>http://www.gnu.org/licenses/lgpl-3.0.txt</url>
+ </license>
+ </licenses>
+ <scm>
+ <connection>scm:svn:https://vt-middleware.googlecode.com/svn/vt-ldap/trunk</connection>
+ <url>http://vt-middleware.googlecode.com/svn/vt-ldap/trunk</url>
+ </scm>
+ <developers>
+ <developer>
+ <id>dfisher</id>
+ <name>Daniel Fisher</name>
+ <email>dfisher at vt.edu</email>
+ <organization>Virginia Tech</organization>
+ <organizationUrl>http://www.vt.edu</organizationUrl>
+ <roles>
+ <role>developer</role>
+ </roles>
+ </developer>
+ <developer>
+ <id>marvin.addison</id>
+ <name>Marvin Addison</name>
+ <email>serac at vt.edu</email>
+ <organization>Virginia Tech</organization>
+ <organizationUrl>http://www.vt.edu</organizationUrl>
+ <roles>
+ <role>developer</role>
+ </roles>
+ </developer>
+ </developers>
+
+ <properties>
+ <project.build.sourceEncoding>iso-8859-1</project.build.sourceEncoding>
+ <checkstyle.dir>${basedir}/src/main/checkstyle</checkstyle.dir>
+ <testng.dir>${basedir}/src/test/testng</testng.dir>
+ <assembly.dir>${basedir}/src/main/assembly</assembly.dir>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-cli</groupId>
+ <artifactId>commons-cli</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.1.3</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-pool</groupId>
+ <artifactId>commons-pool</artifactId>
+ <version>1.5.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ <version>1.6.1</version>
+ <exclusions>
+ <exclusion>
+ <groupId>xml-apis</groupId>
+ <artifactId>xml-apis</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>jaxen</groupId>
+ <artifactId>jaxen</artifactId>
+ <version>1.1.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>5.14</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.17</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>com.sun.jmx</groupId>
+ <artifactId>jmxri</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.sun.jdmk</groupId>
+ <artifactId>jmxtools</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>javax.jms</groupId>
+ <artifactId>jms</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>javax.mail</groupId>
+ <artifactId>mail</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>httpunit</groupId>
+ <artifactId>httpunit</artifactId>
+ <version>1.6.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ <version>3.2.3.RELEASE</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <testResources>
+ <testResource>
+ <directory>src/test/resources</directory>
+ <filtering>true</filtering>
+ </testResource>
+ </testResources>
+ <plugins>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-info</id>
+ <phase>validate</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${basedir}/target/package-info</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${basedir}</directory>
+ <filtering>true</filtering>
+ <includes>
+ <include>README*</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${basedir}</directory>
+ <filtering>false</filtering>
+ <includes>
+ <include>LICENSE*</include>
+ <include>NOTICE*</include>
+ <include>CHANGELOG*</include>
+ <include>pom.xml</include>
+ </includes>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-scripts</id>
+ <phase>validate</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${basedir}/target/bin</outputDirectory>
+ <resources>
+ <resource>
+ <directory>bin</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <fork>true</fork>
+ <debug>true</debug>
+ <showDeprecation>true</showDeprecation>
+ <showWarnings>true</showWarnings>
+ <compilerArgument>-Xlint:unchecked</compilerArgument>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>2.5</version>
+ <configuration>
+ <configLocation>${checkstyle.dir}/checkstyle_checks.xml</configLocation>
+ <headerLocation>${checkstyle.dir}/checkstyle_header</headerLocation>
+ <suppressionsLocation>${checkstyle.dir}/suppressions.xml</suppressionsLocation>
+ <includeTestSourceDirectory>true</includeTestSourceDirectory>
+ <failsOnError>false</failsOnError>
+ <outputFileFormat>plain</outputFileFormat>
+ </configuration>
+ <executions>
+ <execution>
+ <id>checkstyle</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>checkstyle</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <argLine>-Xms1536m -Xmx1536m -XX:PermSize=512m -XX:MaxPermSize=512m</argLine>
+ <!-- tests are currently host specific and require authorization -->
+ <skipTests>true</skipTests>
+ <suiteXmlFiles>
+ <suiteXmlFile>${testng.dir}/testng.xml</suiteXmlFile>
+ </suiteXmlFiles>
+ <!-- sometimes useful if testng runs out of memory
+ <disableXmlReport>true</disableXmlReport>
+ -->
+ <systemProperties>
+ <property>
+ <name>log4j.configuration</name>
+ <value>log4j.xml</value>
+ </property>
+ </systemProperties>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <links>
+ <link>http://java.sun.com/j2se/1.5.0/docs/api</link>
+ </links>
+ <bottom><![CDATA[<i>Copyright © 2003-2010 Virginia Tech. All Rights Reserved.</i>]]></bottom>
+ </configuration>
+ <executions>
+ <execution>
+ <id>javadoc</id>
+ <phase>package</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>source</id>
+ <phase>package</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>jar</id>
+ <phase>package</phase>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <appendAssemblyId>true</appendAssemblyId>
+ <descriptors>
+ <descriptor>${assembly.dir}/vt-middleware.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <extensions>
+ <extension>
+ <groupId>org.jvnet.wagon-svn</groupId>
+ <artifactId>wagon-svn</artifactId>
+ <version>1.9</version>
+ </extension>
+ </extensions>
+ </build>
+ <profiles>
+ <profile>
+ <id>sign-artifacts</id>
+ <activation>
+ <property>
+ <name>sign</name>
+ <value>true</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>package</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/properties/krb5_jaas.config b/properties/krb5_jaas.config
new file mode 100644
index 0000000..08b3557
--- /dev/null
+++ b/properties/krb5_jaas.config
@@ -0,0 +1,11 @@
+com.sun.security.jgss.initiate {
+ com.sun.security.auth.module.Krb5LoginModule required
+ doNotPrompt="true"
+ debug="true"
+ renewTGT="true"
+ principal="dfisher"
+ useTicketCache="true"
+ ticketCache="/tmp/krb5cc_dfisher"
+ useKeyTab="true"
+ keyTab="/home/dfisher/krb5.keytab";
+};
diff --git a/properties/ldap.properties b/properties/ldap.properties
new file mode 100644
index 0000000..7354b2c
--- /dev/null
+++ b/properties/ldap.properties
@@ -0,0 +1,122 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# fully qualified class name of the context factory that JNDI should use
+# default value is 'com.sun.jndi.ldap.LdapCtxFactory'
+#edu.vt.middleware.ldap.contextFactory=
+
+# fully qualified class name which implements javax.net.ssl.SSLSocketFactory
+#edu.vt.middleware.ldap.sslSocketFactory=
+
+# fully qualified class name which implements javax.net.ssl.HostnameVerifier
+#edu.vt.middleware.ldap.hostnameVerifier=
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://directory.vt.edu:389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=People,dc=vt,dc=edu
+
+# bind DN if one is required to bind before searching
+#edu.vt.middleware.ldap.bindDn=cn=manager,ou=Services,dc=vt,dc=edu
+
+# credential for the bind DN
+#edu.vt.middleware.ldap.bindCredential=manager_password
+
+# LDAP authentication mechanism
+# default value is 'simple'
+#edu.vt.middleware.ldap.authtype=
+
+# require an authoritative source, this value must be either 'true' or 'false'
+#edu.vt.middleware.ldap.authoritative=
+
+# sets the amount of time in milliseconds that search operations will block
+#edu.vt.middleware.ldap.timeLimit=
+
+# sets the amount of time in milliseconds that connect operations will block
+#edu.vt.middleware.ldap.timeout=
+
+# sets the maximum number of entries that search operations will return
+#edu.vt.middleware.ldap.countLimit=
+
+# sets the batch size to use when returning results
+# default value is '-1'
+#edu.vt.middleware.ldap.batchSize=
+
+# sets the DNS url to use for hostname resolution
+# example is 'dns://somehost/wiz.com'
+#edu.vt.middleware.ldap.dnsUrl=
+
+# sets the preferred language
+# default value is determined by the service provider
+#edu.vt.middleware.ldap.language=
+
+# specifies how referrals should be handled
+# must be one of 'throw', 'ignore', or 'follow'
+#edu.vt.middleware.ldap.referral=
+
+# specifies how aliases should be handled
+# must be one of 'always', 'never', 'finding', or 'searching'
+#edu.vt.middleware.ldap.derefAliases=
+
+# specifies additional attributes which should be treated as binary
+# attribute names should be space delimited
+edu.vt.middleware.ldap.binaryAttributes=userSMIMECertificate
+
+# only return attribute type names, this value must be either 'true' or 'false'
+#edu.vt.middleware.ldap.typesOnly=
+
+# whether SSL should be used for LDAP connections
+# default value is 'false'
+#edu.vt.middleware.ldap.ssl=
+
+# whether TLS should be used for LDAP connections
+# default value is 'false'
+#edu.vt.middleware.ldap.tls=
+
+## LDAP AUTHENTICATOR CONFIG ##
+
+# can be used to override any of the previous properties
+
+# fully qualified class name which implements javax.net.ssl.SSLSocketFactory
+#edu.vt.middleware.ldap.auth.sslSocketFactory=
+
+# fully qualified class name which implements javax.net.ssl.HostnameVerifier
+#edu.vt.middleware.ldap.auth.hostnameVerifier=
+
+# hostname and optional port of your LDAP
+edu.vt.middleware.ldap.auth.ldapUrl=ldap://authn.directory.vt.edu:389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.auth.baseDn=ou=People,dc=vt,dc=edu
+
+# LDAP authentication mechanism
+# default value is 'simple'
+#edu.vt.middleware.ldap.auth.authtype=
+
+# LDAP field which contains user identifier
+edu.vt.middleware.ldap.auth.userField=uupid
+
+# whether the authentication dn should be constructed or looked up in the LDAP
+edu.vt.middleware.ldap.auth.constructDn=false
+
+# whether the authentication dn should be searched for over the entire base
+edu.vt.middleware.ldap.auth.subtreeSearch=false
+
+# whether authentication credentials should be logged
+# default value is 'false'
+#edu.vt.middleware.ldap.auth.logCredentials=
+
+# whether SSL should be used for LDAP connections
+# default value is 'false'
+#edu.vt.middleware.ldap.auth.ssl=
+
+# whether TLS should be used for LDAP connections
+# default value is 'false'
+edu.vt.middleware.ldap.auth.tls=true
+
+# ldap filter to use for performing authorization
+#edu.vt.middleware.ldap.auth.authorizationFilter=(&(eduPersonAffiliation=VT-ALUM)(eduPersonAffiliation=VT-EMPLOYEE))
diff --git a/properties/ldap_jaas.config b/properties/ldap_jaas.config
new file mode 100644
index 0000000..9dccf02
--- /dev/null
+++ b/properties/ldap_jaas.config
@@ -0,0 +1,8 @@
+vt-ldap {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ ldapUrl="ldap://authn.directory.vt.edu:389"
+ baseDn="ou=people,dc=vt,dc=edu"
+ tls="true"
+ userFilter="(uupid={0})"
+ userRoleAttribute="eduPersonAffiliation,groupMembership";
+};
diff --git a/src/main/assembly/vt-middleware.xml b/src/main/assembly/vt-middleware.xml
new file mode 100644
index 0000000..2386fdd
--- /dev/null
+++ b/src/main/assembly/vt-middleware.xml
@@ -0,0 +1,74 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<assembly>
+ <id>dist</id>
+ <formats>
+ <format>tar.gz</format>
+ <format>zip</format>
+ </formats>
+ <dependencySets>
+ <dependencySet>
+ <useProjectArtifact>false</useProjectArtifact>
+ <outputDirectory>lib</outputDirectory>
+ <scope>runtime</scope>
+ <includes>
+ <include>*:jar</include>
+ </includes>
+ </dependencySet>
+ </dependencySets>
+ <fileSets>
+ <fileSet>
+ <directory>target/package-info</directory>
+ <outputDirectory>/</outputDirectory>
+ <includes>
+ <include>**</include>
+ </includes>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ </fileSet>
+ <fileSet>
+ <directory>src</directory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ </fileSet>
+ <fileSet>
+ <directory>properties</directory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ </fileSet>
+ <fileSet>
+ <directory>licenses</directory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ </fileSet>
+ <fileSet>
+ <directory>dict</directory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ </fileSet>
+ <fileSet>
+ <directory>target/bin</directory>
+ <outputDirectory>bin</outputDirectory>
+ <fileMode>0755</fileMode>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ <excludes>
+ <exclude>*.bat</exclude>
+ </excludes>
+ </fileSet>
+ <fileSet>
+ <directory>target/bin</directory>
+ <outputDirectory>bin</outputDirectory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ <includes>
+ <include>*.bat</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>target</directory>
+ <outputDirectory>jars</outputDirectory>
+ <includes>
+ <include>${project.build.finalName}*.jar</include>
+ </includes>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ </fileSet>
+ <fileSet>
+ <directory>target/site/apidocs</directory>
+ <outputDirectory>docs/apidocs</outputDirectory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/src/main/checkstyle/checkstyle_checks.xml b/src/main/checkstyle/checkstyle_checks.xml
new file mode 100644
index 0000000..051c88f
--- /dev/null
+++ b/src/main/checkstyle/checkstyle_checks.xml
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC
+ "-//Puppy Crawl//DTD Check Configuration 1.1//EN"
+ "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+
+<module name="Checker">
+
+ <!-- limit source file size -->
+ <module name="FileLength">
+ <property name="max" value="2000" />
+ </module>
+
+ <!-- forbid tab characters in source files -->
+ <module name="FileTabCharacter"/>
+
+ <module name="NewlineAtEndOfFile"/>
+ <module name="TreeWalker">
+ <property name="tabWidth" value="2"/>
+
+ <!-- javadocs -->
+ <module name="JavadocType">
+ <property name="authorFormat" value="\S+"/>
+ <property name="versionFormat" value="\S+"/>
+ </module>
+ <module name="JavadocMethod">
+ <property name="allowThrowsTagsForSubclasses" value="true"/>
+ <property name="allowUndeclaredRTE" value="true"/>
+ </module>
+ <module name="JavadocVariable"/>
+ <module name="JavadocStyle">
+ <property name="checkFirstSentence" value="false"/>
+ </module>
+
+ <!-- naming -->
+ <module name="AbstractClassName"/>
+ <module name="ClassTypeParameterName"/>
+ <module name="ConstantName"/>
+ <module name="LocalFinalVariableName"/>
+ <module name="LocalVariableName"/>
+ <module name="MemberName"/>
+ <module name="MethodName"/>
+ <module name="MethodTypeParameterName"/>
+ <module name="PackageName"/>
+ <module name="ParameterName"/>
+ <module name="StaticVariableName"/>
+ <module name="TypeName"/>
+
+ <!-- imports -->
+ <module name="AvoidStarImport"/>
+ <module name="AvoidStaticImport"/>
+ <module name="IllegalImport"/>
+ <module name="RedundantImport"/>
+ <module name="UnusedImports"/>
+ <module name="ImportOrder">
+ <property name="groups" value="java,javax"/>
+ <property name="ordered" value="true"/>
+ </module>
+
+ <!-- sizes -->
+ <module name="LineLength">
+ <property name="ignorePattern" value="(^.*\$.*\$.*$)|(^import .*)"/>
+ <property name="max" value="80"/>
+ </module>
+ <module name="MethodLength">
+ <property name="max" value="300"/>
+ </module>
+ <module name="AnonInnerLength"/>
+ <module name="ParameterNumber"/>
+
+ <!-- whitespace -->
+ <module name="GenericWhitespace"/>
+ <module name="EmptyForInitializerPad"/>
+ <module name="EmptyForIteratorPad"/>
+ <module name="MethodParamPad"/>
+ <module name="NoWhitespaceAfter"/>
+ <module name="NoWhitespaceBefore">
+ <property name="allowLineBreaks" value="true"/>
+ <property name="tokens"
+ value="SEMI,DOT,POST_DEC,POST_INC,PLUS"/>
+ </module>
+ <module name="OperatorWrap">
+ <property name="option" value="eol"/>
+ <property name="tokens"
+ value="BAND,BOR,BSR,BXOR,DIV,EQUAL,GE,GT,LAND,LE,
+ LITERAL_INSTANCEOF,LOR,LT,MINUS,MOD,NOT_EQUAL,PLUS,
+ SL,SR,STAR"/>
+ </module>
+ <module name="ParenPad"/>
+ <module name="TypecastParenPad"/>
+ <module name="WhitespaceAfter"/>
+ <module name="WhitespaceAround">
+ <property name="tokens"
+ value="ASSIGN,BAND,BAND_ASSIGN,BOR,BOR_ASSIGN,BSR,BSR_ASSIGN,
+ BXOR,BXOR_ASSIGN,COLON,DIV,DIV_ASSIGN,EQUAL,GE,GT,LAND,
+ LE,LITERAL_ASSERT,LITERAL_CATCH,LITERAL_DO,
+ LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,
+ LITERAL_RETURN,LITERAL_SYNCHRONIZED,LITERAL_TRY,
+ LITERAL_WHILE,LOR,LT,MINUS,MINUS_ASSIGN,MOD,MOD_ASSIGN,
+ NOT_EQUAL,PLUS_ASSIGN,QUESTION,SL,
+ SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN"/>
+ </module>
+
+ <!-- modifiers -->
+ <module name="ModifierOrder"/>
+ <module name="RedundantModifier"/>
+
+ <!-- blocks -->
+ <module name="EmptyBlock"/>
+ <module name="LeftCurly">
+ <property name="option" value="nl"/>
+ <property name="tokens"
+ value="CLASS_DEF,CTOR_DEF,INTERFACE_DEF,METHOD_DEF"/>
+ </module>
+ <module name="NeedBraces"/>
+ <module name="RightCurly"/>
+ <module name="AvoidNestedBlocks"/>
+
+ <!-- coding -->
+ <module name="ArrayTrailingComma"/>
+ <module name="CovariantEquals"/>
+ <module name="DoubleCheckedLocking"/>
+ <module name="EmptyStatement"/>
+ <module name="EqualsAvoidNull"/>
+ <module name="EqualsHashCode"/>
+ <module name="FinalLocalVariable"/>
+ <!--
+ <module name="HiddenField"/>
+ -->
+ <module name="IllegalInstantiation">
+ <property name="classes" value="java.lang.Boolean"/>
+ </module>
+ <module name="InnerAssignment"/>
+ <module name="MagicNumber"/>
+ <module name="MissingSwitchDefault"/>
+ <module name="RedundantThrows"/>
+ <module name="SimplifyBooleanExpression"/>
+ <module name="SimplifyBooleanReturn"/>
+ <module name="StringLiteralEquality"/>
+ <module name="NestedIfDepth">
+ <property name="max" value="5"/>
+ </module>
+ <module name="NestedTryDepth">
+ <property name="max" value="5"/>
+ </module>
+ <module name="SuperClone"/>
+ <module name="SuperFinalize"/>
+ <module name="PackageDeclaration"/>
+ <module name="ReturnCount"/>
+ <module name="IllegalType">
+ <property name="illegalClassNames"
+ value="java.util.GregorianCalendar, java.util.Hashtable,
+ java.util.HashSet, java.util.HashMap,
+ java.util.ArrayList, java.util.LinkedList,
+ java.util.LinkedHashMap, java.util.LinkedHashSet,
+ java.util.TreeSet, java.util.TreeMap, java.util.Vector"/>
+ </module>
+ <module name="DeclarationOrder"/>
+ <module name="ParameterAssignment"/>
+ <module name="ExplicitInitialization"/>
+ <module name="DefaultComesLast"/>
+ <module name="FallThrough"/>
+ <module name="MultipleVariableDeclarations"/>
+ <module name="RequireThis">
+ <property name="checkFields" value="false"/>
+ <property name="checkMethods" value="false"/>
+ </module>
+ <module name="UnnecessaryParentheses"/>
+
+ <!-- design -->
+ <module name="VisibilityModifier">
+ <property name="protectedAllowed" value="true"/>
+ </module>
+ <module name="FinalClass"/>
+ <module name="InterfaceIsType"/>
+ <module name="HideUtilityClassConstructor"/>
+ <module name="MutableException"/>
+ <module name="ThrowsCount">
+ <property name="max" value="2"/>
+ </module>
+
+ <!-- prevent trailing whitespace -->
+ <module name="Regexp">
+ <property name="format" value="[ \t]+$"/>
+ <property name="illegalPattern" value="true"/>
+ <property name="message" value="Trailing whitespace"/>
+ </module>
+
+ <!-- misc -->
+ <module name="UpperEll"/>
+ <module name="ArrayTypeStyle"/>
+ <module name="FinalParameters"/>
+ <module name="Indentation">
+ <property name="basicOffset" value="2"/>
+ <property name="caseIndent" value="0"/>
+ </module>
+ <module name="TrailingComment"/>
+
+ </module>
+</module>
diff --git a/src/main/checkstyle/checkstyle_header b/src/main/checkstyle/checkstyle_header
new file mode 100644
index 0000000..d8c79ee
--- /dev/null
+++ b/src/main/checkstyle/checkstyle_header
@@ -0,0 +1,13 @@
+/\*
+ \$Id*
+
+ Copyright \(C\) \d\d\d\d Virginia Tech*
+ All rights reserved\.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: *
+ Email: *\@*\.*
+ Version: \$Revision*
+ Updated: \$Date*
+\*/
diff --git a/src/main/checkstyle/suppressions.xml b/src/main/checkstyle/suppressions.xml
new file mode 100644
index 0000000..c548080
--- /dev/null
+++ b/src/main/checkstyle/suppressions.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suppressions PUBLIC
+ "-//Puppy Crawl//DTD Suppressions 1.0//EN"
+ "http://www.puppycrawl.com/dtds/suppressions_1_0.dtd">
+
+<suppressions>
+ <!-- classloader problem with custom exception -->
+ <suppress checks=".*"
+ files=".*\.java"
+ lines="0"/>
+
+ <!-- nothing magic about test data -->
+ <suppress checks="MagicNumber"
+ files=".*Test\.java" />
+</suppressions>
diff --git a/src/main/java/edu/vt/middleware/ldap/AbstractCli.java b/src/main/java/edu/vt/middleware/ldap/AbstractCli.java
new file mode 100644
index 0000000..6c14f68
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/AbstractCli.java
@@ -0,0 +1,278 @@
+/*
+ $Id: AbstractCli.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import edu.vt.middleware.ldap.props.LdapConfigPropertyInvoker;
+import edu.vt.middleware.ldap.props.LdapProperties;
+import edu.vt.middleware.ldap.props.PropertyConfig;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Abstract base class for all CLI handlers.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public abstract class AbstractCli
+{
+
+ /** Option to print usage. */
+ protected static final String OPT_HELP = "help";
+
+ /** Option for ldap trace. */
+ protected static final String OPT_TRACE = "trace";
+
+ /** Option for loading ldap configuration from properties. */
+ protected static final String OPT_USE_PROPERTIES = "useProperties";
+
+ /** Option for dsmlv1 output. */
+ protected static final String OPT_DSMLV1 = "dsmlv1";
+
+ /** Option for dsmlv2 output. */
+ protected static final String OPT_DSMLV2 = "dsmlv2";
+
+ /** List of command options. */
+ protected List<String> opts = new ArrayList<String>();
+
+ /** Log. */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /** Command line options. */
+ protected Options options = new Options();
+
+ /** Whether to output dsml version 1, the default is ldif. */
+ protected boolean outputDsmlv1;
+
+ /** Whether to output dsml version 2, the default is ldif. */
+ protected boolean outputDsmlv2;
+
+
+ /** Default constructor. */
+ public AbstractCli()
+ {
+ this.opts.add(OPT_HELP);
+ this.opts.add(OPT_TRACE);
+ this.opts.add(OPT_USE_PROPERTIES);
+ this.opts.add(OPT_DSMLV1);
+ this.opts.add(OPT_DSMLV2);
+ }
+
+
+ /**
+ * Parses command line options and invokes the proper handler to perform the
+ * requested action, or the default action if no action is specified.
+ *
+ * @param args Command line arguments.
+ */
+ public final void performAction(final String[] args)
+ {
+ initOptions();
+ try {
+ if (args.length > 0) {
+ final CommandLineParser parser = new GnuParser();
+ final CommandLine line = parser.parse(options, args);
+ dispatch(line);
+ } else {
+ printExamples();
+ }
+ } catch (ParseException pex) {
+ System.err.println(
+ "Failed parsing command arguments: " + pex.getMessage());
+ } catch (IllegalArgumentException iaex) {
+ String msg = "Operation failed: " + iaex.getMessage();
+ if (iaex.getCause() != null) {
+ msg += " Underlying reason: " + iaex.getCause().getMessage();
+ }
+ System.err.println(msg);
+ } catch (Exception ex) {
+ System.err.println("Operation failed:");
+ ex.printStackTrace(System.err);
+ }
+ }
+
+
+ /** Initialize CLI options. */
+ protected abstract void initOptions();
+
+
+ /**
+ * Initialize CLI options with the supplied invoker.
+ *
+ * @param invoker <code>PropertyInvoker</code>
+ */
+ protected void initOptions(final LdapConfigPropertyInvoker invoker)
+ {
+ final Map<String, String> args = this.getArgs();
+ for (String s : invoker.getProperties()) {
+ final String arg = s.substring(s.lastIndexOf(".") + 1, s.length());
+ if (args.containsKey(arg)) {
+ options.addOption(new Option(arg, true, args.get(arg)));
+ }
+ }
+ options.addOption(new Option(OPT_HELP, false, "display all options"));
+ options.addOption(
+ new Option(OPT_TRACE, false, "print ASN.1 BER packets to System.out"));
+ options.addOption(
+ new Option(
+ OPT_USE_PROPERTIES,
+ false,
+ "load options from the default properties file"));
+ options.addOption(
+ new Option(OPT_DSMLV1, false, "output results in DSML v1"));
+ options.addOption(
+ new Option(OPT_DSMLV2, false, "output results in DSML v2"));
+ }
+
+
+ /**
+ * Gets the name of the command for which this class provides a CLI interface.
+ *
+ * @return Name of CLI command.
+ */
+ protected abstract String getCommandName();
+
+
+ /**
+ * Dispatch command line data to the handler that can perform the operation
+ * requested on the command line.
+ *
+ * @param line Parsed command line arguments container.
+ *
+ * @throws Exception On errors thrown by handler.
+ */
+ protected abstract void dispatch(final CommandLine line)
+ throws Exception;
+
+
+ /** Prints CLI help text. */
+ protected void printHelp()
+ {
+ final HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp(getCommandName(), options);
+ }
+
+
+ /** Prints CLI usage examples. */
+ protected void printExamples()
+ {
+ final String fullName = getClass().getName();
+ final String name = fullName.substring(fullName.lastIndexOf('.') + 1);
+ final InputStream in = getClass().getResourceAsStream(name + ".examples");
+ if (in != null) {
+ final BufferedReader reader = new BufferedReader(
+ new InputStreamReader(in));
+ try {
+ System.out.println();
+
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ System.out.println(line);
+ }
+ } catch (IOException e) {
+ System.err.println("Error reading examples from resource stream.");
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException ex) {
+ System.err.println("Error closing example resource stream.");
+ }
+ System.out.println();
+ }
+ } else {
+ System.out.println("No usage examples available for " + getCommandName());
+ }
+ }
+
+
+ /**
+ * Returns the command line arguments for this cli.
+ *
+ * @return map of arg name to description
+ */
+ protected Map<String, String> getArgs()
+ {
+ final Map<String, String> args = new HashMap<String, String>();
+ final String fullName = getClass().getName();
+ final String name = fullName.substring(fullName.lastIndexOf('.') + 1);
+ final InputStream in = getClass().getResourceAsStream(name + ".args");
+ if (in != null) {
+ final BufferedReader reader = new BufferedReader(
+ new InputStreamReader(in));
+ try {
+ System.out.println();
+
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ final String[] s = line.split(":");
+ if (s.length > 1) {
+ args.put(s[0], s[1]);
+ }
+ }
+ } catch (IOException e) {
+ System.err.println("Error reading arguments from resource stream.");
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException ex) {
+ System.err.println("Error closing arguments resource stream.");
+ }
+ System.out.println();
+ }
+ } else {
+ System.out.println("No arguments available for " + getCommandName());
+ }
+ return args;
+ }
+
+
+ /**
+ * Initialize the supplied config with command line options.
+ *
+ * @param config property config to configure
+ * @param line Parsed command line arguments container.
+ *
+ * @throws Exception On errors thrown by handler.
+ */
+ protected void initLdapProperties(
+ final PropertyConfig config,
+ final CommandLine line)
+ throws Exception
+ {
+ final LdapProperties ldapProperties = new LdapProperties(config);
+ for (Option o : line.getOptions()) {
+ if (o.getOpt().equals(OPT_USE_PROPERTIES)) {
+ ldapProperties.useDefaultPropertiesFile();
+ } else if (!this.opts.contains(o.getOpt())) {
+ ldapProperties.setProperty(o.getOpt(), o.getValue());
+ }
+ }
+ ldapProperties.configure();
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/AbstractLdap.java b/src/main/java/edu/vt/middleware/ldap/AbstractLdap.java
new file mode 100644
index 0000000..3d5973b
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/AbstractLdap.java
@@ -0,0 +1,1166 @@
+/*
+ $Id: AbstractLdap.java 1440 2010-06-27 16:41:34Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1440 $
+ Updated: $Date: 2010-06-27 17:41:34 +0100 (Sun, 27 Jun 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import javax.naming.Binding;
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.Control;
+import javax.naming.ldap.LdapContext;
+import javax.naming.ldap.PagedResultsControl;
+import javax.naming.ldap.PagedResultsResponseControl;
+import edu.vt.middleware.ldap.handler.AttributeHandler;
+import edu.vt.middleware.ldap.handler.AttributesProcessor;
+import edu.vt.middleware.ldap.handler.ConnectionHandler;
+import edu.vt.middleware.ldap.handler.CopyResultHandler;
+import edu.vt.middleware.ldap.handler.SearchCriteria;
+import edu.vt.middleware.ldap.handler.SearchResultHandler;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AbstractLdap</code> contains the functions for basic interaction with a
+ * LDAP. Methods are provided for connecting, binding, querying and updating.
+ *
+ * @param <T> type of LdapConfig
+ *
+ * @author Middleware Services
+ * @version $Revision: 1440 $ $Date: 2010-06-27 17:41:34 +0100 (Sun, 27 Jun 2010) $
+ */
+public abstract class AbstractLdap<T extends LdapConfig> implements BaseLdap
+{
+
+ /** Default copy search result handler, used if none supplied. */
+ protected static final CopyResultHandler<SearchResult>
+ SR_COPY_RESULT_HANDLER = new CopyResultHandler<SearchResult>();
+
+ /** Default copy name class pair handler. */
+ protected static final CopyResultHandler<NameClassPair>
+ NCP_COPY_RESULT_HANDLER = new CopyResultHandler<NameClassPair>();
+
+ /** Default copy binding handler. */
+ protected static final CopyResultHandler<Binding>
+ BINDING_COPY_RESULT_HANDLER = new CopyResultHandler<Binding>();
+
+ /** Default copy result handler. */
+ protected static final CopyResultHandler<Object> COPY_RESULT_HANDLER =
+ new CopyResultHandler<Object>();
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** LDAP connection handler. */
+ protected ConnectionHandler connectionHandler;
+
+ /** LDAP configuration environment. */
+ protected T config;
+
+
+ /**
+ * This will set the config parameters of this <code>Ldap</code>.
+ *
+ * @param ldapConfig <code>LdapConfig</code>
+ */
+ protected void setLdapConfig(final T ldapConfig)
+ {
+ if (this.config != null) {
+ this.config.checkImmutable();
+ }
+ this.config = ldapConfig;
+ }
+
+
+ /**
+ * This will perform an LDAP compare operation with the supplied filter and
+ * dn. Note that to perform a <b>real</b> LDAP compare operation, your filter
+ * must be of the form '(name=value)'. Any other filter expression will result
+ * in a regular object level search operation. In either case the desired
+ * result is achieved, but the underlying LDAP invocation is different.
+ *
+ * @param dn <code>String</code> name to compare
+ * @param filter <code>String</code> expression to use for compare
+ * @param filterArgs <code>Object[]</code> to substitute for variables in
+ * the filter
+ *
+ * @return <code>boolean</code> - result of compare operation
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected boolean compare(
+ final String dn,
+ final String filter,
+ final Object[] filterArgs)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Compare with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ this.logger.debug(" filter = " + filter);
+ this.logger.debug(" filterArgs = " + Arrays.toString(filterArgs));
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ boolean success = false;
+ LdapContext ctx = null;
+ NamingEnumeration<SearchResult> en = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ en = ctx.search(
+ dn,
+ filter,
+ filterArgs,
+ LdapConfig.getCompareSearchControls());
+
+ if (en.hasMore()) {
+ success = true;
+ }
+
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (en != null) {
+ en.close();
+ }
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ return success;
+ }
+
+
+ /**
+ * This will query the LDAP with the supplied dn, filter, filter arguments,
+ * and search controls. This method will perform a search whose scope is
+ * defined in the search controls. The resulting <code>Iterator</code> is a
+ * deep copy of the original search results. If filterArgs is null, then no
+ * variable substitution will occur. See {@link
+ * javax.naming.DirContext#search( String, String, Object[], SearchControls)}.
+ *
+ * @param dn <code>String</code> name to begin search at
+ * @param filter <code>String</code> expression to use for the search
+ * @param filterArgs <code>Object[]</code> to substitute for variables in
+ * the filter
+ * @param searchControls <code>SearchControls</code> to perform search with
+ * @param handler <code>SearchResultHandler[]</code> to post process results
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected Iterator<SearchResult> search(
+ final String dn,
+ final String filter,
+ final Object[] filterArgs,
+ final SearchControls searchControls,
+ final SearchResultHandler... handler)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Search with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ this.logger.debug(" filter = " + filter);
+ this.logger.debug(" filterArgs = " + Arrays.toString(filterArgs));
+ this.logger.debug(" searchControls = " + searchControls);
+ this.logger.debug(" handler = " + Arrays.toString(handler));
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ List<SearchResult> results = null;
+ LdapContext ctx = null;
+ NamingEnumeration<SearchResult> en = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ en = ctx.search(dn, filter, filterArgs, searchControls);
+
+ if (handler != null && handler.length > 0) {
+ final SearchCriteria sc = new SearchCriteria();
+ if (ctx != null && !"".equals(ctx.getNameInNamespace())) {
+ sc.setDn(ctx.getNameInNamespace());
+ } else {
+ sc.setDn(dn);
+ }
+ sc.setFilter(filter);
+ sc.setFilterArgs(filterArgs);
+ if (searchControls != null) {
+ sc.setReturnAttrs(searchControls.getReturningAttributes());
+ }
+ for (int j = 0; j < handler.length; j++) {
+ if (j == 0) {
+ results = handler[j].process(
+ sc,
+ en,
+ this.config.getHandlerIgnoreExceptions());
+ } else {
+ results = handler[j].process(sc, results);
+ }
+ }
+ } else {
+ results = SR_COPY_RESULT_HANDLER.process(
+ null,
+ en,
+ this.config.getHandlerIgnoreExceptions());
+ }
+
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (en != null) {
+ en.close();
+ }
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ return results.iterator();
+ }
+
+
+ /**
+ * This will query the LDAP with the supplied dn, filter, filter arguments,
+ * and search controls. See {@link #search(String, String, Object[],
+ * SearchControls, SearchResultHandler...)}. The PagedResultsControl is used
+ * in conjunction with {@link LdapConfig#getPagedResultsSize()} to produce the
+ * results.
+ *
+ * @param dn <code>String</code> name to begin search at
+ * @param filter <code>String</code> expression to use for the search
+ * @param filterArgs <code>Object[]</code> to substitute for variables in
+ * the filter
+ * @param searchControls <code>SearchControls</code> to perform search with
+ * @param handler <code>SearchResultHandler[]</code> to post process results
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected Iterator<SearchResult> pagedSearch(
+ final String dn,
+ final String filter,
+ final Object[] filterArgs,
+ final SearchControls searchControls,
+ final SearchResultHandler... handler)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Paginated search with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ this.logger.debug(" filter = " + filter);
+ this.logger.debug(" filterArgs = " + Arrays.toString(filterArgs));
+ this.logger.debug(" searchControls = " + searchControls);
+ this.logger.debug(" handler = " + Arrays.toString(handler));
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ final List<SearchResult> results = new ArrayList<SearchResult>();
+ LdapContext ctx = null;
+ NamingEnumeration<SearchResult> en = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ byte[] cookie = null;
+ ctx = this.getContext();
+ ctx.setRequestControls(
+ new Control[] {
+ new PagedResultsControl(
+ this.config.getPagedResultsSize(),
+ Control.CRITICAL),
+ });
+ do {
+ List<SearchResult> pagedResults = null;
+ en = ctx.search(dn, filter, filterArgs, searchControls);
+
+ if (handler != null && handler.length > 0) {
+ final SearchCriteria sc = new SearchCriteria();
+ if (ctx != null && !"".equals(ctx.getNameInNamespace())) {
+ sc.setDn(ctx.getNameInNamespace());
+ } else {
+ sc.setDn(dn);
+ }
+ sc.setFilter(filter);
+ sc.setFilterArgs(filterArgs);
+ if (searchControls != null) {
+ sc.setReturnAttrs(searchControls.getReturningAttributes());
+ }
+ for (int j = 0; j < handler.length; j++) {
+ if (j == 0) {
+ pagedResults = handler[j].process(
+ sc,
+ en,
+ this.config.getHandlerIgnoreExceptions());
+ } else {
+ pagedResults = handler[j].process(sc, pagedResults);
+ }
+ }
+ } else {
+ pagedResults = SR_COPY_RESULT_HANDLER.process(
+ null,
+ en,
+ this.config.getHandlerIgnoreExceptions());
+ }
+
+ results.addAll(pagedResults);
+
+ final Control[] controls = ctx.getResponseControls();
+ if (controls != null) {
+ for (int j = 0; j < controls.length; j++) {
+ if (controls[j] instanceof PagedResultsResponseControl) {
+ final PagedResultsResponseControl prrc =
+ (PagedResultsResponseControl) controls[j];
+ cookie = prrc.getCookie();
+ }
+ }
+ }
+
+ // re-activate paged results
+ ctx.setRequestControls(
+ new Control[] {
+ new PagedResultsControl(
+ this.config.getPagedResultsSize(),
+ cookie,
+ Control.CRITICAL),
+ });
+
+ } while (cookie != null);
+
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ } catch (IOException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Could not encode page size into control", e);
+ }
+ throw new NamingException(e.getMessage());
+ }
+ }
+ } finally {
+ if (en != null) {
+ en.close();
+ }
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ return results.iterator();
+ }
+
+
+ /**
+ * This will query the LDAP for the supplied dn, matching attributes and
+ * return attributes. This method will always perform a one level search. The
+ * resulting <code>Iterator</code> is a deep copy of the original search
+ * results. If matchAttrs is empty or null then all objects in the target
+ * context are returned. If retAttrs is null then all attributes will be
+ * returned. If retAttrs is an empty array then no attributes will be
+ * returned. See {@link javax.naming.DirContext#search(String, Attributes,
+ * String[])}.
+ *
+ * @param dn <code>String</code> name to search in
+ * @param matchAttrs <code>Attributes</code> attributes to match
+ * @param retAttrs <code>String[]</code> attributes to return
+ * @param handler <code>SearchResultHandler[]</code> to post process results
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected Iterator<SearchResult> searchAttributes(
+ final String dn,
+ final Attributes matchAttrs,
+ final String[] retAttrs,
+ final SearchResultHandler... handler)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("One level search with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ this.logger.debug(" matchAttrs = " + matchAttrs);
+ this.logger.debug(
+ " retAttrs = " +
+ (retAttrs == null ? "all attributes" : Arrays.toString(retAttrs)));
+ this.logger.debug(" handler = " + Arrays.toString(handler));
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ List<SearchResult> results = null;
+ LdapContext ctx = null;
+ NamingEnumeration<SearchResult> en = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ en = ctx.search(dn, matchAttrs, retAttrs);
+
+ if (handler != null && handler.length > 0) {
+ final SearchCriteria sc = new SearchCriteria();
+ if (ctx != null && !"".equals(ctx.getNameInNamespace())) {
+ sc.setDn(ctx.getNameInNamespace());
+ } else {
+ sc.setDn(dn);
+ }
+ sc.setMatchAttrs(matchAttrs);
+ sc.setReturnAttrs(retAttrs);
+ if (handler != null && handler.length > 0) {
+ for (int j = 0; j < handler.length; j++) {
+ if (j == 0) {
+ results = handler[j].process(
+ sc,
+ en,
+ this.config.getHandlerIgnoreExceptions());
+ } else {
+ results = handler[j].process(sc, results);
+ }
+ }
+ }
+ } else {
+ results = SR_COPY_RESULT_HANDLER.process(
+ null,
+ en,
+ this.config.getHandlerIgnoreExceptions());
+ }
+
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (en != null) {
+ en.close();
+ }
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ return results.iterator();
+ }
+
+
+ /**
+ * This will enumerate the names bounds to the specified context, along with
+ * the class names of objects bound to them. The resulting <code>
+ * Iterator</code> is a deep copy of the original search results. See {@link
+ * javax.naming.Context#list(String)}.
+ *
+ * @param dn <code>String</code> LDAP context to list
+ *
+ * @return <code>Iterator</code> - LDAP search result
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected Iterator<NameClassPair> list(final String dn)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("list with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ List<NameClassPair> results = null;
+ LdapContext ctx = null;
+ NamingEnumeration<NameClassPair> en = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ en = ctx.list(dn);
+
+ results = NCP_COPY_RESULT_HANDLER.process(
+ null,
+ en,
+ this.config.getHandlerIgnoreExceptions());
+
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (en != null) {
+ en.close();
+ }
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ return results.iterator();
+ }
+
+
+ /**
+ * This will enumerate the names bounds to the specified context, along with
+ * the objects bound to them. The resulting <code>Iterator</code> is a deep
+ * copy of the original search results. See {@link
+ * javax.naming.Context#listBindings(String)}.
+ *
+ * @param dn <code>String</code> LDAP context to list
+ *
+ * @return <code>Iterator</code> - LDAP search result
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected Iterator<Binding> listBindings(final String dn)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("listBindings with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ List<Binding> results = null;
+ LdapContext ctx = null;
+ NamingEnumeration<Binding> en = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ en = ctx.listBindings(dn);
+
+ results = BINDING_COPY_RESULT_HANDLER.process(
+ null,
+ en,
+ this.config.getHandlerIgnoreExceptions());
+
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (en != null) {
+ en.close();
+ }
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ return results.iterator();
+ }
+
+
+ /**
+ * This will return the matching attributes associated with the supplied dn.
+ * If retAttrs is null then all attributes will be returned. If retAttrs is an
+ * empty array then no attributes will be returned. See {@link
+ * javax.naming.DirContext#getAttributes(String, String[])}.
+ *
+ * @param dn <code>String</code> named object in the LDAP
+ * @param retAttrs <code>String[]</code> attributes to return
+ * @param handler <code>AttributeHandler[]</code> to post process results
+ *
+ * @return <code>Attributes</code>
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected Attributes getAttributes(
+ final String dn,
+ final String[] retAttrs,
+ final AttributeHandler... handler)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Attribute search with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ this.logger.debug(
+ " retAttrs = " +
+ (retAttrs == null ? "all attributes" : Arrays.toString(retAttrs)));
+ this.logger.debug(" handler = " + Arrays.toString(handler));
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ LdapContext ctx = null;
+ Attributes attrs = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ attrs = ctx.getAttributes(dn, retAttrs);
+
+ if (handler != null && handler.length > 0) {
+ final SearchCriteria sc = new SearchCriteria();
+ if (ctx != null && !"".equals(ctx.getNameInNamespace())) {
+ sc.setDn(ctx.getNameInNamespace());
+ } else {
+ sc.setDn(dn);
+ }
+ for (int j = 0; j < handler.length; j++) {
+ attrs = AttributesProcessor.executeHandler(
+ sc,
+ attrs,
+ handler[j],
+ this.config.getHandlerIgnoreExceptions());
+ }
+ }
+
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ return attrs;
+ }
+
+
+ /**
+ * This will return the LDAP schema associated with the supplied dn. The
+ * resulting <code>Iterator</code> is a deep copy of the original search
+ * results. See {@link javax.naming.DirContext#getSchema(String)}.
+ *
+ * @param dn <code>String</code> named object in the LDAP
+ *
+ * @return <code>Iterator</code> - LDAP search result
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected Iterator<SearchResult> getSchema(final String dn)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Schema search with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ List<SearchResult> results = null;
+ LdapContext ctx = null;
+ DirContext schema = null;
+ NamingEnumeration<SearchResult> en = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ schema = ctx.getSchema(dn);
+ en = schema.search("", null);
+
+ results = SR_COPY_RESULT_HANDLER.process(
+ null,
+ en,
+ this.config.getHandlerIgnoreExceptions());
+
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (schema != null) {
+ schema.close();
+ }
+ if (en != null) {
+ en.close();
+ }
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ return results.iterator();
+ }
+
+
+ /**
+ * This will modify the supplied attributes for the supplied value given by
+ * the modification operation. modOp must be one of: ADD_ATTRIBUTE,
+ * REPLACE_ATTRIBUTE, REMOVE_ATTRIBUTE. The order of the modifications is not
+ * specified. Where possible, the modifications are performed atomically. See
+ * {@link javax.naming.DirContext#modifyAttributes( String, int, Attributes)}.
+ *
+ * @param dn <code>String</code> named object in the LDAP
+ * @param modOp <code>int</code> modification operation
+ * @param attrs <code>Attributes</code> attributes to be used for the
+ * operation, may be null
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected void modifyAttributes(
+ final String dn,
+ final int modOp,
+ final Attributes attrs)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Modify attributes with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ this.logger.debug(" modOp = " + modOp);
+ this.logger.debug(" attrs = " + attrs);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ LdapContext ctx = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ ctx.modifyAttributes(dn, modOp, attrs);
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ }
+
+
+ /**
+ * This will modify the supplied dn using the supplied modifications. The
+ * modifications are performed in the order specified. Each modification
+ * specifies a modification operation code and an attribute on which to
+ * operate. Where possible, the modifications are performed atomically. See
+ * {@link javax.naming.DirContext#modifyAttributes(String,
+ * ModificationItem[])}.
+ *
+ * @param dn <code>String</code> named object in the LDAP
+ * @param mods <code>ModificationItem[]</code> modifications
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected void modifyAttributes(
+ final String dn,
+ final ModificationItem[] mods)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Modify attributes with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ this.logger.debug(" mods = " + Arrays.toString(mods));
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ LdapContext ctx = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ ctx.modifyAttributes(dn, mods);
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ }
+
+
+ /**
+ * This will create the supplied dn in the LDAP namespace with the supplied
+ * attributes. See {@link javax.naming.DirContext#createSubcontext(String,
+ * Attributes)}. Note that the context created by this operation is
+ * immediately closed.
+ *
+ * @param dn <code>String</code> named object in the LDAP
+ * @param attrs <code>Attributes</code> attributes to be added to this entry
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected void create(final String dn, final Attributes attrs)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Create name with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ this.logger.debug(" attrs = " + attrs);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ LdapContext ctx = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ ctx.createSubcontext(dn, attrs).close();
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ }
+
+
+ /**
+ * This will rename the supplied dn in the LDAP namespace. See {@link
+ * javax.naming.Context#rename(String, String)}.
+ *
+ * @param oldDn <code>String</code> object to rename
+ * @param newDn <code>String</code> new name
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected void rename(final String oldDn, final String newDn)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Rename name with the following parameters:");
+ this.logger.debug(" oldDn = " + oldDn);
+ this.logger.debug(" newDn = " + newDn);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ LdapContext ctx = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ ctx.rename(oldDn, newDn);
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ }
+
+
+ /**
+ * This will delete the supplied dn from the LDAP namespace. Note that this
+ * method does not throw NameNotFoundException if the supplied dn does not
+ * exist. See {@link javax.naming.Context#destroySubcontext(String)}.
+ *
+ * @param dn <code>String</code> named object in the LDAP
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected void delete(final String dn)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Delete name with the following parameters:");
+ this.logger.debug(" dn = " + dn);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" config = " + this.config.getEnvironment());
+ }
+ }
+
+ LdapContext ctx = null;
+ try {
+ for (
+ int i = 0;
+ i <= this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1;
+ i++) {
+ try {
+ ctx = this.getContext();
+ ctx.destroySubcontext(dn);
+ break;
+ } catch (NamingException e) {
+ this.operationRetry(ctx, e, i);
+ }
+ }
+ } finally {
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+ }
+
+
+ /**
+ * This will establish a connection if one does not already exist by binding
+ * to the LDAP using parameters given by {@link LdapConfig#getBindDn()} and
+ * {@link LdapConfig#getBindCredential()}. If these parameters have not been
+ * set then an anonymous bind will be attempted. This connection must be
+ * closed using {@link #close}. Any method which requires an LDAP connection
+ * will call this method independently. This method should only be used if you
+ * need to verify that you can connect to the LDAP.
+ *
+ * @return <code>boolean</code> - whether the connection was successful
+ *
+ * @throws NamingException if the LDAP cannot be reached
+ */
+ public synchronized boolean connect()
+ throws NamingException
+ {
+ boolean success = false;
+ if (this.connectionHandler == null) {
+ this.connectionHandler = this.config.getConnectionHandler().newInstance();
+ }
+ if (this.connectionHandler.isConnected()) {
+ success = true;
+ } else {
+ this.connectionHandler.connect(
+ this.config.getBindDn(),
+ this.config.getBindCredential());
+ success = true;
+ }
+ return success;
+ }
+
+
+ /**
+ * This will close the current connection to the LDAP and establish a new
+ * connection to the LDAP using {@link #connect}.
+ *
+ * @return <code>boolean</code> - whether the connection was successful
+ *
+ * @throws NamingException if the LDAP cannot be reached
+ */
+ public synchronized boolean reconnect()
+ throws NamingException
+ {
+ this.close();
+ return this.connect();
+ }
+
+
+ /** This will close the connection to the LDAP. */
+ public synchronized void close()
+ {
+ if (this.connectionHandler != null) {
+ try {
+ this.connectionHandler.close();
+ } catch (NamingException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("Error closing connection with the LDAP", e);
+ }
+ } finally {
+ this.connectionHandler = null;
+ }
+ }
+ }
+
+
+ /**
+ * This will return an initialized connection to the LDAP.
+ *
+ * @return <code>LdapContext</code>
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected LdapContext getContext()
+ throws NamingException
+ {
+ this.connect();
+ if (
+ this.connectionHandler != null &&
+ this.connectionHandler.isConnected()) {
+ return this.connectionHandler.getLdapContext().newInstance(null);
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Confirms whether the supplied exception matches an exception from {@link
+ * LdapConfig#getOperationRetryExceptions()} and the supplied count is less
+ * than {@link LdapConfig#getOperationRetry()}. {@link
+ * LdapConfig#getOperationRetryWait()} is used in conjunction with {@link
+ * LdapConfig#getOperationRetryBackoff()} to delay retries. Calls {@link
+ * #close()} if no exception is thrown, which allows the client to reconnect
+ * when the operation is performed again.
+ *
+ * @param ctx <code>LdapContext</code> that performed the operation
+ * @param e <code>NamingException</code> that was thrown
+ * @param count <code>int</code> operation attempts
+ *
+ * @throws NamingException if the operation won't be retried
+ */
+ protected void operationRetry(
+ final LdapContext ctx,
+ final NamingException e,
+ final int count)
+ throws NamingException
+ {
+ boolean ignoreException = false;
+ final Class<?>[] ignore = this.config.getOperationRetryExceptions();
+ if (ignore != null && ignore.length > 0) {
+ for (Class<?> ne : ignore) {
+ if (ne.isInstance(e)) {
+ ignoreException = true;
+ break;
+ }
+ }
+ }
+ if (
+ ignoreException &&
+ (count < this.config.getOperationRetry() ||
+ this.config.getOperationRetry() == -1)) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn(
+ "Error performing LDAP operation, " +
+ "retrying (attempt " + count + ")",
+ e);
+ }
+ if (ctx != null) {
+ ctx.close();
+ }
+ this.close();
+ if (this.config.getOperationRetryWait() > 0) {
+ long sleepTime = this.config.getOperationRetryWait();
+ if (this.config.getOperationRetryBackoff() > 0 && count > 0) {
+ sleepTime = sleepTime * this.config.getOperationRetryBackoff() *
+ count;
+ }
+ try {
+ Thread.sleep(sleepTime);
+ } catch (InterruptedException ie) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Operation retry wait interrupted", e);
+ }
+ }
+ }
+ } else {
+ throw e;
+ }
+ }
+
+
+ /**
+ * Provides a descriptive string representation of this instance.
+ *
+ * @return String of the form $Classname at hashCode::config=$config.
+ */
+ @Override
+ public String toString()
+ {
+ return
+ String.format(
+ "%s@%d::config=%s",
+ this.getClass().getName(),
+ this.hashCode(),
+ this.config);
+ }
+
+
+ /**
+ * Called by the garbage collector on an object when garbage collection
+ * determines that there are no more references to the object.
+ *
+ * @throws Throwable if an exception is thrown by this method
+ */
+ protected void finalize()
+ throws Throwable
+ {
+ try {
+ this.close();
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/AttributesFactory.java b/src/main/java/edu/vt/middleware/ldap/AttributesFactory.java
new file mode 100644
index 0000000..9db5830
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/AttributesFactory.java
@@ -0,0 +1,192 @@
+/*
+ $Id: AttributesFactory.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+
+/**
+ * <code>AttributesFactory</code> provides convenience methods for creating
+ * <code>Attributes</code> and <code>Attribute</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class AttributesFactory
+{
+
+ /** Default constructor. */
+ private AttributesFactory() {}
+
+
+ /**
+ * Creates a new <code>Attributes</code> with the supplied name. Attributes
+ * will be case-insensitive.
+ *
+ * @param name of the attribute
+ *
+ * @return <code>Attributes</code>
+ */
+ public static Attributes createAttributes(final String name)
+ {
+ return createAttributes(name, LdapConstants.DEFAULT_IGNORE_CASE);
+ }
+
+
+ /**
+ * Creates a new <code>Attributes</code> with the supplied name.
+ *
+ * @param name of the attribute
+ * @param ignoreCase whether to ignore the case of attribute values
+ *
+ * @return <code>Attributes</code>
+ */
+ public static Attributes createAttributes(
+ final String name,
+ final boolean ignoreCase)
+ {
+ return createAttributes(name, null, ignoreCase);
+ }
+
+
+ /**
+ * Creates a new <code>Attributes</code> with the supplied name and value.
+ * Attributes will be case-insensitive.
+ *
+ * @param name of the attribute
+ * @param value of the attribute
+ *
+ * @return <code>Attributes</code>
+ */
+ public static Attributes createAttributes(
+ final String name,
+ final Object value)
+ {
+ return createAttributes(name, value, LdapConstants.DEFAULT_IGNORE_CASE);
+ }
+
+
+ /**
+ * Creates a new <code>Attributes</code> with the supplied name and value.
+ *
+ * @param name of the attribute
+ * @param value of the attribute
+ * @param ignoreCase whether to ignore the case of attribute values
+ *
+ * @return <code>Attributes</code>
+ */
+ public static Attributes createAttributes(
+ final String name,
+ final Object value,
+ final boolean ignoreCase)
+ {
+ if (value == null) {
+ return createAttributes(name, null, ignoreCase);
+ } else {
+ return createAttributes(name, new Object[] {value}, ignoreCase);
+ }
+ }
+
+
+ /**
+ * Creates a new <code>Attributes</code> with the supplied name and values.
+ * Attributes will be case-insensitive.
+ *
+ * @param name of the attribute
+ * @param values of the attribute
+ *
+ * @return <code>Attributes</code>
+ */
+ public static Attributes createAttributes(
+ final String name,
+ final Object[] values)
+ {
+ return createAttributes(name, values, LdapConstants.DEFAULT_IGNORE_CASE);
+ }
+
+
+ /**
+ * Creates a new <code>Attributes</code> with the supplied name and values.
+ *
+ * @param name of the attribute
+ * @param values of the attribute
+ * @param ignoreCase whether to ignore the case of attribute values
+ *
+ * @return <code>Attributes</code>
+ */
+ public static Attributes createAttributes(
+ final String name,
+ final Object[] values,
+ final boolean ignoreCase)
+ {
+ final Attributes attrs = new BasicAttributes(ignoreCase);
+ attrs.put(createAttribute(name, values));
+ return attrs;
+ }
+
+
+ /**
+ * Creates a new <code>Attribute</code> with the supplied name.
+ *
+ * @param name of the attribute
+ *
+ * @return <code>Attribute</code>
+ */
+ public static Attribute createAttribute(final String name)
+ {
+ return createAttribute(name, null);
+ }
+
+
+ /**
+ * Creates a new <code>Attribute</code> with the supplied name and value.
+ *
+ * @param name of the attribute
+ * @param value of the attribute
+ *
+ * @return <code>Attribute</code>
+ */
+ public static Attribute createAttribute(final String name, final Object value)
+ {
+ if (value == null) {
+ return createAttribute(name, null);
+ } else {
+ return createAttribute(name, new Object[] {value});
+ }
+ }
+
+
+ /**
+ * Creates a new <code>Attribute</code> with the supplied name and values.
+ *
+ * @param name of the attribute
+ * @param values of the attribute
+ *
+ * @return <code>Attribute</code>
+ */
+ public static Attribute createAttribute(
+ final String name,
+ final Object[] values)
+ {
+ final Attribute attr = new BasicAttribute(name);
+ if (values != null) {
+ for (Object o : values) {
+ attr.add(o);
+ }
+ }
+ return attr;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/BaseLdap.java b/src/main/java/edu/vt/middleware/ldap/BaseLdap.java
new file mode 100644
index 0000000..ce1c87a
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/BaseLdap.java
@@ -0,0 +1,52 @@
+/*
+ $Id: BaseLdap.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import javax.naming.NamingException;
+
+/**
+ * <code>BaseLdap</code> provides a base interface for all ldap implementations.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface BaseLdap
+{
+
+
+ /**
+ * This will establish a connection to the ldap.
+ *
+ * @return <code>boolean</code> - whether the connection was successfull
+ *
+ * @throws NamingException if the LDAP cannot be reached
+ */
+ boolean connect()
+ throws NamingException;
+
+
+ /**
+ * This will close the connection to the LDAP and establish a new connection.
+ *
+ * @return <code>boolean</code> - whether the connection was successfull
+ *
+ * @throws NamingException if the LDAP cannot be reached
+ */
+ boolean reconnect()
+ throws NamingException;
+
+
+ /** This will close the connection to the LDAP. */
+ void close();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/Ldap.java b/src/main/java/edu/vt/middleware/ldap/Ldap.java
new file mode 100644
index 0000000..d05a231
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/Ldap.java
@@ -0,0 +1,747 @@
+/*
+ $Id: Ldap.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.Iterator;
+import javax.naming.Binding;
+import javax.naming.NameClassPair;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.handler.AttributeHandler;
+import edu.vt.middleware.ldap.handler.ExtendedAttributeHandler;
+import edu.vt.middleware.ldap.handler.ExtendedSearchResultHandler;
+import edu.vt.middleware.ldap.handler.SearchCriteria;
+import edu.vt.middleware.ldap.handler.SearchResultHandler;
+
+/**
+ * <code>Ldap</code> contains functions for basic interaction with an LDAP.
+ * Methods are provided for connecting, binding, querying and updating.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class Ldap extends AbstractLdap<LdapConfig> implements Serializable
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -2715321533384426365L;
+
+ /**
+ * Enum to define the type of attribute modification. See {@link
+ * javax.naming.directory.DirContext}.
+ */
+ public enum AttributeModification {
+
+ /** add an attribute. */
+ ADD(DirContext.ADD_ATTRIBUTE),
+
+ /** replace an attribute. */
+ REPLACE(DirContext.REPLACE_ATTRIBUTE),
+
+ /** remove an attribute. */
+ REMOVE(DirContext.REMOVE_ATTRIBUTE);
+
+
+ /** underlying modification operation integer. */
+ private int modOp;
+
+
+ /**
+ * Creates a new <code>AttributeModification</code> with the supplied
+ * integer.
+ *
+ * @param i modification operation
+ */
+ AttributeModification(final int i)
+ {
+ this.modOp = i;
+ }
+
+
+ /**
+ * Returns the modification operation integer.
+ *
+ * @return <code>int</code>
+ */
+ public int modOp()
+ {
+ return this.modOp;
+ }
+
+
+ /**
+ * Method to convert a JNDI constant value to an enum. Returns null if the
+ * supplied constant does not match a valid value.
+ *
+ * @param i modification operation
+ *
+ * @return attribute modification
+ */
+ public static AttributeModification parseModificationOperation(final int i)
+ {
+ AttributeModification am = null;
+ if (ADD.modOp() == i) {
+ am = ADD;
+ } else if (REPLACE.modOp() == i) {
+ am = REPLACE;
+ } else if (REMOVE.modOp() == i) {
+ am = REMOVE;
+ }
+ return am;
+ }
+ }
+
+ /** Default constructor. */
+ public Ldap() {}
+
+
+ /**
+ * This will create a new <code>Ldap</code> with the supplied <code>
+ * LdapConfig</code>.
+ *
+ * @param ldapConfig <code>LdapConfig</code>
+ */
+ public Ldap(final LdapConfig ldapConfig)
+ {
+ this.setLdapConfig(ldapConfig);
+ }
+
+
+ /** {@inheritDoc} */
+ public void setLdapConfig(final LdapConfig ldapConfig)
+ {
+ super.setLdapConfig(ldapConfig);
+ }
+
+
+ /**
+ * This returns the <code>LdapConfig</code> of the <code>Ldap</code>.
+ *
+ * @return <code>LdapConfig</code>
+ */
+ public LdapConfig getLdapConfig()
+ {
+ return this.config;
+ }
+
+
+ /**
+ * This will set the config parameters of this <code>Ldap</code> using the
+ * default properties file, which must be located in your classpath.
+ */
+ public void loadFromProperties()
+ {
+ this.setLdapConfig(LdapConfig.createFromProperties(null));
+ }
+
+
+ /**
+ * This will set the config parameters of this <code>Ldap</code> using the
+ * supplied input stream.
+ *
+ * @param is <code>InputStream</code>
+ */
+ public void loadFromProperties(final InputStream is)
+ {
+ this.setLdapConfig(LdapConfig.createFromProperties(is));
+ }
+
+
+ /**
+ * This will perform an LDAP compare operation with the supplied filter.
+ * {@link LdapConfig#getBaseDn()} is used as the dn to compare. See {@link
+ * #compare(String, SearchFilter)}.
+ *
+ * @param filter <code>SearchFilter</code> expression to use for compare
+ *
+ * @return <code>boolean</code> - result of compare operation
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public boolean compare(final SearchFilter filter)
+ throws NamingException
+ {
+ return this.compare(this.config.getBaseDn(), filter);
+ }
+
+
+ /**
+ * This will perform an LDAP compare operation with the supplied filter and
+ * dn.
+ *
+ * @param dn <code>String</code> name to compare
+ * @param filter <code>SearchFilter</code> expression to use for compare
+ *
+ * @return <code>boolean</code> - result of compare operation
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public boolean compare(final String dn, final SearchFilter filter)
+ throws NamingException
+ {
+ return
+ super.compare(dn, filter.getFilter(), filter.getFilterArgs().toArray());
+ }
+
+
+ /**
+ * This will query the LDAP with the supplied filter. All attributes will be
+ * returned. {@link LdapConfig#getBaseDn()} is used as the start point for
+ * searching. Search controls will be created from {@link
+ * LdapConfig#getSearchControls(String[])}. See {@link
+ * #search(String,SearchFilter,String[])}.
+ *
+ * @param filter <code>SearchFilter</code> expression to use for the search
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> search(final SearchFilter filter)
+ throws NamingException
+ {
+ return
+ this.search(
+ this.config.getBaseDn(),
+ filter,
+ this.config.getSearchControls(null));
+ }
+
+
+ /**
+ * This will query the LDAP with the supplied filter and return attributes.
+ * {@link LdapConfig#getBaseDn()} is used as the start point for searching.
+ * Search controls will be created from {@link
+ * LdapConfig#getSearchControls(String[])}. See {@link
+ * #search(String,SearchFilter,String[])}.
+ *
+ * @param filter <code>SearchFilter</code> expression to use for the search
+ * @param retAttrs <code>String[]</code> attributes to return
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> search(
+ final SearchFilter filter,
+ final String[] retAttrs)
+ throws NamingException
+ {
+ return
+ this.search(
+ this.config.getBaseDn(),
+ filter,
+ this.config.getSearchControls(retAttrs));
+ }
+
+
+ /**
+ * This will query the LDAP with the supplied filter and search controls.
+ * {@link LdapConfig#getBaseDn()} is used as the start point for searching.
+ * See {@link #search(String,SearchFilter,SearchControls)}.
+ *
+ * @param filter <code>SearchFilter</code> expression to use for the search
+ * @param searchControls <code>SearchControls</code> to search with
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> search(
+ final SearchFilter filter,
+ final SearchControls searchControls)
+ throws NamingException
+ {
+ return this.search(this.config.getBaseDn(), filter, searchControls);
+ }
+
+
+ /**
+ * This will query the LDAP with the supplied dn and filter. All attributes
+ * will be returned. Search controls will be created from {@link
+ * LdapConfig#getSearchControls(String[])}. See {@link
+ * #search(String,SearchFilter,String[])}.
+ *
+ * @param dn <code>String</code> name to begin search at
+ * @param filter <code>SearchFilter</code> expression to use for the search
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> search(
+ final String dn,
+ final SearchFilter filter)
+ throws NamingException
+ {
+ return this.search(dn, filter, this.config.getSearchControls(null));
+ }
+
+
+ /**
+ * This will query the LDAP with the supplied dn, filter, and return
+ * attributes. Search controls will be created from {@link
+ * LdapConfig#getSearchControls(String[])}. See {@link
+ * #search(String,SearchFilter,SearchControls,SearchResultHandler[])}.
+ *
+ * @param dn <code>String</code> name to begin search at
+ * @param filter <code>SearchFilter</code> expression to use for the search
+ * @param retAttrs <code>String[]</code> attributes to return
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> search(
+ final String dn,
+ final SearchFilter filter,
+ final String[] retAttrs)
+ throws NamingException
+ {
+ return
+ this.search(
+ dn,
+ filter,
+ this.config.getSearchControls(retAttrs),
+ this.config.getSearchResultHandlers());
+ }
+
+
+ /**
+ * This will query the LDAP with the supplied dn, filter, and search controls.
+ * See {@link
+ * #search(String,SearchFilter,SearchControls,SearchResultHandler[])}.
+ *
+ * @param dn <code>String</code> name to begin search at
+ * @param filter <code>SearchFilter</code> expression to use for the search
+ * @param searchControls <code>SearchControls</code> to search with
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> search(
+ final String dn,
+ final SearchFilter filter,
+ final SearchControls searchControls)
+ throws NamingException
+ {
+ return
+ this.search(
+ dn,
+ filter,
+ searchControls,
+ this.config.getSearchResultHandlers());
+ }
+
+
+ /**
+ * This will query the LDAP with the supplied dn, filter, return attributes,
+ * and search result handler. Search controls will be created from {@link
+ * LdapConfig#getSearchControls(String[])}. See {@link #search(
+ * String,SearchFilter,SearchControls,SearchResultHandler...)}.
+ *
+ * @param dn <code>String</code> name to begin search at
+ * @param filter <code>SearchFilter</code> expression to use for the search
+ * @param retAttrs <code>String[]</code> attributes to return
+ * @param handler <code>SearchResultHandler[]</code> of handlers to execute
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> search(
+ final String dn,
+ final SearchFilter filter,
+ final String[] retAttrs,
+ final SearchResultHandler... handler)
+ throws NamingException
+ {
+ return
+ this.search(dn, filter, this.config.getSearchControls(retAttrs), handler);
+ }
+
+
+ /**
+ * This will query the LDAP with the supplied dn, filter, search controls, and
+ * search result handler. If {@link LdapConfig#getPagedResultsSize()} is
+ * greater than 0, the PagedResultsControl will be invoked. See {@link
+ * AbstractLdap
+ * #search(String,String,Object[],SearchControls,SearchResultHandler[])}.
+ *
+ * @param dn <code>String</code> name to begin search at
+ * @param filter <code>SearchFilter</code> expression to use for the search
+ * @param searchControls <code>SearchControls</code> to search with
+ * @param handler <code>SearchResultHandler[]</code> of handlers to execute
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> search(
+ final String dn,
+ final SearchFilter filter,
+ final SearchControls searchControls,
+ final SearchResultHandler... handler)
+ throws NamingException
+ {
+ if (handler != null && handler.length > 0) {
+ for (SearchResultHandler h : handler) {
+ if (ExtendedSearchResultHandler.class.isInstance(h)) {
+ ((ExtendedSearchResultHandler) h).setSearchResultLdap(this);
+ }
+
+ final AttributeHandler[] attrHandler = h.getAttributeHandler();
+ if (attrHandler != null && attrHandler.length > 0) {
+ for (AttributeHandler ah : attrHandler) {
+ if (ExtendedAttributeHandler.class.isInstance(ah)) {
+ ((ExtendedAttributeHandler) ah).setSearchResultLdap(this);
+ }
+ }
+ }
+ }
+ }
+ if (this.config.getPagedResultsSize() > 0) {
+ return
+ super.pagedSearch(
+ dn,
+ filter.getFilter(),
+ filter.getFilterArgs().toArray(),
+ searchControls,
+ handler);
+ } else {
+ return
+ super.search(
+ dn,
+ filter.getFilter(),
+ filter.getFilterArgs().toArray(),
+ searchControls,
+ handler);
+ }
+ }
+
+
+ /**
+ * This will query the LDAP for the supplied matching attributes. All
+ * attributes will be returned. {@link LdapConfig#getBaseDn()} is used as the
+ * name to search.
+ * See {@link #searchAttributes(String, Attributes, String[])}.
+ *
+ * @param matchAttrs <code>Attributes</code> attributes to match
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> searchAttributes(final Attributes matchAttrs)
+ throws NamingException
+ {
+ return this.searchAttributes(this.config.getBaseDn(), matchAttrs, null);
+ }
+
+
+ /**
+ * This will query the LDAP for the supplied matching attributes and return
+ * attributes. {@link LdapConfig#getBaseDn()} is used as the name to search.
+ * See {@link #searchAttributes(String, Attributes, String[])}.
+ *
+ * @param matchAttrs <code>Attributes</code> attributes to match
+ * @param retAttrs <code>String[]</code> attributes to return
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> searchAttributes(
+ final Attributes matchAttrs,
+ final String[] retAttrs)
+ throws NamingException
+ {
+ return this.searchAttributes(this.config.getBaseDn(), matchAttrs, retAttrs);
+ }
+
+
+ /**
+ * This will query the LDAP for the supplied dn and matching attributes. All
+ * attributes will be returned. See {@link #searchAttributes(String,
+ * Attributes, String[])}.
+ *
+ * @param dn <code>String</code> name to search in
+ * @param matchAttrs <code>Attributes</code> attributes to match
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> searchAttributes(
+ final String dn,
+ final Attributes matchAttrs)
+ throws NamingException
+ {
+ return this.searchAttributes(dn, matchAttrs, null);
+ }
+
+
+ /**
+ * This will query the LDAP for the supplied dn, matching attributes and
+ * return attributes. See {@link #searchAttributes( String, Attributes,
+ * String[], SearchResultHandler[])}. This method converts relative DNs to
+ * fully qualified DNs, no post processing is required
+ *
+ * @param dn <code>String</code> name to search in
+ * @param matchAttrs <code>Attributes</code> attributes to match
+ * @param retAttrs <code>String[]</code> attributes to return
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Iterator<SearchResult> searchAttributes(
+ final String dn,
+ final Attributes matchAttrs,
+ final String[] retAttrs)
+ throws NamingException
+ {
+ return
+ this.searchAttributes(
+ dn,
+ matchAttrs,
+ retAttrs,
+ this.config.getSearchResultHandlers());
+ }
+
+
+ /** {@inheritDoc} */
+ public Iterator<SearchResult> searchAttributes(
+ final String dn,
+ final Attributes matchAttrs,
+ final String[] retAttrs,
+ final SearchResultHandler... handler)
+ throws NamingException
+ {
+ if (handler != null && handler.length > 0) {
+ for (SearchResultHandler h : handler) {
+ if (ExtendedSearchResultHandler.class.isInstance(h)) {
+ ((ExtendedSearchResultHandler) h).setSearchResultLdap(this);
+ }
+
+ final AttributeHandler[] attrHandler = h.getAttributeHandler();
+ if (attrHandler != null && attrHandler.length > 0) {
+ for (AttributeHandler ah : attrHandler) {
+ if (ExtendedAttributeHandler.class.isInstance(ah)) {
+ ((ExtendedAttributeHandler) ah).setSearchResultLdap(this);
+ }
+ }
+ }
+ }
+ }
+ return super.searchAttributes(dn, matchAttrs, retAttrs, handler);
+ }
+
+
+ /** {@inheritDoc} */
+ public Iterator<NameClassPair> list(final String dn)
+ throws NamingException
+ {
+ return super.list(dn);
+ }
+
+
+ /** {@inheritDoc} */
+ public Iterator<Binding> listBindings(final String dn)
+ throws NamingException
+ {
+ return super.listBindings(dn);
+ }
+
+
+ /**
+ * This will return all the attributes associated with the supplied dn. See
+ * {@link #getAttributes(String, String[])}.
+ *
+ * @param dn <code>String</code> named object in the LDAP
+ *
+ * @return <code>Attributes</code>
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Attributes getAttributes(final String dn)
+ throws NamingException
+ {
+ return this.getAttributes(dn, null);
+ }
+
+
+ /**
+ * This will return the matching attributes associated with the supplied dn.
+ * If retAttrs is null then all attributes will be returned. If retAttrs is an
+ * empty array then no attributes will be returned. See {@link
+ * #getAttributes(String, String[], AttributeHandler[])}.
+ *
+ * @param dn <code>String</code> named object in the LDAP
+ * @param retAttrs <code>String[]</code> attributes to return
+ *
+ * @return <code>Attributes</code>
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public Attributes getAttributes(final String dn, final String[] retAttrs)
+ throws NamingException
+ {
+ return this.getAttributes(dn, retAttrs, new AttributeHandler[0]);
+ }
+
+
+ /** {@inheritDoc} */
+ public Attributes getAttributes(
+ final String dn,
+ final String[] retAttrs,
+ final AttributeHandler... handler)
+ throws NamingException
+ {
+ if (handler != null && handler.length > 0) {
+ for (AttributeHandler h : handler) {
+ if (ExtendedAttributeHandler.class.isInstance(h)) {
+ ((ExtendedAttributeHandler) h).setSearchResultLdap(this);
+ }
+ }
+ }
+ return super.getAttributes(dn, retAttrs, handler);
+ }
+
+
+ /** {@inheritDoc} */
+ public Iterator<SearchResult> getSchema(final String dn)
+ throws NamingException
+ {
+ return super.getSchema(dn);
+ }
+
+
+ /**
+ * This will modify the supplied attributes for the supplied value given by
+ * the modification operation. See {@link
+ * AbstractLdap#modifyAttributes(String, int, Attributes)}.
+ *
+ * @param dn <code>String</code> named object in the LDAP
+ * @param mod <code>AttributeModification</code> modification operation
+ * @param attrs <code>Attributes</code> attributes to be used for the
+ * operation, may be null
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public void modifyAttributes(
+ final String dn,
+ final AttributeModification mod,
+ final Attributes attrs)
+ throws NamingException
+ {
+ super.modifyAttributes(dn, mod.modOp(), attrs);
+ }
+
+
+ /** {@inheritDoc} */
+ public void modifyAttributes(final String dn, final ModificationItem[] mods)
+ throws NamingException
+ {
+ super.modifyAttributes(dn, mods);
+ }
+
+
+ /** {@inheritDoc} */
+ public void create(final String dn, final Attributes attrs)
+ throws NamingException
+ {
+ super.create(dn, attrs);
+ }
+
+
+ /** {@inheritDoc} */
+ public void rename(final String oldDn, final String newDn)
+ throws NamingException
+ {
+ super.rename(oldDn, newDn);
+ }
+
+
+ /** {@inheritDoc} */
+ public void delete(final String dn)
+ throws NamingException
+ {
+ super.delete(dn);
+ }
+
+
+ /**
+ * This will return a list of SASL mechanisms that this LDAP supports.
+ *
+ * @return <code>String[]</code> - supported SASL mechanisms
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public String[] getSaslMechanisms()
+ throws NamingException
+ {
+ final Attributes attrs = this.getAttributes(
+ "",
+ new String[] {LdapConstants.SUPPORTED_SASL_MECHANISMS});
+
+ String[] results = new String[0];
+ if (attrs != null) {
+ final Attribute attr = attrs.get(LdapConstants.SUPPORTED_SASL_MECHANISMS);
+ if (attr != null) {
+ results = (String[]) COPY_RESULT_HANDLER.process(
+ new SearchCriteria(""),
+ attr.getAll()).toArray(results);
+ }
+ }
+
+ return results;
+ }
+
+
+ /**
+ * This will return a list of controls that this LDAP supports.
+ *
+ * @return <code>String[]</code> - supported controls
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public String[] getSupportedControls()
+ throws NamingException
+ {
+ final Attributes attrs = this.getAttributes(
+ "",
+ new String[] {LdapConstants.SUPPORTED_CONTROL});
+
+ String[] results = new String[0];
+ if (attrs != null) {
+ final Attribute attr = attrs.get(LdapConstants.SUPPORTED_CONTROL);
+ if (attr != null) {
+ results = (String[]) COPY_RESULT_HANDLER.process(
+ new SearchCriteria(""),
+ attr.getAll()).toArray(results);
+ }
+ }
+
+ return results;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/LdapCli.java b/src/main/java/edu/vt/middleware/ldap/LdapCli.java
new file mode 100644
index 0000000..0918779
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/LdapCli.java
@@ -0,0 +1,179 @@
+/*
+ $Id: LdapCli.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.Iterator;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.dsml.Dsmlv1;
+import edu.vt.middleware.ldap.dsml.Dsmlv2;
+import edu.vt.middleware.ldap.ldif.Ldif;
+import edu.vt.middleware.ldap.props.LdapConfigPropertyInvoker;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+/**
+ * Command line interface for ldap operations.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class LdapCli extends AbstractCli
+{
+
+ /** Option for ldap query. */
+ protected static final String OPT_QUERY = "query";
+
+ /** Name of operation provided by this class. */
+ private static final String COMMAND_NAME = "ldapsearch";
+
+
+ /** Default constructor. */
+ public LdapCli()
+ {
+ super();
+ this.opts.add(OPT_QUERY);
+ }
+
+
+ /**
+ * CLI entry point method.
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(final String[] args)
+ {
+ new LdapCli().performAction(args);
+ }
+
+
+ /** {@inheritDoc} */
+ protected void initOptions()
+ {
+ super.initOptions(
+ new LdapConfigPropertyInvoker(
+ LdapConfig.class,
+ LdapConfig.PROPERTIES_DOMAIN));
+
+ options.addOption(new Option(OPT_QUERY, true, ""));
+ }
+
+
+ /**
+ * Initialize an LdapConfig with command line options.
+ *
+ * @param line Parsed command line arguments container.
+ *
+ * @return <code>LdapConfig</code> that has been initialized
+ *
+ * @throws Exception On errors thrown by handler.
+ */
+ protected LdapConfig initLdapConfig(final CommandLine line)
+ throws Exception
+ {
+ final LdapConfig config = new LdapConfig();
+ this.initLdapProperties(config, line);
+ if (line.hasOption(OPT_TRACE)) {
+ config.setTracePackets(System.out);
+ }
+ if (config.getBindDn() != null && config.getBindCredential() == null) {
+ // prompt the user to enter a password
+ System.out.print(
+ "Enter password for service user " + config.getBindDn() + ": ");
+
+ final String pass = (new BufferedReader(new InputStreamReader(System.in)))
+ .readLine();
+ config.setBindCredential(pass);
+ }
+ return config;
+ }
+
+
+ /** {@inheritDoc} */
+ protected void dispatch(final CommandLine line)
+ throws Exception
+ {
+ if (line.hasOption(OPT_DSMLV1)) {
+ this.outputDsmlv1 = true;
+ } else if (line.hasOption(OPT_DSMLV2)) {
+ this.outputDsmlv2 = true;
+ }
+ if (line.hasOption(OPT_HELP)) {
+ printHelp();
+ } else if (line.hasOption(OPT_QUERY)) {
+ search(
+ initLdapConfig(line),
+ line.getOptionValue(OPT_QUERY),
+ line.getArgs());
+ } else {
+ printExamples();
+ }
+ }
+
+
+ /**
+ * Executes the ldap search operation.
+ *
+ * @param config Ldap configuration.
+ * @param filter Ldap filter to search on.
+ * @param attrs Ldap attributes to return.
+ *
+ * @throws Exception On errors.
+ */
+ protected void search(
+ final LdapConfig config,
+ final String filter,
+ final String[] attrs)
+ throws Exception
+ {
+ final Ldap ldap = new Ldap();
+ ldap.setLdapConfig(config);
+
+ try {
+ Iterator<SearchResult> results = null;
+ if (attrs == null || attrs.length == 0) {
+ results = ldap.search(new SearchFilter(filter));
+ } else {
+ results = ldap.search(new SearchFilter(filter), attrs);
+ }
+ if (this.outputDsmlv1) {
+ (new Dsmlv1()).outputDsml(
+ results,
+ new BufferedWriter(new OutputStreamWriter(System.out)));
+ } else if (this.outputDsmlv2) {
+ (new Dsmlv2()).outputDsml(
+ results,
+ new BufferedWriter(new OutputStreamWriter(System.out)));
+ } else {
+ (new Ldif()).outputLdif(
+ results,
+ new BufferedWriter(new OutputStreamWriter(System.out)));
+ }
+ } finally {
+ if (ldap != null) {
+ ldap.close();
+ }
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ protected String getCommandName()
+ {
+ return COMMAND_NAME;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/LdapConfig.java b/src/main/java/edu/vt/middleware/ldap/LdapConfig.java
new file mode 100644
index 0000000..34a3608
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/LdapConfig.java
@@ -0,0 +1,1921 @@
+/*
+ $Id: LdapConfig.java 1786 2011-01-05 14:45:07Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1786 $
+ Updated: $Date: 2011-01-05 14:45:07 +0000 (Wed, 05 Jan 2011) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import javax.naming.CommunicationException;
+import javax.naming.LimitExceededException;
+import javax.naming.ServiceUnavailableException;
+import javax.naming.directory.SearchControls;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+import edu.vt.middleware.ldap.handler.ConnectionHandler;
+import edu.vt.middleware.ldap.handler.DefaultConnectionHandler;
+import edu.vt.middleware.ldap.handler.FqdnSearchResultHandler;
+import edu.vt.middleware.ldap.handler.SearchResultHandler;
+import edu.vt.middleware.ldap.handler.TlsConnectionHandler;
+import edu.vt.middleware.ldap.props.AbstractPropertyConfig;
+import edu.vt.middleware.ldap.props.LdapConfigPropertyInvoker;
+import edu.vt.middleware.ldap.props.LdapProperties;
+
+/**
+ * <code>LdapConfig</code> contains all the configuration data that the <code>
+ * Ldap</code> needs to control connections and searching.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1786 $ $Date: 2011-01-05 14:45:07 +0000 (Wed, 05 Jan 2011) $
+ */
+public class LdapConfig extends AbstractPropertyConfig
+{
+
+
+ /** Domain to look for ldap properties in, value is {@value}. */
+ public static final String PROPERTIES_DOMAIN = "edu.vt.middleware.ldap.";
+
+ /** Invoker for ldap properties. */
+ private static final LdapConfigPropertyInvoker PROPERTIES =
+ new LdapConfigPropertyInvoker(LdapConfig.class, PROPERTIES_DOMAIN);
+
+
+ /**
+ * Enum to define the type of search scope. See {@link
+ * javax.naming.directory.SearchControls}.
+ */
+ public enum SearchScope {
+
+ /** object level search. */
+ OBJECT(SearchControls.OBJECT_SCOPE),
+
+ /** one level search. */
+ ONELEVEL(SearchControls.ONELEVEL_SCOPE),
+
+ /** subtree search. */
+ SUBTREE(SearchControls.SUBTREE_SCOPE);
+
+ /** underlying search scope integer. */
+ private int scope;
+
+
+ /**
+ * Creates a new <code>SearchScope</code> with the supplied integer.
+ *
+ * @param i search scope
+ */
+ SearchScope(final int i)
+ {
+ this.scope = i;
+ }
+
+
+ /**
+ * Returns the search scope integer.
+ *
+ * @return <code>int</code>
+ */
+ public int scope()
+ {
+ return this.scope;
+ }
+
+
+ /**
+ * Method to convert a JNDI constant value to an enum. Returns null if the
+ * supplied constant does not match a known value.
+ *
+ * @param i search scope
+ *
+ * @return search scope
+ */
+ public static SearchScope parseSearchScope(final int i)
+ {
+ SearchScope ss = null;
+ if (OBJECT.scope() == i) {
+ ss = OBJECT;
+ } else if (ONELEVEL.scope() == i) {
+ ss = ONELEVEL;
+ } else if (SUBTREE.scope() == i) {
+ ss = SUBTREE;
+ }
+ return ss;
+ }
+ }
+
+ /** Default context factory. */
+ private String contextFactory = LdapConstants.DEFAULT_CONTEXT_FACTORY;
+
+ /** Default connection handler. */
+ private ConnectionHandler connectionHandler = new DefaultConnectionHandler(
+ this);
+
+ /** Default ldap socket factory used for SSL and TLS. */
+ private SSLSocketFactory sslSocketFactory;
+
+ /** Default hostname verifier for TLS connections. */
+ private HostnameVerifier hostnameVerifier;
+
+ /** URL to the LDAP(s). */
+ private String ldapUrl;
+
+ /** Hostname of the LDAP server. */
+ private String host;
+
+ /** Port the LDAP server is listening on. */
+ private String port = LdapConstants.DEFAULT_PORT;
+
+ /** Amount of time in milliseconds that connect operations will block. */
+ private Integer timeout;
+
+ /** DN to bind as before performing operations. */
+ private String bindDn;
+
+ /** Credential for the bind DN. */
+ private Object bindCredential;
+
+ /** Base dn for LDAP searching. */
+ private String baseDn = LdapConstants.DEFAULT_BASE_DN;
+
+ /** Type of search scope to use, default is subtree. */
+ private SearchScope searchScope = SearchScope.SUBTREE;
+
+ /** Security level to use when binding to the LDAP. */
+ private String authtype = LdapConstants.DEFAULT_AUTHTYPE;
+
+ /** Whether to require the most authoritative source for this service. */
+ private boolean authoritative = LdapConstants.DEFAULT_AUTHORITATIVE;
+
+ /** Preferred batch size to use when returning results. */
+ private Integer batchSize;
+
+ /** Amount of time in milliseconds that search operations will block. */
+ private Integer timeLimit;
+
+ /** Maximum number of entries that search operations will return. */
+ private Long countLimit;
+
+ /** Size of result set when using paged searching. */
+ private Integer pagedResultsSize;
+
+ /** Number of times to retry ldap operations on communication exception. */
+ private Integer operationRetry;
+
+ /** Exception types to retry operations on. */
+ private Class<?>[] operationRetryExceptions = new Class[] {
+ CommunicationException.class,
+ ServiceUnavailableException.class,
+ };
+
+ /** Amount of time in milliseconds to wait before retrying. */
+ private Long operationRetryWait;
+
+ /** Factor to multiply operation retry wait by. */
+ private Integer operationRetryBackoff;
+
+ /** Whether link dereferencing should be performed during the search. */
+ private boolean derefLinkFlag;
+
+ /** Whether objects will be returned in the result. */
+ private boolean returningObjFlag;
+
+ /** DNS host to use for JNDI URL context implementation. */
+ private String dnsUrl;
+
+ /** Preferred language as defined by RFC 1766. */
+ private String language;
+
+ /** How the provider should handle referrals. */
+ private String referral;
+
+ /** How the provider should handle aliases. */
+ private String derefAliases;
+
+ /** Additional attributes that should be considered binary. */
+ private String binaryAttributes;
+
+ /** Handlers to process search results. */
+ private SearchResultHandler[] searchResultHandlers =
+ new SearchResultHandler[] {new FqdnSearchResultHandler()};
+
+ /** Exception types to ignore when handling results. */
+ private Class<?>[] handlerIgnoreExceptions = new Class[] {
+ LimitExceededException.class,
+ };
+
+ /** SASL authorization ID. */
+ private String saslAuthorizationId;
+
+ /** SASL realm. */
+ private String saslRealm;
+
+ /** Whether only attribute type names should be returned. */
+ private boolean typesOnly = LdapConstants.DEFAULT_TYPES_ONLY;
+
+ /** Additional environment properties. */
+ private Map<String, Object> additionalEnvironmentProperties =
+ new HashMap<String, Object>();
+
+ /** Whether to log authentication credentials. */
+ private boolean logCredentials = LdapConstants.DEFAULT_LOG_CREDENTIALS;
+
+ /** Connect to LDAP using SSL protocol. */
+ private boolean ssl = LdapConstants.DEFAULT_USE_SSL;
+
+ /** Stream to print LDAP ASN.1 BER packets. */
+ private PrintStream tracePackets;
+
+
+ /** Default constructor. */
+ public LdapConfig() {}
+
+
+ /**
+ * This will create a new <code>LdapConfig</code> with the supplied ldap url.
+ *
+ * @param ldapUrl <code>String</code> LDAP URL
+ */
+ public LdapConfig(final String ldapUrl)
+ {
+ this();
+ this.setLdapUrl(ldapUrl);
+ }
+
+
+ /**
+ * This will create a new <code>LdapConfig</code> with the supplied ldap url
+ * and base Strings.
+ *
+ * @param ldapUrl <code>String</code> LDAP URL
+ * @param baseDn <code>String</code> LDAP base DN
+ */
+ public LdapConfig(final String ldapUrl, final String baseDn)
+ {
+ this();
+ this.setLdapUrl(ldapUrl);
+ this.setBaseDn(baseDn);
+ }
+
+
+ /**
+ * This returns the Context environment properties that are used to make LDAP
+ * connections.
+ *
+ * @return <code>Hashtable</code> - context environment
+ */
+ public Hashtable<String, ?> getEnvironment()
+ {
+ final Hashtable<String, Object> environment =
+ new Hashtable<String, Object>();
+ environment.put(LdapConstants.CONTEXT_FACTORY, this.contextFactory);
+
+ if (this.authoritative) {
+ environment.put(
+ LdapConstants.AUTHORITATIVE,
+ Boolean.valueOf(this.authoritative).toString());
+ }
+
+ if (this.batchSize != null) {
+ environment.put(LdapConstants.BATCH_SIZE, this.batchSize.toString());
+ }
+
+ if (this.dnsUrl != null) {
+ environment.put(LdapConstants.DNS_URL, this.dnsUrl);
+ }
+
+ if (this.language != null) {
+ environment.put(LdapConstants.LANGUAGE, this.language);
+ }
+
+ if (this.referral != null) {
+ environment.put(LdapConstants.REFERRAL, this.referral);
+ }
+
+ if (this.derefAliases != null) {
+ environment.put(LdapConstants.DEREF_ALIASES, this.derefAliases);
+ }
+
+ if (this.binaryAttributes != null) {
+ environment.put(LdapConstants.BINARY_ATTRIBUTES, this.binaryAttributes);
+ }
+
+ if (this.saslAuthorizationId != null) {
+ environment.put(
+ LdapConstants.SASL_AUTHORIZATION_ID,
+ this.saslAuthorizationId);
+ }
+
+ if (this.saslRealm != null) {
+ environment.put(LdapConstants.SASL_REALM, this.saslRealm);
+ }
+
+ if (this.typesOnly) {
+ environment.put(
+ LdapConstants.TYPES_ONLY,
+ Boolean.valueOf(this.typesOnly).toString());
+ }
+
+ if (this.ssl) {
+ environment.put(LdapConstants.PROTOCOL, LdapConstants.SSL_PROTOCOL);
+ if (this.sslSocketFactory != null) {
+ environment.put(
+ LdapConstants.SOCKET_FACTORY,
+ this.sslSocketFactory.getClass().getName());
+ }
+ }
+
+ if (this.tracePackets != null) {
+ environment.put(LdapConstants.TRACE, this.tracePackets);
+ }
+
+ if (this.ldapUrl != null) {
+ environment.put(LdapConstants.PROVIDER_URL, this.ldapUrl);
+ }
+
+ if (this.timeout != null) {
+ environment.put(LdapConstants.TIMEOUT, this.timeout.toString());
+ }
+
+ if (!this.additionalEnvironmentProperties.isEmpty()) {
+ for (
+ Map.Entry<String, Object> entry :
+ this.additionalEnvironmentProperties.entrySet()) {
+ environment.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ return environment;
+ }
+
+
+ /**
+ * This returns the context factory of the <code>LdapConfig</code>.
+ *
+ * @return <code>String</code> - context factory
+ */
+ public String getContextFactory()
+ {
+ return this.contextFactory;
+ }
+
+
+ /**
+ * This returns the connection handler of the <code>LdapConfig</code>.
+ *
+ * @return <code>ConnectionHandler</code> - connection handler
+ */
+ public ConnectionHandler getConnectionHandler()
+ {
+ return this.connectionHandler;
+ }
+
+
+ /**
+ * This returns the SSL socket factory of the <code>LdapConfig</code>.
+ *
+ * @return <code>SSLSocketFactory</code> - SSL socket factory
+ */
+ public SSLSocketFactory getSslSocketFactory()
+ {
+ return this.sslSocketFactory;
+ }
+
+
+ /**
+ * This returns whether the <code>LdapConfig</code> is using a custom SSL
+ * socket factory.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean useSslSocketFactory()
+ {
+ return this.sslSocketFactory != null;
+ }
+
+
+ /**
+ * This returns the hostname verifier of the <code>LdapConfig</code>.
+ *
+ * @return <code>HostnameVerifier</code> - hostname verifier
+ */
+ public HostnameVerifier getHostnameVerifier()
+ {
+ return this.hostnameVerifier;
+ }
+
+
+ /**
+ * This returns whether the <code>LdapConfig</code> is using a custom hostname
+ * verifier.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean useHostnameVerifier()
+ {
+ return this.hostnameVerifier != null;
+ }
+
+
+ /**
+ * This returns the ldap url of the <code>LdapConfig</code>.
+ *
+ * @return <code>String</code> - ldap url
+ */
+ public String getLdapUrl()
+ {
+ return this.ldapUrl;
+ }
+
+
+ /**
+ * This returns the hostname of the <code>LdapConfig</code>.
+ *
+ * @return <code>String</code> - hostname
+ *
+ * @deprecated use {@link #getLdapUrl()} instead
+ */
+ @Deprecated
+ public String getHost()
+ {
+ return this.host;
+ }
+
+
+ /**
+ * This returns the port of the <code>LdapConfig</code>.
+ *
+ * @return <code>String</code> - port
+ *
+ * @deprecated use {@link #getLdapUrl()} instead
+ */
+ @Deprecated
+ public String getPort()
+ {
+ return this.port;
+ }
+
+
+ /**
+ * This returns the timeout for the <code>LdapConfig</code>. If this value is
+ * 0, then connect operations will wait indefinitely.
+ *
+ * @return <code>int</code> - timeout
+ */
+ public int getTimeout()
+ {
+ int time = LdapConstants.DEFAULT_TIMEOUT;
+ if (this.timeout != null) {
+ time = this.timeout.intValue();
+ }
+ return time;
+ }
+
+
+ /**
+ * This returns the bind DN.
+ *
+ * @return <code>String</code> - DN to bind as
+ */
+ public String getBindDn()
+ {
+ return this.bindDn;
+ }
+
+
+ /**
+ * This returns the username of the service user.
+ *
+ * @return <code>String</code> - username
+ *
+ * @deprecated use {@link #getBindDn()} instead
+ */
+ @Deprecated
+ public String getServiceUser()
+ {
+ return this.getBindDn();
+ }
+
+
+ /**
+ * This returns the credential used with the bind DN.
+ *
+ * @return <code>Object</code> - bind DN credential
+ */
+ public Object getBindCredential()
+ {
+ return this.bindCredential;
+ }
+
+
+ /**
+ * This returns the credential of the service user.
+ *
+ * @return <code>Object</code> - credential
+ *
+ * @deprecated use {@link #getBindCredential()} instead
+ */
+ @Deprecated
+ public Object getServiceCredential()
+ {
+ return this.getBindCredential();
+ }
+
+
+ /**
+ * This returns the base dn for the <code>LdapConfig</code>.
+ *
+ * @return <code>String</code> - base dn
+ *
+ * @deprecated use {@link #getBaseDn()} instead
+ */
+ public String getBase()
+ {
+ return this.getBaseDn();
+ }
+
+
+ /**
+ * This returns the base dn for the <code>LdapConfig</code>.
+ *
+ * @return <code>String</code> - base dn
+ */
+ public String getBaseDn()
+ {
+ return this.baseDn;
+ }
+
+
+ /**
+ * This returns the search scope for the <code>LdapConfig</code>.
+ *
+ * @return <code>SearchScope</code> - search scope
+ */
+ public SearchScope getSearchScope()
+ {
+ return this.searchScope;
+ }
+
+
+ /**
+ * This returns whether the search scope is set to object.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isObjectSearchScope()
+ {
+ return this.searchScope == SearchScope.OBJECT;
+ }
+
+
+ /**
+ * This returns whether the search scope is set to one level.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isOneLevelSearchScope()
+ {
+ return this.searchScope == SearchScope.ONELEVEL;
+ }
+
+
+ /**
+ * This returns whether the search scope is set to sub tree.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isSubTreeSearchScope()
+ {
+ return this.searchScope == SearchScope.SUBTREE;
+ }
+
+
+ /**
+ * This returns the security level for the <code>LdapConfig</code>.
+ *
+ * @return <code>String</code> - security level
+ */
+ public String getAuthtype()
+ {
+ return this.authtype;
+ }
+
+
+ /**
+ * This returns whether the security authentication context is set to 'none'.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isAnonymousAuth()
+ {
+ return this.authtype.equalsIgnoreCase(LdapConstants.NONE_AUTHTYPE);
+ }
+
+
+ /**
+ * This returns whether the security authentication context is set to
+ * 'simple'.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isSimpleAuth()
+ {
+ return this.authtype.equalsIgnoreCase(LdapConstants.SIMPLE_AUTHTYPE);
+ }
+
+
+ /**
+ * This returns whether the security authentication context is set to
+ * 'strong'.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isStrongAuth()
+ {
+ return this.authtype.equalsIgnoreCase(LdapConstants.STRONG_AUTHTYPE);
+ }
+
+
+ /**
+ * This returns whether the security authentication context will perform a
+ * SASL bind as defined by the supported SASL mechanisms.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isSaslAuth()
+ {
+ boolean authtypeSasl = false;
+ for (String sasl : LdapConstants.SASL_MECHANISMS) {
+ if (this.authtype.equalsIgnoreCase(sasl)) {
+ authtypeSasl = true;
+ break;
+ }
+ }
+ return authtypeSasl;
+ }
+
+
+ /**
+ * This returns whether the security authentication context is set to
+ * 'EXTERNAL'.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isExternalAuth()
+ {
+ return
+ this.authtype.equalsIgnoreCase(LdapConstants.SASL_MECHANISM_EXTERNAL);
+ }
+
+
+ /**
+ * This returns whether the security authentication context is set to
+ * 'DIGEST-MD5'.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isDigestMD5Auth()
+ {
+ return
+ this.authtype.equalsIgnoreCase(LdapConstants.SASL_MECHANISM_DIGEST_MD5);
+ }
+
+
+ /**
+ * This returns whether the security authentication context is set to
+ * 'CRAM-MD5'.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isCramMD5Auth()
+ {
+ return
+ this.authtype.equalsIgnoreCase(LdapConstants.SASL_MECHANISM_CRAM_MD5);
+ }
+
+
+ /**
+ * This returns whether the security authentication context is set to
+ * 'GSSAPI'.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isGSSAPIAuth()
+ {
+ return this.authtype.equalsIgnoreCase(LdapConstants.SASL_MECHANISM_GSS_API);
+ }
+
+
+ /**
+ * See {@link #isAuthoritative()}.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean getAuthoritative()
+ {
+ return this.isAuthoritative();
+ }
+
+
+ /**
+ * This returns whether the <code>LdapConfig</code> is set to require a
+ * authoritative source.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isAuthoritative()
+ {
+ return this.authoritative;
+ }
+
+
+ /**
+ * This returns the time limit for the <code>LdapConfig</code>. If this value
+ * is 0, then search operations will wait indefinitely for an answer.
+ *
+ * @return <code>int</code> - time limit
+ */
+ public int getTimeLimit()
+ {
+ int limit = LdapConstants.DEFAULT_TIME_LIMIT;
+ if (this.timeLimit != null) {
+ limit = this.timeLimit.intValue();
+ }
+ return limit;
+ }
+
+
+ /**
+ * This returns the count limit for the <code>LdapConfig</code>. If this value
+ * is 0, then search operations will return all the results it finds.
+ *
+ * @return <code>long</code> - count limit
+ */
+ public long getCountLimit()
+ {
+ long limit = LdapConstants.DEFAULT_COUNT_LIMIT;
+ if (this.countLimit != null) {
+ limit = this.countLimit.longValue();
+ }
+ return limit;
+ }
+
+
+ /**
+ * This returns the paged results size for the <code>LdapConfig</code>. This
+ * value is used whenever the PagedResultsControl in invoked.
+ *
+ * @return <code>int</code> - page size
+ */
+ public int getPagedResultsSize()
+ {
+ int size = LdapConstants.DEFAULT_PAGED_RESULTS_SIZE;
+ if (this.pagedResultsSize != null) {
+ size = this.pagedResultsSize.intValue();
+ }
+ return size;
+ }
+
+
+ /**
+ * This returns the number of times ldap operations will be retried if a
+ * communication exception occurs. If this value is 0, no retries will occur.
+ *
+ * @return <code>int</code> - retry count
+ */
+ public int getOperationRetry()
+ {
+ int retry = LdapConstants.DEFAULT_OPERATION_RETRY;
+ if (this.operationRetry != null) {
+ retry = this.operationRetry.intValue();
+ }
+ return retry;
+ }
+
+
+ /**
+ * This returns the exception types to retry operations on.
+ *
+ * @return <code>Class[]</code>
+ */
+ public Class<?>[] getOperationRetryExceptions()
+ {
+ return this.operationRetryExceptions;
+ }
+
+
+ /**
+ * This returns the operation retry wait time for the <code>LdapConfig</code>.
+ *
+ * @return <code>int</code> - time limit
+ */
+ public long getOperationRetryWait()
+ {
+ long wait = LdapConstants.DEFAULT_OPERATION_RETRY_WAIT;
+ if (this.operationRetryWait != null) {
+ wait = this.operationRetryWait.intValue();
+ }
+ return wait;
+ }
+
+
+ /**
+ * This returns the factor by which to multiply the operation retry wait time.
+ * This allows clients to progressively delay each retry. The formula for
+ * backoff is (wait * backoff * attempt). So a wait time of 2s with a backoff
+ * of 3 will delay by 6s, then 12s, then 18s, and so forth.
+ *
+ * @return <code>int</code> - backoff factor
+ */
+ public int getOperationRetryBackoff()
+ {
+ int backoff = LdapConstants.DEFAULT_OPERATION_RETRY_BACKOFF;
+ if (this.operationRetryBackoff != null) {
+ backoff = this.operationRetryBackoff.intValue();
+ }
+ return backoff;
+ }
+
+
+ /**
+ * This returns the derefLinkFlag for the <code>LdapConfig</code>.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean getDerefLinkFlag()
+ {
+ return this.derefLinkFlag;
+ }
+
+
+ /**
+ * This returns the returningObjFlag for the <code>LdapConfig</code>.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean getReturningObjFlag()
+ {
+ return this.returningObjFlag;
+ }
+
+
+ /**
+ * This returns the batch size for the <code>LdapConfig</code>. If this value
+ * is -1, then the default provider setting is being used.
+ *
+ * @return <code>int</code> - batch size
+ */
+ public int getBatchSize()
+ {
+ int size = LdapConstants.DEFAULT_BATCH_SIZE;
+ if (this.batchSize != null) {
+ size = this.batchSize.intValue();
+ }
+ return size;
+ }
+
+
+ /**
+ * This returns the dns url for the <code>LdapConfig</code>. If this value is
+ * null, then this property is not being used.
+ *
+ * @return <code>String</code> - dns url
+ */
+ public String getDnsUrl()
+ {
+ return this.dnsUrl;
+ }
+
+
+ /**
+ * This returns the preferred language for the <code>LdapConfig</code>. If
+ * this value is null, then the default provider setting is being used.
+ *
+ * @return <code>String</code> - language
+ */
+ public String getLanguage()
+ {
+ return this.language;
+ }
+
+
+ /**
+ * This returns the referral setting for the <code>LdapConfig</code>. If this
+ * value is null, then the default provider setting is being used.
+ *
+ * @return <code>String</code> - referral
+ */
+ public String getReferral()
+ {
+ return this.referral;
+ }
+
+
+ /**
+ * This returns the alias setting for the <code>LdapConfig</code>. If this
+ * value is null, then the default provider setting is being used.
+ *
+ * @return <code>String</code> - alias
+ */
+ public String getDerefAliases()
+ {
+ return this.derefAliases;
+ }
+
+
+ /**
+ * This returns additional binary attributes for the <code>LdapConfig</code>.
+ * If this value is null, then the default provider setting is being used.
+ *
+ * @return <code>String</code> - binary attributes
+ */
+ public String getBinaryAttributes()
+ {
+ return this.binaryAttributes;
+ }
+
+
+ /**
+ * This returns the handlers to use for processing search results.
+ *
+ * @return <code>SearchResultHandler[]</code>
+ */
+ public SearchResultHandler[] getSearchResultHandlers()
+ {
+ return this.searchResultHandlers;
+ }
+
+
+ /**
+ * This returns the exception types to ignore when handling results.
+ *
+ * @return <code>Class[]</code>
+ */
+ public Class<?>[] getHandlerIgnoreExceptions()
+ {
+ return this.handlerIgnoreExceptions;
+ }
+
+
+ /**
+ * This returns ths SASL authorization id for the <code>LdapConfig</code>.
+ *
+ * @return <code>String</code> - authorization id
+ */
+ public String getSaslAuthorizationId()
+ {
+ return this.saslAuthorizationId;
+ }
+
+
+ /**
+ * This returns ths SASL realm for the <code>LdapConfig</code>.
+ *
+ * @return <code>String</code> - realm
+ */
+ public String getSaslRealm()
+ {
+ return this.saslRealm;
+ }
+
+
+ /**
+ * See {@link #isTypesOnly()}.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean getTypesOnly()
+ {
+ return this.isTypesOnly();
+ }
+
+
+ /**
+ * This returns whether the <code>LdapConfig</code> is set to only return
+ * attribute types.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isTypesOnly()
+ {
+ return this.typesOnly;
+ }
+
+
+ /**
+ * This returns any environment properties that may have been set for the
+ * <code>LdapConfig</code> using {@link
+ * #setEnvironmentProperties(String,String)} that do not represent properties
+ * of this config. The collection returned is unmodifiable.
+ *
+ * @return <code>Map</code> - additional environment properties
+ */
+ public Map<String, Object> getEnvironmentProperties()
+ {
+ return Collections.unmodifiableMap(this.additionalEnvironmentProperties);
+ }
+
+
+ /**
+ * This returns whether authentication credentials will be logged.
+ *
+ * @return <code>boolean</code> - whether authentication credentials will be
+ * logged.
+ */
+ public boolean getLogCredentials()
+ {
+ return this.logCredentials;
+ }
+
+
+ /**
+ * See {@link #isSslEnabled()}.
+ *
+ * @return <code>boolean</code> - whether the SSL protocol is being used
+ */
+ public boolean getSsl()
+ {
+ return this.isSslEnabled();
+ }
+
+
+ /**
+ * This returns whether the <code>LdapConfig</code> is using the SSL protocol
+ * for connections.
+ *
+ * @return <code>boolean</code> - whether the SSL protocol is being used
+ */
+ public boolean isSslEnabled()
+ {
+ return this.ssl;
+ }
+
+
+ /**
+ * See {@link #isTlsEnabled()}.
+ *
+ * @return <code>boolean</code> - whether the TLS protocol is being used
+ */
+ public boolean getTls()
+ {
+ return this.isTlsEnabled();
+ }
+
+
+ /**
+ * This returns whether the <code>LdapConfig</code> is using the TLS protocol
+ * for connections.
+ *
+ * @return <code>boolean</code> - whether the TLS protocol is being used
+ */
+ public boolean isTlsEnabled()
+ {
+ return
+ this.connectionHandler != null &&
+ this.connectionHandler.getClass().isAssignableFrom(
+ TlsConnectionHandler.class);
+ }
+
+
+ /**
+ * This sets the context factory of the <code>LdapConfig</code>.
+ *
+ * @param contextFactory <code>String</code> context factory
+ */
+ public void setContextFactory(final String contextFactory)
+ {
+ checkImmutable();
+ checkStringInput(contextFactory, false);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting contextFactory: " + contextFactory);
+ }
+ this.contextFactory = contextFactory;
+ }
+
+
+ /**
+ * This sets the connection handler of the <code>LdapConfig</code>.
+ *
+ * @param connectionHandler <code>ConnectionHandler</code> connection
+ * handler
+ */
+ public void setConnectionHandler(final ConnectionHandler connectionHandler)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting connectionHandler: " + connectionHandler);
+ }
+ this.connectionHandler = connectionHandler;
+ if (this.connectionHandler != null) {
+ this.connectionHandler.setLdapConfig(this);
+ }
+ }
+
+
+ /**
+ * This sets the SSL socket factory of the <code>LdapConfig</code>.
+ *
+ * @param sslSocketFactory <code>SSLSocketFactory</code> SSL socket factory
+ */
+ public void setSslSocketFactory(final SSLSocketFactory sslSocketFactory)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting sslSocketFactory: " + sslSocketFactory);
+ }
+ this.sslSocketFactory = sslSocketFactory;
+ }
+
+
+ /**
+ * This sets the hostname verifier of the <code>LdapConfig</code>.
+ *
+ * @param hostnameVerifier <code>HostnameVerifier</code> hostname verifier
+ */
+ public void setHostnameVerifier(final HostnameVerifier hostnameVerifier)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting hostnameVerifier: " + hostnameVerifier);
+ }
+ this.hostnameVerifier = hostnameVerifier;
+ }
+
+
+ /**
+ * This sets the ldap url of the <code>LdapConfig</code>.
+ *
+ * @param ldapUrl <code>String</code> url
+ */
+ public void setLdapUrl(final String ldapUrl)
+ {
+ checkImmutable();
+ checkStringInput(ldapUrl, true);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting ldapUrl: " + ldapUrl);
+ }
+ this.ldapUrl = ldapUrl;
+ }
+
+
+ /**
+ * This sets the hostname of the <code>LdapConfig</code>. The host string may
+ * be of the form ldap://host.domain.name:389, host.domain.name:389, or
+ * host.domain.name. Do not use with {@link #setLdapUrl(String)}.
+ *
+ * @param host <code>String</code> hostname
+ *
+ * @deprecated use {@link #setLdapUrl(String)} instead
+ */
+ @Deprecated
+ public void setHost(final String host)
+ {
+ checkImmutable();
+ if (host != null) {
+ final int prefixLength = LdapConstants.PROVIDER_URL_PREFIX.length();
+ final int separatorLength = LdapConstants.PROVIDER_URL_SEPARATOR.length();
+ String h = host;
+
+ // if host contains '://' and there is data after it, remove the scheme
+ if (
+ h.indexOf(LdapConstants.PROVIDER_URL_PREFIX) != -1 &&
+ h.indexOf(LdapConstants.PROVIDER_URL_PREFIX) + prefixLength <
+ h.length()) {
+ final String scheme = h.substring(
+ 0,
+ h.indexOf(LdapConstants.PROVIDER_URL_PREFIX));
+ if (scheme.equalsIgnoreCase(LdapConstants.PROVIDER_URL_SSL_SCHEME)) {
+ this.setSsl(true);
+ this.setPort(LdapConstants.DEFAULT_SSL_PORT);
+ }
+ h = h.substring(
+ h.indexOf(LdapConstants.PROVIDER_URL_PREFIX) + prefixLength,
+ h.length());
+ }
+
+ // if host contains ':' and there is data after it, remove the port
+ if (
+ h.indexOf(LdapConstants.PROVIDER_URL_SEPARATOR) != -1 &&
+ h.indexOf(LdapConstants.PROVIDER_URL_SEPARATOR) + separatorLength <
+ h.length()) {
+ final String p = h.substring(
+ h.indexOf(LdapConstants.PROVIDER_URL_SEPARATOR) + separatorLength,
+ h.length());
+ this.port = p;
+ h = h.substring(0, h.indexOf(LdapConstants.PROVIDER_URL_SEPARATOR));
+ }
+
+ this.host = h;
+ this.setLdapUrl(
+ LdapConstants.PROVIDER_URL_SCHEME + LdapConstants.PROVIDER_URL_PREFIX +
+ this.host + LdapConstants.PROVIDER_URL_SEPARATOR + this.port);
+ }
+ }
+
+
+ /**
+ * This sets the port of the <code>LdapConfig</code>. Do not use with {@link
+ * #setLdapUrl(String)}.
+ *
+ * @param port <code>String</code> port
+ *
+ * @deprecated use {@link #setLdapUrl(String)} instead
+ */
+ @Deprecated
+ public void setPort(final String port)
+ {
+ checkImmutable();
+ this.port = port;
+ if (this.host != null) {
+ this.setLdapUrl(
+ LdapConstants.PROVIDER_URL_SCHEME + LdapConstants.PROVIDER_URL_PREFIX +
+ this.host + LdapConstants.PROVIDER_URL_SEPARATOR + this.port);
+ }
+ }
+
+
+ /**
+ * This sets the maximum amount of time in milliseconds that connect
+ * operations will block.
+ *
+ * @param timeout <code>int</code>
+ */
+ public void setTimeout(final int timeout)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting timeout: " + timeout);
+ }
+ this.timeout = new Integer(timeout);
+ }
+
+
+ /**
+ * This sets the bind DN to authenticate as before performing operations.
+ *
+ * @param dn <code>String</code> bind DN
+ */
+ public void setBindDn(final String dn)
+ {
+ checkImmutable();
+ checkStringInput(dn, true);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting bindDn: " + dn);
+ }
+ this.bindDn = dn;
+ }
+
+
+ /**
+ * This sets the username of the service user. user must be a fully qualified
+ * DN.
+ *
+ * @param user <code>String</code> username
+ *
+ * @deprecated use {@link #setBindDn(String)} instead
+ */
+ @Deprecated
+ public void setServiceUser(final String user)
+ {
+ this.setBindDn(user);
+ }
+
+
+ /**
+ * This sets the credential of the bind DN.
+ *
+ * @param credential <code>Object</code>
+ */
+ public void setBindCredential(final Object credential)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ if (this.getLogCredentials()) {
+ this.logger.trace("setting bindCredential: " + credential);
+ } else {
+ this.logger.trace("setting bindCredential: <suppressed>");
+ }
+ }
+ this.bindCredential = credential;
+ }
+
+
+ /**
+ * This sets the credential of the service user.
+ *
+ * @param credential <code>Object</code>
+ *
+ * @deprecated use {@link #setBindCredential(Object)} instead
+ */
+ @Deprecated
+ public void setServiceCredential(final Object credential)
+ {
+ this.setBindCredential(credential);
+ }
+
+
+ /**
+ * This sets the username and credential of the service user. user must be a
+ * fully qualified DN.
+ *
+ * @param user <code>String</code> service user dn
+ * @param credential <code>Object</code>
+ *
+ * @deprecated use {@link #setBindDn(String)} and {@link
+ * #setBindCredential(Object)} instead
+ */
+ @Deprecated
+ public void setService(final String user, final Object credential)
+ {
+ this.setBindDn(user);
+ this.setBindCredential(credential);
+ }
+
+
+ /**
+ * This sets the base dn for the <code>LdapConfig</code>.
+ *
+ * @param base <code>String</code> base dn
+ *
+ * @deprecated use {@link #setBaseDn(String)}
+ */
+ public void setBase(final String base)
+ {
+ this.setBaseDn(base);
+ }
+
+
+ /**
+ * This sets the base dn for the <code>LdapConfig</code>.
+ *
+ * @param baseDn <code>String</code> base dn
+ */
+ public void setBaseDn(final String baseDn)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting baseDn: " + baseDn);
+ }
+ this.baseDn = baseDn;
+ }
+
+
+ /**
+ * This sets the search scope for the <code>LdapConfig</code>.
+ *
+ * @param searchScope <code>SearchScope</code>
+ */
+ public void setSearchScope(final SearchScope searchScope)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting searchScope: " + searchScope);
+ }
+ this.searchScope = searchScope;
+ }
+
+
+ /**
+ * This sets the security level for the <code>LdapConfig</code>.
+ *
+ * @param authtype <code>String</code> security level
+ */
+ public void setAuthtype(final String authtype)
+ {
+ checkImmutable();
+ checkStringInput(authtype, false);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting authtype: " + authtype);
+ }
+ this.authtype = authtype;
+ }
+
+
+ /**
+ * This specifies whether or not to force this <code>LdapConfig</code> to
+ * require an authoritative source.
+ *
+ * @param authoritative <code>boolean</code>
+ */
+ public void setAuthoritative(final boolean authoritative)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting authoritative: " + authoritative);
+ }
+ this.authoritative = authoritative;
+ }
+
+
+ /**
+ * This sets the maximum amount of time in milliseconds that search operations
+ * will block.
+ *
+ * @param timeLimit <code>int</code>
+ */
+ public void setTimeLimit(final int timeLimit)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting timeLimit: " + timeLimit);
+ }
+ this.timeLimit = new Integer(timeLimit);
+ }
+
+
+ /**
+ * This sets the maximum number of entries that search operations will return.
+ *
+ * @param countLimit <code>long</code>
+ */
+ public void setCountLimit(final long countLimit)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting countLimit: " + countLimit);
+ }
+ this.countLimit = new Long(countLimit);
+ }
+
+
+ /**
+ * This sets the results size to use when the PagedResultsControl is invoked.
+ *
+ * @param pageSize <code>int</code>
+ */
+ public void setPagedResultsSize(final int pageSize)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting pagedResultsSize: " + pageSize);
+ }
+ this.pagedResultsSize = new Integer(pageSize);
+ }
+
+
+ /**
+ * This sets the number of times that ldap operations will be retried if a
+ * communication exception occurs.
+ *
+ * @param operationRetry <code>int</code>
+ */
+ public void setOperationRetry(final int operationRetry)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting operationRetry: " + operationRetry);
+ }
+ this.operationRetry = new Integer(operationRetry);
+ }
+
+
+ /**
+ * This sets the exception types to retry operations on.
+ *
+ * @param exceptions <code>Class[]</code>
+ */
+ public void setOperationRetryExceptions(final Class<?>[] exceptions)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "setting operationRetryExceptions: " + Arrays.toString(exceptions));
+ }
+ this.operationRetryExceptions = exceptions;
+ }
+
+
+ /**
+ * This sets the amount of time in milliseconds that operations should wait
+ * before retrying.
+ *
+ * @param wait <code>long</code>
+ */
+ public void setOperationRetryWait(final long wait)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting operationRetryWait: " + wait);
+ }
+ this.operationRetryWait = new Long(wait);
+ }
+
+
+ /**
+ * This sets the factor by which to multiply the operation retry wait time.
+ * This allows clients to progressively delay each retry. The formula for
+ * backoff is (wait * backoff * attempt). So a wait time of 2s with a backoff
+ * of 3 will delay by 6s, then 12s, then 18s, and so forth.
+ *
+ * @param backoff <code>int</code>
+ */
+ public void setOperationRetryBackoff(final int backoff)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting operationRetryBackoff: " + backoff);
+ }
+ this.operationRetryBackoff = new Integer(backoff);
+ }
+
+
+ /**
+ * This specifies whether or not to force this <code>LdapConfig</code> to link
+ * dereferencing during searches.
+ *
+ * @param derefLinkFlag <code>boolean</code>
+ */
+ public void setDerefLinkFlag(final boolean derefLinkFlag)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting derefLinkFlag: " + derefLinkFlag);
+ }
+ this.derefLinkFlag = derefLinkFlag;
+ }
+
+
+ /**
+ * This specifies whether or not to force this <code>LdapConfig</code> to
+ * return objects for searches.
+ *
+ * @param returningObjFlag <code>boolean</code>
+ */
+ public void setReturningObjFlag(final boolean returningObjFlag)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting returningObjFlag: " + returningObjFlag);
+ }
+ this.returningObjFlag = returningObjFlag;
+ }
+
+
+ /**
+ * This sets the batch size for the <code>LdapConfig</code>. A value of -1
+ * indicates to use the provider default.
+ *
+ * @param batchSize <code>int</code> batch size to use when returning
+ * results
+ */
+ public void setBatchSize(final int batchSize)
+ {
+ checkImmutable();
+ if (batchSize == -1) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting batchSize: " + null);
+ }
+ this.batchSize = null;
+ } else {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting batchSize: " + batchSize);
+ }
+ this.batchSize = new Integer(batchSize);
+ }
+ }
+
+
+ /**
+ * This sets the dns url for the <code>LdapConfig</code>.
+ *
+ * @param dnsUrl <code>String</code>
+ */
+ public void setDnsUrl(final String dnsUrl)
+ {
+ checkImmutable();
+ checkStringInput(dnsUrl, true);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting dnsUrl: " + dnsUrl);
+ }
+ this.dnsUrl = dnsUrl;
+ }
+
+
+ /**
+ * This sets the preferred language for the <code>LdapConfig</code>.
+ *
+ * @param language <code>String</code> defined by RFC 1766
+ */
+ public void setLanguage(final String language)
+ {
+ checkImmutable();
+ checkStringInput(language, true);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting language: " + language);
+ }
+ this.language = language;
+ }
+
+
+ /**
+ * This specifies how the <code>LdapConfig</code> should handle referrals.
+ * referral must be one of: "throw", "ignore", or "follow".
+ *
+ * @param referral <code>String</code>
+ */
+ public void setReferral(final String referral)
+ {
+ checkImmutable();
+ checkStringInput(referral, true);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting referral: " + referral);
+ }
+ this.referral = referral;
+ }
+
+
+ /**
+ * This specifies how the <code>LdapConfig</code> should handle aliases.
+ * derefAliases must be one of: "always", "never", "finding", or "searching".
+ *
+ * @param derefAliases <code>String</code>
+ */
+ public void setDerefAliases(final String derefAliases)
+ {
+ checkImmutable();
+ checkStringInput(derefAliases, true);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting derefAliases: " + derefAliases);
+ }
+ this.derefAliases = derefAliases;
+ }
+
+
+ /**
+ * This specifies additional attributes that should be considered binary.
+ * Attributes should be space delimited.
+ *
+ * @param binaryAttributes <code>String</code>
+ */
+ public void setBinaryAttributes(final String binaryAttributes)
+ {
+ checkImmutable();
+ checkStringInput(binaryAttributes, true);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting binaryAttributes: " + binaryAttributes);
+ }
+ this.binaryAttributes = binaryAttributes;
+ }
+
+
+ /**
+ * This sets the handlers for processing search results.
+ *
+ * @param handlers <code>SearchResultHandler[]</code>
+ */
+ public void setSearchResultHandlers(final SearchResultHandler[] handlers)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "setting searchResultsHandlers: " + Arrays.toString(handlers));
+ }
+ this.searchResultHandlers = handlers;
+ }
+
+
+ /**
+ * This sets the exception types to ignore when handling results.
+ *
+ * @param exceptions <code>Class[]</code>
+ */
+ public void setHandlerIgnoreExceptions(final Class<?>[] exceptions)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "setting handlerIgnoreExceptions: " + Arrays.toString(exceptions));
+ }
+ this.handlerIgnoreExceptions = exceptions;
+ }
+
+
+ /**
+ * This specifies a SASL authorization id.
+ *
+ * @param saslAuthorizationId <code>String</code>
+ */
+ public void setSaslAuthorizationId(final String saslAuthorizationId)
+ {
+ checkImmutable();
+ checkStringInput(saslAuthorizationId, true);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting saslAuthorizationId: " + saslAuthorizationId);
+ }
+ this.saslAuthorizationId = saslAuthorizationId;
+ }
+
+
+ /**
+ * This specifies a SASL realm.
+ *
+ * @param saslRealm <code>String</code>
+ */
+ public void setSaslRealm(final String saslRealm)
+ {
+ checkImmutable();
+ checkStringInput(saslRealm, true);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting saslRealm: " + saslRealm);
+ }
+ this.saslRealm = saslRealm;
+ }
+
+
+ /**
+ * This specifies whether or not to force this <code>LdapConfig</code> to
+ * return only attribute types.
+ *
+ * @param typesOnly <code>boolean</code>
+ */
+ public void setTypesOnly(final boolean typesOnly)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting typesOnly: " + typesOnly);
+ }
+ this.typesOnly = typesOnly;
+ }
+
+
+ /** {@inheritDoc} */
+ public String getPropertiesDomain()
+ {
+ return PROPERTIES_DOMAIN;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setEnvironmentProperties(final String name, final String value)
+ {
+ checkImmutable();
+ if (name != null && value != null) {
+ if (PROPERTIES.hasProperty(name)) {
+ PROPERTIES.setProperty(this, name, value);
+ } else {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting property " + name + ": " + value);
+ }
+ this.additionalEnvironmentProperties.put(name, value);
+ }
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean hasEnvironmentProperty(final String name)
+ {
+ return PROPERTIES.hasProperty(name);
+ }
+
+
+ /**
+ * Create an instance of this class initialized with properties from the input
+ * stream. If the input stream is null, load properties from the default
+ * properties file.
+ *
+ * @param is to load properties from
+ *
+ * @return <code>LdapConfig</code> initialized ldap config
+ */
+ public static LdapConfig createFromProperties(final InputStream is)
+ {
+ final LdapConfig ldapConfig = new LdapConfig();
+ LdapProperties properties = null;
+ if (is != null) {
+ properties = new LdapProperties(ldapConfig, is);
+ } else {
+ properties = new LdapProperties(ldapConfig);
+ properties.useDefaultPropertiesFile();
+ }
+ properties.configure();
+ return ldapConfig;
+ }
+
+
+ /**
+ * This sets whether authentication credentials will be logged.
+ *
+ * @param log <code>boolean</code>
+ */
+ public void setLogCredentials(final boolean log)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting logCredentials: " + log);
+ }
+ this.logCredentials = log;
+ }
+
+
+ /**
+ * This sets this <code>LdapConfig</code> to use the SSL protocol for
+ * connections.
+ *
+ * @param ssl <code>boolean</code>
+ */
+ public void setSsl(final boolean ssl)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting ssl: " + ssl);
+ }
+ this.ssl = ssl;
+ }
+
+
+ /**
+ * This sets this <code>LdapConfig</code> to use the TLS protocol for
+ * connections. Specifically it sets the connection handler to use {@link
+ * TlsConnectionHandler}.
+ *
+ * @param tls <code>boolean</code>
+ */
+ public void setTls(final boolean tls)
+ {
+ if (tls) {
+ this.setConnectionHandler(new TlsConnectionHandler());
+ } else {
+ this.setConnectionHandler(new DefaultConnectionHandler());
+ }
+ }
+
+
+ /**
+ * This returns a <code>SearchControls</code> object configured with this
+ * <code>LdapConfig</code>.
+ *
+ * @param retAttrs <code>String[]</code> attributes to return from search
+ *
+ * @return <code>SearchControls</code>
+ */
+ public SearchControls getSearchControls(final String[] retAttrs)
+ {
+ final SearchControls ctls = new SearchControls();
+ ctls.setReturningAttributes(retAttrs);
+ ctls.setSearchScope(this.getSearchScope().ordinal());
+ ctls.setTimeLimit(this.getTimeLimit());
+ ctls.setCountLimit(this.getCountLimit());
+ ctls.setDerefLinkFlag(this.getDerefLinkFlag());
+ ctls.setReturningObjFlag(this.getReturningObjFlag());
+ return ctls;
+ }
+
+
+ /**
+ * This returns a <code>SearchControls</code> object configured to perform a
+ * LDAP compare operation.
+ *
+ * @return <code>SearchControls</code>
+ */
+ public static SearchControls getCompareSearchControls()
+ {
+ final SearchControls ctls = new SearchControls();
+ ctls.setReturningAttributes(new String[0]);
+ ctls.setSearchScope(SearchScope.OBJECT.ordinal());
+ return ctls;
+ }
+
+
+ /**
+ * This sets this <code>LdapConfig</code> to print ASN.1 BER packets to the
+ * supplied <code>PrintStream</code>.
+ *
+ * @param stream <code>PrintStream</code>
+ */
+ public void setTracePackets(final PrintStream stream)
+ {
+ checkImmutable();
+ this.tracePackets = stream;
+ }
+
+
+ /**
+ * Provides a descriptive string representation of this instance.
+ *
+ * @return String of the form $Classname at hashCode::env=$env.
+ */
+ @Override
+ public String toString()
+ {
+ return
+ String.format(
+ "%s@%d::env=%s",
+ this.getClass().getName(),
+ this.hashCode(),
+ this.getEnvironment());
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/LdapConstants.java b/src/main/java/edu/vt/middleware/ldap/LdapConstants.java
new file mode 100644
index 0000000..d637470
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/LdapConstants.java
@@ -0,0 +1,352 @@
+/*
+ $Id: LdapConstants.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+/**
+ * <code>LdapConstants</code> contains all the constants needed for creating a
+ * <code>Ldap</code>. See
+ * http://java.sun.com/j2se/1.4.2/docs/guide/jndi/jndi-ldap.html or
+ * http://java.sun.com/j2se/1.4.2/docs/guide/jndi/spec/jndi/properties.html for
+ * more information on JNDI properties.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class LdapConstants
+{
+
+ /**
+ * The value of this property is a fully qualified class name of the factory
+ * class which creates the initial context for the LDAP service provider. The
+ * value of this constant is {@value}.
+ */
+ public static final String CONTEXT_FACTORY = "java.naming.factory.initial";
+
+ /**
+ * The value of this property is a string identifying the class name of a
+ * socket factory. The value of this constant is {@value}.
+ */
+ public static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket";
+
+ /**
+ * The value of this property is a string specifying the authoritativeness of
+ * the service requested. The value of this constant is {@value}.
+ */
+ public static final String AUTHORITATIVE = "java.naming.authoritative";
+
+ /**
+ * The value of this property is a java.io.OutputStream object into which a
+ * hexadecimal dump of the incoming and outgoing LDAP ASN.1 BER packets is
+ * written. The value of this constant is {@value}.
+ */
+ public static final String TRACE = "com.sun.jndi.ldap.trace.ber";
+
+ /**
+ * The value of this property is a string that specifies the authentication
+ * mechanism(s) for the provider to use. The value of this constant is {@value
+ * }.
+ */
+ public static final String AUTHENTICATION =
+ "java.naming.security.authentication";
+
+ /**
+ * The value of this property is a string that specifies the identity of the
+ * principal to be authenticated. The value of this constant is {@value}.
+ */
+ public static final String PRINCIPAL = "java.naming.security.principal";
+
+ /**
+ * The value of this property is an object that specifies the credentials of
+ * the principal to be authenticated. The value of this constant is {@value}.
+ */
+ public static final String CREDENTIALS = "java.naming.security.credentials";
+
+ /**
+ * The value of this property is a string of decimal digits that specifies the
+ * batch size of search results returned by the server. The value of this
+ * constant is {@value}.
+ */
+ public static final String BATCH_SIZE = "java.naming.batchsize";
+
+ /**
+ * The value of this property is a string that specifies the DNS host and
+ * domain names. The value of this constant is {@value}.
+ */
+ public static final String DNS_URL = "java.naming.dns.url";
+
+ /**
+ * The value of this property is a string language tag according to RFC 1766.
+ * The value of this constant is {@value}.
+ */
+ public static final String LANGUAGE = "java.naming.language";
+
+ /**
+ * The value of this property is a string that specifies how referrals shall
+ * be handled by the provider. The value of this constant is {@value}.
+ */
+ public static final String REFERRAL = "java.naming.referral";
+
+ /**
+ * The value of this property is a string that specifies how aliases shall be
+ * handled by the provider. The value of this constant is {@value}.
+ */
+ public static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";
+
+ /**
+ * The value of this property is a string that specifies additional binary
+ * attributes. The value of this constant is {@value}.
+ */
+ public static final String BINARY_ATTRIBUTES =
+ "java.naming.ldap.attributes.binary";
+
+ /**
+ * The value of this property is a string that specifies a SASL authorization
+ * id. The value of this constant is {@value}.
+ */
+ public static final String SASL_AUTHORIZATION_ID =
+ "java.naming.security.sasl.authorizationId";
+
+ /**
+ * The value of this property is a string that specifies a SASL realm. The
+ * value of this constant is {@value}.
+ */
+ public static final String SASL_REALM = "java.naming.security.sasl.realm";
+
+ /**
+ * The value of this property is a string that specifies to only return
+ * attribute type names, no values. The value of this constant is {@value}.
+ */
+ public static final String TYPES_ONLY = "java.naming.ldap.typesOnly";
+
+ /**
+ * The value of this property is a string that specifies the security protocol
+ * for the provider to use. The value of this constant is {@value}.
+ */
+ public static final String PROTOCOL = "java.naming.security.protocol";
+
+ /**
+ * The value of this property is a string that specifies the protocol version
+ * for the provider. The value of this constant is {@value}.
+ */
+ public static final String VERSION = "java.naming.ldap.version";
+
+ /**
+ * The value of this property is a URL string that specifies the hostname and
+ * port number of the LDAP server, and the root distinguished name of the
+ * naming context to use. The value of this constant is {@value}.
+ */
+ public static final String PROVIDER_URL = "java.naming.provider.url";
+
+ /**
+ * The value of this property is a string that specifies the time in
+ * milliseconds that a connection attempt will abort if the connection cannot
+ * be made. The value of this constant is {@value}.
+ */
+ public static final String TIMEOUT = "com.sun.jndi.ldap.connect.timeout";
+
+ /**
+ * Value passed to PROTOCOL to use SSL.
+ * The value of this constant is {@value}.
+ */
+ public static final String SSL_PROTOCOL = "ssl";
+
+ /**
+ * Value passed to AUTHENTICATION to use simple authentication. The value of
+ * this constant is {@value}.
+ */
+ public static final String SIMPLE_AUTHTYPE = "simple";
+
+ /**
+ * Value passed to AUTHENTICATION to use simple authentication. The value of
+ * this constant is {@value}.
+ */
+ public static final String STRONG_AUTHTYPE = "strong";
+
+ /**
+ * Value passed to AUTHENTICATION to use none authentication The value of this
+ * constant is {@value}.
+ */
+ public static final String NONE_AUTHTYPE = "none";
+
+ /**
+ * Value passed to VERSION to use ldap version 3 controls The value of this
+ * constant is {@value}.
+ */
+ public static final String VERSION_THREE = "3";
+
+ /** Ldap scheme, the value of this constant is {@value}. */
+ public static final String PROVIDER_URL_SCHEME = "ldap";
+
+ /** Secure ldap scheme, the value of this constant is {@value}. */
+ public static final String PROVIDER_URL_SSL_SCHEME = "ldaps";
+
+ /**
+ * URL prefix used for constructing URLs. The value of this constant is
+ * {@value}.
+ */
+ public static final String PROVIDER_URL_PREFIX = "://";
+
+ /**
+ * URL separator used for constructing URLs. The value of this constant is
+ * {@value}.
+ */
+ public static final String PROVIDER_URL_SEPARATOR = ":";
+
+ /**
+ * Ldap command which returns a list of supported SASL mechanisms. The value
+ * of this constant is {@value}.
+ */
+ public static final String SUPPORTED_SASL_MECHANISMS =
+ "supportedSASLMechanisms";
+
+ /**
+ * Ldap command which returns a list of supported controls. The value of this
+ * constant is {@value}.
+ */
+ public static final String SUPPORTED_CONTROL = "supportedcontrol";
+
+ /**
+ * Value passed to AUTHENTICATION to use SASL authentication. The value of
+ * this constant is {@value}.
+ */
+ public static final String SASL_MECHANISM_EXTERNAL = "EXTERNAL";
+
+ /**
+ * Value passed to AUTHENTICATION to use DIGEST-MD5 authentication. The value
+ * of this constant is {@value}.
+ */
+ public static final String SASL_MECHANISM_DIGEST_MD5 = "DIGEST-MD5";
+
+ /**
+ * Value passed to AUTHENTICATION to use CRAM-MD5 authentication. The value of
+ * this constant is {@value}.
+ */
+ public static final String SASL_MECHANISM_CRAM_MD5 = "CRAM-MD5";
+
+ /**
+ * Value passed to AUTHENTICATION to use GSS-API authentication. The value of
+ * this constant is {@value}.
+ */
+ public static final String SASL_MECHANISM_GSS_API = "GSSAPI";
+
+ /** List of supported SASL Mechanisms. */
+ public static final String[] SASL_MECHANISMS = new String[] {
+ SASL_MECHANISM_EXTERNAL,
+ SASL_MECHANISM_DIGEST_MD5,
+ SASL_MECHANISM_CRAM_MD5,
+ SASL_MECHANISM_GSS_API,
+ };
+
+ /** Default context factory, value of this constant is {@value}. */
+ public static final String DEFAULT_CONTEXT_FACTORY =
+ "com.sun.jndi.ldap.LdapCtxFactory";
+
+ /** Default base DN, value of this constant is {@value}. */
+ public static final String DEFAULT_BASE_DN = "";
+
+ /**
+ * Default timeout, -1 means use provider setting. The value of this constant
+ * is {@value}.
+ */
+ public static final int DEFAULT_TIMEOUT = -1;
+
+ /** Default authentication type, the value of this constant is {@value}. */
+ public static final String DEFAULT_AUTHTYPE = SIMPLE_AUTHTYPE;
+
+ /**
+ * Default time limit, 0 means wait indefinitely. The value of this constant
+ * is {@value}.
+ */
+ public static final int DEFAULT_TIME_LIMIT = 0;
+
+ /**
+ * Default count limit, 0 means return all results. The value of this constant
+ * is {@value}.
+ */
+ public static final long DEFAULT_COUNT_LIMIT = 0;
+
+ /** Default paged results size. The value of this constant is {@value}. */
+ public static final int DEFAULT_PAGED_RESULTS_SIZE = 0;
+
+ /**
+ * Default batch size, -1 means use provider setting. The value of this
+ * constant is {@value}.
+ */
+ public static final int DEFAULT_BATCH_SIZE = -1;
+
+ /** Default authoritative value, the value of this constant is {@value}. */
+ public static final boolean DEFAULT_AUTHORITATIVE = false;
+
+ /** Default type only value, the value of this constant is {@value}. */
+ public static final boolean DEFAULT_TYPES_ONLY = false;
+
+ /** Default ignore case value, value of this constant is {@value}. */
+ public static final boolean DEFAULT_IGNORE_CASE = true;
+
+ /** Default ldap port, the value of this constant is {@value}. */
+ public static final String DEFAULT_PORT = "389";
+
+ /** Default ldaps port, the value of this constant is {@value}. */
+ public static final String DEFAULT_SSL_PORT = "636";
+
+ /** Whether to use SSL by default, the value of this constant is {@value}. */
+ public static final boolean DEFAULT_USE_SSL = false;
+
+ /**
+ * Whether to log authentication credentials. The value of this constant is
+ * {@value}.
+ */
+ public static final boolean DEFAULT_LOG_CREDENTIALS = false;
+
+ /**
+ * Default userfield field used by Authenticator. The value of this constant
+ * is {@value}.
+ */
+ public static final String DEFAULT_USER_FIELD = "uid";
+
+ /**
+ * Whether Authenticator should throw an exception if multiple DNs are found
+ * by {@link edu.vt.middleware.ldap.auth.Authenticator#getDn(String)}. The
+ * value of this constant is {@value}.
+ */
+ public static final boolean DEFAULT_ALLOW_MULTIPLE_DNS = false;
+
+ /**
+ * Default character set for creating strings. The value of this constant is
+ * {@value}.
+ */
+ public static final String DEFAULT_CHARSET = "UTF-8";
+
+ /**
+ * Default number of times to retry an operation on failure. The value of this
+ * constant is {@value}.
+ */
+ public static final int DEFAULT_OPERATION_RETRY = 1;
+
+ /**
+ * Default amount of time to wait between operation retries. The value of this
+ * constant is {@value}.
+ */
+ public static final long DEFAULT_OPERATION_RETRY_WAIT = 0;
+
+ /**
+ * Default factor to multiply the operation retry wait by. The value of this
+ * constant is {@value}.
+ */
+ public static final int DEFAULT_OPERATION_RETRY_BACKOFF = 0;
+
+
+ /** Default constructor. */
+ private LdapConstants() {}
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/LdapSearch.java b/src/main/java/edu/vt/middleware/ldap/LdapSearch.java
new file mode 100644
index 0000000..a58f926
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/LdapSearch.java
@@ -0,0 +1,172 @@
+/*
+ $Id: LdapSearch.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.bean.LdapBeanFactory;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import edu.vt.middleware.ldap.pool.LdapPool;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>LdapSearch</code> queries an LDAP and returns the result. Each instance
+ * of <code>LdapSearch</code> maintains it's own pool of LDAP connections.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class LdapSearch
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Ldap object to use for searching. */
+ protected LdapPool<Ldap> pool;
+
+ /** Ldap bean factory. */
+ protected LdapBeanFactory beanFactory = LdapBeanProvider.getLdapBeanFactory();
+
+
+ /**
+ * This creates a new <code>LdapSearch</code> with the supplied pool.
+ *
+ * @param pool <code>LdapPool</code>
+ */
+ public LdapSearch(final LdapPool<Ldap> pool)
+ {
+ this.pool = pool;
+ }
+
+
+ /**
+ * Returns the factory for creating ldap beans.
+ *
+ * @return <code>LdapBeanFactory</code>
+ */
+ public LdapBeanFactory getLdapBeanFactory()
+ {
+ return this.beanFactory;
+ }
+
+
+ /**
+ * Sets the factory for creating ldap beans.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public void setLdapBeanFactory(final LdapBeanFactory lbf)
+ {
+ if (lbf != null) {
+ this.beanFactory = lbf;
+ }
+ }
+
+
+ /**
+ * This will perform an LDAP search with the supplied query and return
+ * attributes.
+ *
+ * @param query <code>String</code> to search for
+ * @param attrs <code>String[]</code> to return
+ *
+ * @return <code>Iterator</code> of search results
+ *
+ * @throws NamingException if an error occurs while searching
+ */
+ public Iterator<SearchResult> search(final String query, final String[] attrs)
+ throws NamingException
+ {
+ Iterator<SearchResult> queryResults = null;
+ if (query != null) {
+ try {
+ Ldap ldap = null;
+ try {
+ ldap = this.pool.checkOut();
+ queryResults = ldap.search(new SearchFilter(query), attrs);
+ } catch (NamingException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error attempting LDAP search", e);
+ }
+ throw e;
+ } finally {
+ this.pool.checkIn(ldap);
+ }
+ } catch (Exception e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error using LDAP pool", e);
+ }
+ }
+ }
+ return queryResults;
+ }
+
+
+ /**
+ * This will perform an LDAP search with the supplied query and return
+ * attributes. The results will be written to the supplied <code>
+ * Writer</code>.
+ *
+ * @param query <code>String</code> to search for
+ * @param attrs <code>String[]</code> to return
+ * @param writer <code>Writer</code> to write to
+ *
+ * @throws NamingException if an error occurs while searching
+ * @throws IOException if an error occurs while writing search results
+ */
+ public void search(
+ final String query,
+ final String[] attrs,
+ final Writer writer)
+ throws NamingException, IOException
+ {
+ final LdapResult lr = this.beanFactory.newLdapResult();
+ lr.addEntries(this.search(query, attrs));
+ writer.write(lr.toString());
+ writer.flush();
+ }
+
+
+ /**
+ * Empties the underlying ldap pool, closing all connections. See {@link
+ * LdapPool#close()}.
+ */
+ public void close()
+ {
+ this.pool.close();
+ }
+
+
+ /**
+ * Called by the garbage collector on an object when garbage collection
+ * determines that there are no more references to the object.
+ *
+ * @throws Throwable if an exception is thrown by this method
+ */
+ protected void finalize()
+ throws Throwable
+ {
+ try {
+ this.close();
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/LdapUtil.java b/src/main/java/edu/vt/middleware/ldap/LdapUtil.java
new file mode 100644
index 0000000..33f0ee4
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/LdapUtil.java
@@ -0,0 +1,222 @@
+/*
+ $Id: LdapUtil.java 2217 2012-01-23 19:56:35Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2217 $
+ Updated: $Date: 2012-01-23 19:56:35 +0000 (Mon, 23 Jan 2012) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.util.regex.Pattern;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>LdapUtil</code> provides helper methods for <code>Ldap</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2217 $ $Date: 2012-01-23 19:56:35 +0000 (Mon, 23 Jan 2012) $
+ */
+public final class LdapUtil
+{
+
+ /** Size of buffer in bytes to use when reading files. */
+ private static final int READ_BUFFER_SIZE = 128;
+
+ /** Pattern to match ipv4 addresses. */
+ private static final Pattern IPV4_PATTERN =
+ Pattern.compile(
+ "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)" +
+ "(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
+
+ /** Pattern to match ipv6 addresses. */
+ private static final Pattern IPV6_STD_PATTERN =
+ Pattern.compile("^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
+
+ /** Pattern to match ipv6 hex compressed addresses. */
+ private static final Pattern IPV6_HEX_COMPRESSED_PATTERN =
+ Pattern.compile(
+ "^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::" +
+ "((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$");
+
+
+ /** Default constructor. */
+ private LdapUtil() {}
+
+
+ /**
+ * This checks a credential to ensure it is the right type and it is not
+ * empty. A credential can be of type String, char[], or byte[].
+ *
+ * @param credential <code>Object</code> to check
+ *
+ * @return <code>boolean</code> - whether the credential is valid
+ */
+ public static boolean checkCredential(final Object credential)
+ {
+ boolean answer = false;
+ if (credential != null) {
+ if (credential instanceof String) {
+ final String string = (String) credential;
+ if (!"".equals(string)) {
+ answer = true;
+ }
+ } else if (credential instanceof char[]) {
+ final char[] array = (char[]) credential;
+ if (array.length != 0) {
+ answer = true;
+ }
+ } else if (credential instanceof byte[]) {
+ final byte[] array = (byte[]) credential;
+ if (array.length != 0) {
+ answer = true;
+ }
+ }
+ }
+ return answer;
+ }
+
+
+ /**
+ * This will convert the supplied value to a base64 encoded string. Returns
+ * null if the bytes cannot be encoded.
+ *
+ * @param value <code>byte[]</code> to base64 encode
+ *
+ * @return <code>String</code>
+ */
+ public static String base64Encode(final byte[] value)
+ {
+ String encodedValue = null;
+ if (value != null) {
+ try {
+ encodedValue = new String(
+ Base64.encodeBase64(value),
+ LdapConstants.DEFAULT_CHARSET);
+ } catch (UnsupportedEncodingException e) {
+ final Log logger = LogFactory.getLog(LdapUtil.class);
+ if (logger.isErrorEnabled()) {
+ logger.error(
+ "Could not encode value using " + LdapConstants.DEFAULT_CHARSET);
+ }
+ }
+ }
+ return encodedValue;
+ }
+
+
+ /**
+ * This will convert the supplied value to a base64 encoded string. Returns
+ * null if the string cannot be encoded.
+ *
+ * @param value <code>String</code> to base64 encode
+ *
+ * @return <code>String</code>
+ */
+ public static String base64Encode(final String value)
+ {
+ String encodedValue = null;
+ if (value != null) {
+ try {
+ encodedValue = base64Encode(
+ value.getBytes(LdapConstants.DEFAULT_CHARSET));
+ } catch (UnsupportedEncodingException e) {
+ final Log logger = LogFactory.getLog(LdapUtil.class);
+ if (logger.isErrorEnabled()) {
+ logger.error(
+ "Could not encode value using " + LdapConstants.DEFAULT_CHARSET);
+ }
+ }
+ }
+ return encodedValue;
+ }
+
+
+ /**
+ * This will decode the supplied value as a base64 encoded string to a byte[].
+ *
+ * @param value <code>Object</code> to base64 encode
+ *
+ * @return <code>String</code>
+ */
+ public static byte[] base64Decode(final String value)
+ {
+ byte[] decodedValue = null;
+ if (value != null) {
+ decodedValue = Base64.decodeBase64(value.getBytes());
+ }
+ return decodedValue;
+ }
+
+
+ /**
+ * Reads the data at the supplied URL and returns it as a byte array.
+ *
+ * @param url <code>URL</code> to read
+ *
+ * @return <code>byte[]</code> read from URL
+ *
+ * @throws IOException if an error occurs reading data
+ */
+ public static byte[] readURL(final URL url)
+ throws IOException
+ {
+ return readInputStream(url.openStream());
+ }
+
+
+ /**
+ * Reads the data in the supplied stream and returns it as a byte array.
+ *
+ * @param is <code>InputStream</code> to read
+ *
+ * @return <code>byte[]</code> read from the stream
+ *
+ * @throws IOException if an error occurs reading data
+ */
+ public static byte[] readInputStream(final InputStream is)
+ throws IOException
+ {
+ final ByteArrayOutputStream data = new ByteArrayOutputStream();
+ try {
+ final byte[] buffer = new byte[READ_BUFFER_SIZE];
+ int length;
+ while ((length = is.read(buffer)) != -1) {
+ data.write(buffer, 0, length);
+ }
+ } finally {
+ is.close();
+ data.close();
+ }
+ return data.toByteArray();
+ }
+
+
+ /**
+ * Returns whether the supplied string represents an IP address. Matches both
+ * IPv4 and IPv6 addresses.
+ *
+ * @param s to match
+ *
+ * @return whether the supplied string represents an IP address
+ */
+ public static boolean isIPAddress(final String s)
+ {
+ return s != null &&
+ (IPV4_PATTERN.matcher(s).matches() ||
+ IPV6_STD_PATTERN.matcher(s).matches() ||
+ IPV6_HEX_COMPRESSED_PATTERN.matcher(s).matches());
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/SearchFilter.java b/src/main/java/edu/vt/middleware/ldap/SearchFilter.java
new file mode 100644
index 0000000..dcb7fde
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/SearchFilter.java
@@ -0,0 +1,147 @@
+/*
+ $Id: SearchFilter.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * <code>SearchFilter</code> provides a bean for a filter and it's arguments.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class SearchFilter
+{
+
+ /** filter. */
+ private String filter;
+
+ /** filter arguments. */
+ private List<Object> filterArgs = new ArrayList<Object>();
+
+
+ /** Default constructor. */
+ public SearchFilter() {}
+
+
+ /**
+ * Creates a new search filter with the supplied filter.
+ *
+ * @param s to set filter
+ */
+ public SearchFilter(final String s)
+ {
+ this.filter = s;
+ }
+
+
+ /**
+ * Creates a new string search filter with the supplied filter and arguments.
+ *
+ * @param s to set filter
+ * @param o to set filter arguments
+ */
+ public SearchFilter(final String s, final List<?> o)
+ {
+ this.setFilter(s);
+ this.setFilterArgs(o);
+ }
+
+
+ /**
+ * Creates a new search filter with the supplied filter and arguments.
+ *
+ * @param s to set filter
+ * @param o to set filter arguments
+ */
+ public SearchFilter(final String s, final Object[] o)
+ {
+ this.setFilter(s);
+ this.setFilterArgs(o);
+ }
+
+
+ /**
+ * Gets the filter.
+ *
+ * @return filter
+ */
+ public String getFilter()
+ {
+ return this.filter;
+ }
+
+
+ /**
+ * Sets the filter.
+ *
+ * @param s to set filter
+ */
+ public void setFilter(final String s)
+ {
+ this.filter = s;
+ }
+
+
+ /**
+ * Gets the filter arguments.
+ *
+ * @return filter args
+ */
+ public List<Object> getFilterArgs()
+ {
+ return this.filterArgs;
+ }
+
+
+ /**
+ * Sets the filter arguments.
+ *
+ * @param o to set filter arguments
+ */
+ public void setFilterArgs(final List<?> o)
+ {
+ if (o != null) {
+ this.filterArgs.addAll(o);
+ }
+ }
+
+
+ /**
+ * Sets the filter arguments.
+ *
+ * @param o to set filter arguments
+ */
+ public void setFilterArgs(final Object[] o)
+ {
+ if (o != null) {
+ this.filterArgs.addAll(Arrays.asList(o));
+ }
+ }
+
+
+ /**
+ * This returns a string representation of this search filter.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return
+ String.format("filter=%s,filterArgs=%s", this.filter, this.filterArgs);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/AbstractAuthenticator.java b/src/main/java/edu/vt/middleware/ldap/auth/AbstractAuthenticator.java
new file mode 100644
index 0000000..434e670
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/AbstractAuthenticator.java
@@ -0,0 +1,242 @@
+/*
+ $Id: AbstractAuthenticator.java 1743 2010-11-19 17:00:18Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1743 $
+ Updated: $Date: 2010-11-19 17:00:18 +0000 (Fri, 19 Nov 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import java.util.Arrays;
+import javax.naming.AuthenticationException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import edu.vt.middleware.ldap.LdapConstants;
+import edu.vt.middleware.ldap.LdapUtil;
+import edu.vt.middleware.ldap.auth.handler.AuthenticationCriteria;
+import edu.vt.middleware.ldap.auth.handler.AuthenticationHandler;
+import edu.vt.middleware.ldap.auth.handler.AuthenticationResultHandler;
+import edu.vt.middleware.ldap.auth.handler.AuthorizationHandler;
+import edu.vt.middleware.ldap.handler.ConnectionHandler;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AbstractAuthenticator</code> provides basic functionality for
+ * authenticating against an LDAP.
+ *
+ * @param <T> type of AuthenticatorConfig
+ *
+ * @author Middleware Services
+ * @version $Revision: 1743 $ $Date: 2010-11-19 17:00:18 +0000 (Fri, 19 Nov 2010) $
+ */
+public abstract class AbstractAuthenticator<T extends AuthenticatorConfig>
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Authenticator configuration environment. */
+ protected T config;
+
+
+ /**
+ * This will set the config parameters of this <code>Authenticator</code>.
+ *
+ * @param authConfig <code>AuthenticatorConfig</code>
+ */
+ public void setAuthenticatorConfig(final T authConfig)
+ {
+ if (this.config != null) {
+ this.config.checkImmutable();
+ }
+ this.config = authConfig;
+ }
+
+
+ /**
+ * This will authenticate by binding to the LDAP with the supplied dn and
+ * credential. See {@link #authenticateAndAuthorize( String, Object, boolean,
+ * String[], AuthenticationResultHandler[], AuthorizationHandler[])}.
+ *
+ * @param dn <code>String</code> for bind
+ * @param credential <code>Object</code> for bind
+ * @param authResultHandler <code>AuthenticationResultHandler[]</code> to
+ * post process authentication results
+ * @param authzHandler <code>AuthorizationHandler[]</code> to process
+ * authorization after authentication
+ *
+ * @return <code>boolean</code> - whether the bind succeeded
+ *
+ * @throws NamingException if the authentication fails for any other reason
+ * than invalid credentials
+ */
+ protected boolean authenticateAndAuthorize(
+ final String dn,
+ final Object credential,
+ final AuthenticationResultHandler[] authResultHandler,
+ final AuthorizationHandler[] authzHandler)
+ throws NamingException
+ {
+ boolean success = false;
+ try {
+ this.authenticateAndAuthorize(
+ dn,
+ credential,
+ false,
+ null,
+ authResultHandler,
+ authzHandler);
+ success = true;
+ } catch (AuthenticationException e) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Authentication failed for dn: " + dn, e);
+ }
+ } catch (AuthorizationException e) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Authorization failed for dn: " + dn, e);
+ }
+ }
+ return success;
+ }
+
+
+ /**
+ * This will authenticate by binding to the LDAP with the supplied dn and
+ * credential. Authentication will never succeed if {@link
+ * AuthenticatorConfig#getAuthtype()} is set to 'none'. If retAttrs is null
+ * and searchAttrs is true then all user attributes will be returned. If
+ * retAttrs is an empty array and searchAttrs is true then no attributes will
+ * be returned. This method throws AuthenticationException if authentication
+ * fails and AuthorizationException if authorization fails.
+ *
+ * @param dn <code>String</code> for bind
+ * @param credential <code>Object</code> for bind
+ * @param searchAttrs <code>boolean</code> whether to perform attribute
+ * search
+ * @param retAttrs <code>String[]</code> user attributes to return
+ * @param authResultHandler <code>AuthenticationResultHandler[]</code> to
+ * post process authentication results
+ * @param authzHandler <code>AuthorizationHandler[]</code> to process
+ * authorization after authentication
+ *
+ * @return <code>Attribute</code> - belonging to the supplied user, returns
+ * null if searchAttrs is false
+ *
+ * @throws NamingException if any of the ldap operations fail
+ * @throws AuthenticationException if authentication fails
+ * @throws AuthorizationException if authorization fails
+ */
+ protected Attributes authenticateAndAuthorize(
+ final String dn,
+ final Object credential,
+ final boolean searchAttrs,
+ final String[] retAttrs,
+ final AuthenticationResultHandler[] authResultHandler,
+ final AuthorizationHandler[] authzHandler)
+ throws NamingException
+ {
+ // check the authentication type
+ final String authtype = this.config.getAuthtype();
+ if (authtype.equalsIgnoreCase(LdapConstants.NONE_AUTHTYPE)) {
+ throw new AuthenticationException(
+ "Cannot authenticate dn, authtype is 'none'");
+ }
+
+ // check the credential
+ if (!LdapUtil.checkCredential(credential)) {
+ throw new AuthenticationException(
+ "Cannot authenticate dn, invalid credential");
+ }
+
+ // check the dn
+ if (dn == null || "".equals(dn)) {
+ throw new AuthenticationException("Cannot authenticate dn, invalid dn");
+ }
+
+ Attributes userAttributes = null;
+
+ // attempt to bind as this dn
+ final ConnectionHandler ch = this.config.getConnectionHandler()
+ .newInstance();
+ try {
+ final AuthenticationCriteria ac = new AuthenticationCriteria(dn);
+ ac.setCredential(credential);
+ try {
+ final AuthenticationHandler authHandler = this.config
+ .getAuthenticationHandler().newInstance();
+ authHandler.authenticate(ch, ac);
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("Authentication succeeded for dn: " + dn);
+ }
+ } catch (AuthenticationException e) {
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("Authentication failed for dn: " + dn);
+ }
+ if (authResultHandler != null && authResultHandler.length > 0) {
+ for (AuthenticationResultHandler ah : authResultHandler) {
+ ah.process(ac, false);
+ }
+ }
+ throw e;
+ }
+ // authentication succeeded, perform authorization if supplied
+ if (authzHandler != null && authzHandler.length > 0) {
+ for (AuthorizationHandler azh : authzHandler) {
+ try {
+ azh.process(ac, ch.getLdapContext());
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info(
+ "Authorization succeeded for dn: " + dn + " with handler: " +
+ azh);
+ }
+ } catch (AuthenticationException e) {
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info(
+ "Authorization failed for dn: " + dn + " with handler: " + azh);
+ }
+ if (authResultHandler != null && authResultHandler.length > 0) {
+ for (AuthenticationResultHandler ah : authResultHandler) {
+ ah.process(ac, false);
+ }
+ }
+ throw e;
+ }
+ }
+ }
+ if (searchAttrs) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Returning attributes: ");
+ this.logger.debug(
+ " " +
+ (retAttrs == null ? "all attributes" : Arrays.toString(retAttrs)));
+ }
+ userAttributes = ch.getLdapContext().getAttributes(dn, retAttrs);
+ }
+ if (authResultHandler != null && authResultHandler.length > 0) {
+ for (AuthenticationResultHandler ah : authResultHandler) {
+ ah.process(ac, true);
+ }
+ }
+ } finally {
+ ch.close();
+ }
+
+ return userAttributes;
+ }
+
+
+ /** This will close the connection on the underlying DN resolver. */
+ public synchronized void close()
+ {
+ if (this.config.getDnResolver() != null) {
+ this.config.getDnResolver().close();
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/Authenticator.java b/src/main/java/edu/vt/middleware/ldap/auth/Authenticator.java
new file mode 100644
index 0000000..f57fb6c
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/Authenticator.java
@@ -0,0 +1,366 @@
+/*
+ $Id: Authenticator.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.auth.handler.AuthenticationResultHandler;
+import edu.vt.middleware.ldap.auth.handler.AuthorizationHandler;
+import edu.vt.middleware.ldap.auth.handler.CompareAuthorizationHandler;
+
+/**
+ * <code>Authenticator</code> contains functions for authenticating a user
+ * against an LDAP.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class Authenticator extends AbstractAuthenticator<AuthenticatorConfig>
+ implements Serializable
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -444519681288987247L;
+
+
+ /** Default constructor. */
+ public Authenticator() {}
+
+
+ /**
+ * This will create a new <code>Authenticator</code> with the supplied <code>
+ * AuthenticatorConfig</code>.
+ *
+ * @param authConfig <code>AuthenticatorConfig</code>
+ */
+ public Authenticator(final AuthenticatorConfig authConfig)
+ {
+ this.setAuthenticatorConfig(authConfig);
+ }
+
+
+ /**
+ * This returns the <code>AuthenticatorConfig</code> of the <code>
+ * Authenticator</code>.
+ *
+ * @return <code>AuthenticatorConfig</code>
+ */
+ public AuthenticatorConfig getAuthenticatorConfig()
+ {
+ return this.config;
+ }
+
+
+ /**
+ * This will set the config parameters of this <code>Authenticator</code>
+ * using the default properties file, which must be located in your classpath.
+ */
+ public void loadFromProperties()
+ {
+ this.setAuthenticatorConfig(AuthenticatorConfig.createFromProperties(null));
+ }
+
+
+ /**
+ * This will set the config parameters of this <code>Authenticator</code>
+ * using the supplied input stream.
+ *
+ * @param is <code>InputStream</code>
+ */
+ public void loadFromProperties(final InputStream is)
+ {
+ this.setAuthenticatorConfig(AuthenticatorConfig.createFromProperties(is));
+ }
+
+
+ /**
+ * This will attempt to find the LDAP DN for the supplied user. {@link
+ * AuthenticatorConfig#dnResolver} is invoked to perform this operation.
+ *
+ * @param user <code>String</code> to find dn for
+ *
+ * @return <code>String</code> - user's dn
+ *
+ * @throws NamingException an LDAP error occurs
+ */
+ public String getDn(final String user)
+ throws NamingException
+ {
+ return this.config.getDnResolver().resolve(user);
+ }
+
+
+ /**
+ * This will authenticate by binding to the LDAP using parameters given by
+ * {@link AuthenticatorConfig#setUser} and {@link
+ * AuthenticatorConfig#setCredential}. See {@link #authenticate(String,
+ * Object)}.
+ *
+ * @return <code>boolean</code> - whether the bind succeeded
+ *
+ * @throws NamingException if the authentication fails for any other reason
+ * than invalid credentials
+ */
+ public boolean authenticate()
+ throws NamingException
+ {
+ return
+ this.authenticate(this.config.getUser(), this.config.getCredential());
+ }
+
+
+ /**
+ * This will authenticate by binding to the LDAP with the supplied user and
+ * credential. If {@link AuthenticatorConfig#setAuthorizationFilter} has been
+ * called, then it will be used to authorize the user by performing an ldap
+ * compare. See {@link #authenticate(String, Object, SearchFilter)}.
+ *
+ * @param user <code>String</code> username for bind
+ * @param credential <code>Object</code> credential for bind
+ *
+ * @return <code>boolean</code> - whether the bind succeeded
+ *
+ * @throws NamingException if the authentication fails for any other reason
+ * than invalid credentials
+ */
+ public boolean authenticate(final String user, final Object credential)
+ throws NamingException
+ {
+ return
+ this.authenticate(
+ user,
+ credential,
+ new SearchFilter(
+ this.config.getAuthorizationFilter(),
+ this.config.getAuthorizationFilterArgs()));
+ }
+
+
+ /**
+ * This will authenticate by binding to the LDAP with the supplied user and
+ * credential. If the supplied filter is not null it will be injected into a
+ * new instance of CompareAuthorizationHandler and set as the first
+ * AuthorizationHandler to execute. If {@link
+ * AuthenticatorConfig#setAuthenticationResultHandlers(
+ * AuthenticationResultHandler[])} has been called, then it will be used to
+ * post process authentication results. See {@link #authenticate(String,
+ * Object, AuthenticationResultHandler[], AuthorizationHandler[])}.
+ *
+ * @param user <code>String</code> username for bind
+ * @param credential <code>Object</code> credential for bind
+ * @param filter <code>SearchFilter</code> to authorize user
+ *
+ * @return <code>boolean</code> - whether the bind succeeded
+ *
+ * @throws NamingException if the authentication fails for any other reason
+ * than invalid credentials
+ */
+ public boolean authenticate(
+ final String user,
+ final Object credential,
+ final SearchFilter filter)
+ throws NamingException
+ {
+ final List<AuthorizationHandler> authzHandler =
+ new ArrayList<AuthorizationHandler>();
+ if (filter != null && filter.getFilter() != null) {
+ authzHandler.add(new CompareAuthorizationHandler(filter));
+ }
+ if (this.config.getAuthorizationHandlers() != null) {
+ authzHandler.addAll(
+ Arrays.asList(this.config.getAuthorizationHandlers()));
+ }
+ return
+ this.authenticate(
+ user,
+ credential,
+ this.config.getAuthenticationResultHandlers(),
+ authzHandler.toArray(new AuthorizationHandler[0]));
+ }
+
+
+ /**
+ * This will authenticate by binding to the LDAP with the supplied user and
+ * credential. The user's DN will be looked up before performing the bind by
+ * calling {@link DnResolver#resolve(String)}. See {@link
+ * #authenticateAndAuthorize(String, Object, AuthenticationResultHandler[],
+ * AuthorizationHandler[])}.
+ *
+ * @param user <code>String</code> username for bind
+ * @param credential <code>Object</code> credential for bind
+ * @param authHandler <code>AuthenticationResultHandler[]</code> to post
+ * process authentication results
+ * @param authzHandler <code>AuthorizationHandler[]</code> to process
+ * authorization after authentication
+ *
+ * @return <code>boolean</code> - whether the bind succeeded
+ *
+ * @throws NamingException if the authentication fails for any other reason
+ * than invalid credentials
+ */
+ public boolean authenticate(
+ final String user,
+ final Object credential,
+ final AuthenticationResultHandler[] authHandler,
+ final AuthorizationHandler[] authzHandler)
+ throws NamingException
+ {
+ return
+ super.authenticateAndAuthorize(
+ this.getDn(user),
+ credential,
+ authHandler,
+ authzHandler);
+ }
+
+
+ /**
+ * This will authenticate by binding to the LDAP using parameters given by
+ * {@link AuthenticatorConfig#setUser} and {@link
+ * AuthenticatorConfig#setCredential}. See {@link
+ * #authenticate(String,Object,String[])}
+ *
+ * @param retAttrs <code>String[]</code> attributes to return
+ *
+ * @return <code>Attributes</code> - of authenticated user
+ *
+ * @throws NamingException if any of the ldap operations fail
+ */
+ public Attributes authenticate(final String[] retAttrs)
+ throws NamingException
+ {
+ return
+ this.authenticate(
+ this.config.getUser(),
+ this.config.getCredential(),
+ retAttrs);
+ }
+
+
+ /**
+ * This will authenticate by binding to the LDAP with the supplied user and
+ * credential. If {@link AuthenticatorConfig#setAuthorizationFilter} has been
+ * called, then it will be used to authorize the user by performing an ldap
+ * compare. See {@link #authenticate(String, Object, SearchFilter, String[])}
+ *
+ * @param user <code>String</code> username for bind
+ * @param credential <code>Object</code> credential for bind
+ * @param retAttrs <code>String[]</code> to return
+ *
+ * @return <code>Attributes</code> - of authenticated user
+ *
+ * @throws NamingException if any of the ldap operations fail
+ */
+ public Attributes authenticate(
+ final String user,
+ final Object credential,
+ final String[] retAttrs)
+ throws NamingException
+ {
+ return
+ this.authenticate(
+ user,
+ credential,
+ new SearchFilter(
+ this.config.getAuthorizationFilter(),
+ this.config.getAuthorizationFilterArgs()),
+ retAttrs);
+ }
+
+
+ /**
+ * This will authenticate by binding to the LDAP with the supplied user and
+ * credential. If the supplied filter is not null it will be injected into a
+ * new instance of CompareAuthorizationHandler and set as the first
+ * AuthorizationHandler to execute. See {@link #authenticate(String, Object,
+ * String[], AuthenticationResultHandler[], AuthorizationHandler[])}.
+ *
+ * @param user <code>String</code> username for bind
+ * @param credential <code>Object</code> credential for bind
+ * @param filter <code>SearchFilter</code> to authorize user
+ * @param retAttrs <code>String[]</code> to return
+ *
+ * @return <code>Attributes</code> - of authenticated user
+ *
+ * @throws NamingException if any of the ldap operations fail
+ */
+ public Attributes authenticate(
+ final String user,
+ final Object credential,
+ final SearchFilter filter,
+ final String[] retAttrs)
+ throws NamingException
+ {
+ final List<AuthorizationHandler> authzHandler =
+ new ArrayList<AuthorizationHandler>();
+ if (filter != null && filter.getFilter() != null) {
+ authzHandler.add(new CompareAuthorizationHandler(filter));
+ }
+ if (this.config.getAuthorizationHandlers() != null) {
+ authzHandler.addAll(
+ Arrays.asList(this.config.getAuthorizationHandlers()));
+ }
+ return
+ this.authenticate(
+ user,
+ credential,
+ retAttrs,
+ this.config.getAuthenticationResultHandlers(),
+ authzHandler.toArray(new AuthorizationHandler[0]));
+ }
+
+
+ /**
+ * This will authenticate by binding to the LDAP with the supplied user and
+ * credential. The user's DN will be looked up before performing the bind by
+ * calling {@link DnResolver#resolve(String)}. See {@link
+ * #authenticateAndAuthorize(String, Object, boolean, String[],
+ * AuthenticationResultHandler[], AuthorizationHandler[])}.
+ *
+ * @param user <code>String</code> username for bind
+ * @param credential <code>Object</code> credential for bind
+ * @param retAttrs <code>String[]</code> to return
+ * @param authHandler <code>AuthenticationResultHandler[]</code> to post
+ * process authentication results
+ * @param authzHandler <code>AuthorizationHandler[]</code> to process
+ * authorization after authentication
+ *
+ * @return <code>Attributes</code> - of authenticated user
+ *
+ * @throws NamingException if any of the ldap operations fail
+ */
+ public Attributes authenticate(
+ final String user,
+ final Object credential,
+ final String[] retAttrs,
+ final AuthenticationResultHandler[] authHandler,
+ final AuthorizationHandler[] authzHandler)
+ throws NamingException
+ {
+ return
+ this.authenticateAndAuthorize(
+ this.getDn(user),
+ credential,
+ true,
+ retAttrs,
+ authHandler,
+ authzHandler);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/AuthenticatorCli.java b/src/main/java/edu/vt/middleware/ldap/auth/AuthenticatorCli.java
new file mode 100644
index 0000000..1f1fe03
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/AuthenticatorCli.java
@@ -0,0 +1,191 @@
+/*
+ $Id: AuthenticatorCli.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import javax.naming.directory.Attributes;
+import edu.vt.middleware.ldap.AbstractCli;
+import edu.vt.middleware.ldap.bean.LdapAttributes;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import edu.vt.middleware.ldap.dsml.Dsmlv1;
+import edu.vt.middleware.ldap.dsml.Dsmlv2;
+import edu.vt.middleware.ldap.ldif.Ldif;
+import edu.vt.middleware.ldap.props.LdapConfigPropertyInvoker;
+import org.apache.commons.cli.CommandLine;
+
+/**
+ * Command line interface for authenticator operations.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class AuthenticatorCli extends AbstractCli
+{
+
+ /** Name of operation provided by this class. */
+ private static final String COMMAND_NAME = "ldapauth";
+
+
+ /**
+ * CLI entry point method.
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(final String[] args)
+ {
+ new AuthenticatorCli().performAction(args);
+ }
+
+
+ /** {@inheritDoc} */
+ protected void initOptions()
+ {
+ super.initOptions(
+ new LdapConfigPropertyInvoker(
+ AuthenticatorConfig.class,
+ AuthenticatorConfig.PROPERTIES_DOMAIN));
+ }
+
+
+ /**
+ * Initialize an AuthenticatorConfig with command line options.
+ *
+ * @param line Parsed command line arguments container.
+ *
+ * @return <code>AuthenticatorConfig</code> that has been initialized
+ *
+ * @throws Exception On errors thrown by handler.
+ */
+ protected AuthenticatorConfig initAuthenticatorConfig(final CommandLine line)
+ throws Exception
+ {
+ final AuthenticatorConfig config = new AuthenticatorConfig();
+ this.initLdapProperties(config, line);
+ if (line.hasOption(OPT_TRACE)) {
+ config.setTracePackets(System.out);
+ }
+ if (config.getBindDn() != null && config.getBindCredential() == null) {
+ // prompt the user to enter a password
+ System.out.print(
+ "Enter password for service user " + config.getBindDn() + ": ");
+
+ final String pass = (new BufferedReader(new InputStreamReader(System.in)))
+ .readLine();
+ config.setBindCredential(pass);
+ }
+ if (config.getUser() == null) {
+ // prompt for a user name
+ System.out.print("Enter user name: ");
+
+ final String user = (new BufferedReader(new InputStreamReader(System.in)))
+ .readLine();
+ config.setUser(user);
+ }
+ if (config.getCredential() == null) {
+ // prompt the user to enter a password
+ System.out.print("Enter password for user " + config.getUser() + ": ");
+
+ final String pass = (new BufferedReader(new InputStreamReader(System.in)))
+ .readLine();
+ config.setCredential(pass);
+ }
+ return config;
+ }
+
+
+ /** {@inheritDoc} */
+ protected void dispatch(final CommandLine line)
+ throws Exception
+ {
+ if (line.hasOption(OPT_DSMLV1)) {
+ this.outputDsmlv1 = true;
+ } else if (line.hasOption(OPT_DSMLV2)) {
+ this.outputDsmlv2 = true;
+ }
+ if (line.hasOption(OPT_HELP)) {
+ printHelp();
+ } else {
+ authenticate(initAuthenticatorConfig(line), line.getArgs());
+ }
+ }
+
+
+ /**
+ * Executes the authenticate operation.
+ *
+ * @param config Authenticator configuration.
+ * @param attrs Ldap attributes to return
+ *
+ * @throws Exception On errors.
+ */
+ protected void authenticate(
+ final AuthenticatorConfig config,
+ final String[] attrs)
+ throws Exception
+ {
+ final Authenticator auth = new Authenticator();
+ auth.setAuthenticatorConfig(config);
+
+ Attributes results = null;
+ try {
+ if (attrs == null || attrs.length == 0) {
+ results = auth.authenticate(null);
+ } else {
+ results = auth.authenticate(attrs);
+ }
+ if (results != null && results.size() > 0) {
+ final LdapEntry entry = LdapBeanProvider.getLdapBeanFactory()
+ .newLdapEntry();
+ final LdapResult result = LdapBeanProvider.getLdapBeanFactory()
+ .newLdapResult();
+ result.addEntry(entry);
+ entry.setDn(auth.getDn(config.getUser()));
+
+ final LdapAttributes la = LdapBeanProvider.getLdapBeanFactory()
+ .newLdapAttributes();
+ la.addAttributes(results);
+ entry.setLdapAttributes(la);
+ if (this.outputDsmlv1) {
+ (new Dsmlv1()).outputDsml(
+ result.toSearchResults().iterator(),
+ new BufferedWriter(new OutputStreamWriter(System.out)));
+ } else if (this.outputDsmlv2) {
+ (new Dsmlv2()).outputDsml(
+ result.toSearchResults().iterator(),
+ new BufferedWriter(new OutputStreamWriter(System.out)));
+ } else {
+ (new Ldif()).outputLdif(
+ result.toSearchResults().iterator(),
+ new BufferedWriter(new OutputStreamWriter(System.out)));
+ }
+ }
+ } finally {
+ if (auth != null) {
+ auth.close();
+ }
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ protected String getCommandName()
+ {
+ return COMMAND_NAME;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/AuthenticatorConfig.java b/src/main/java/edu/vt/middleware/ldap/auth/AuthenticatorConfig.java
new file mode 100644
index 0000000..480ea84
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/AuthenticatorConfig.java
@@ -0,0 +1,555 @@
+/*
+ $Id: AuthenticatorConfig.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import edu.vt.middleware.ldap.LdapConfig;
+import edu.vt.middleware.ldap.LdapConstants;
+import edu.vt.middleware.ldap.auth.handler.AuthenticationHandler;
+import edu.vt.middleware.ldap.auth.handler.AuthenticationResultHandler;
+import edu.vt.middleware.ldap.auth.handler.AuthorizationHandler;
+import edu.vt.middleware.ldap.auth.handler.BindAuthenticationHandler;
+import edu.vt.middleware.ldap.props.LdapConfigPropertyInvoker;
+import edu.vt.middleware.ldap.props.LdapProperties;
+
+/**
+ * <code>AuthenticatorConfig</code> contains all the configuration data that the
+ * <code>Authenticator</code> needs to control authentication.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class AuthenticatorConfig extends LdapConfig
+{
+
+ /** Domain to look for ldap properties in, value is {@value}. */
+ public static final String PROPERTIES_DOMAIN = "edu.vt.middleware.ldap.auth.";
+
+ /** Invoker for ldap properties. */
+ private static final LdapConfigPropertyInvoker PROPERTIES =
+ new LdapConfigPropertyInvoker(AuthenticatorConfig.class, PROPERTIES_DOMAIN);
+
+ /** Directory user field. */
+ private String[] userField = new String[] {
+ LdapConstants.DEFAULT_USER_FIELD,
+ };
+
+ /** Filter for searching for the user. */
+ private String userFilter;
+
+ /** Filter arguments for searching for the user. */
+ private Object[] userFilterArgs;
+
+ /** User to authenticate. */
+ private String user;
+
+ /** Credential for authenticating user. */
+ private Object credential;
+
+ /** Filter for authorizing user. */
+ private String authorizationFilter;
+
+ /** Filter arguments for authorizing user. */
+ private Object[] authorizationFilterArgs;
+
+ /** Whether to throw an exception if multiple DNs are found. */
+ private boolean allowMultipleDns = LdapConstants.DEFAULT_ALLOW_MULTIPLE_DNS;
+
+ /** For finding LDAP DNs. */
+ private DnResolver dnResolver = new SearchDnResolver(this);
+
+ /** Handler to process authentication. */
+ private AuthenticationHandler authenticationHandler =
+ new BindAuthenticationHandler(this);
+
+ /** Handlers to process authentications. */
+ private AuthenticationResultHandler[] authenticationResultHandlers;
+
+ /** Handlers to process authorization. */
+ private AuthorizationHandler[] authorizationHandlers;
+
+
+ /** Default constructor. */
+ public AuthenticatorConfig()
+ {
+ this.setSearchScope(SearchScope.ONELEVEL);
+ }
+
+
+ /**
+ * This will create a new <code>AuthenticatorConfig</code> with the supplied
+ * ldap url and base Strings.
+ *
+ * @param ldapUrl <code>String</code> LDAP URL
+ * @param baseDn <code>String</code> LDAP base DN
+ */
+ public AuthenticatorConfig(final String ldapUrl, final String baseDn)
+ {
+ this();
+ this.setLdapUrl(ldapUrl);
+ this.setBaseDn(baseDn);
+ }
+
+
+ /**
+ * This returns the user field(s) of the <code>Authenticator</code>.
+ *
+ * @return <code>String[]</code> - user field name(s)
+ */
+ public String[] getUserField()
+ {
+ return this.userField;
+ }
+
+
+ /**
+ * This returns the filter used to search for the user.
+ *
+ * @return <code>String</code> - filter
+ */
+ public String getUserFilter()
+ {
+ return this.userFilter;
+ }
+
+
+ /**
+ * This returns the filter arguments used to search for the user.
+ *
+ * @return <code>Object[]</code> - filter arguments
+ */
+ public Object[] getUserFilterArgs()
+ {
+ return this.userFilterArgs;
+ }
+
+
+ /**
+ * This returns the user of the <code>Authenticator</code>.
+ *
+ * @return <code>String</code> - user name
+ */
+ public String getUser()
+ {
+ return this.user;
+ }
+
+
+ /**
+ * This returns the credential of the <code>Authenticator</code>.
+ *
+ * @return <code>Object</code> - user credential
+ */
+ public Object getCredential()
+ {
+ return this.credential;
+ }
+
+
+ /**
+ * This returns the filter used to authorize users.
+ *
+ * @return <code>String</code> - filter
+ */
+ public String getAuthorizationFilter()
+ {
+ return this.authorizationFilter;
+ }
+
+
+ /**
+ * This returns the filter arguments used to authorize users.
+ *
+ * @return <code>Object[]</code> - filter arguments
+ */
+ public Object[] getAuthorizationFilterArgs()
+ {
+ return this.authorizationFilterArgs;
+ }
+
+
+ /**
+ * This returns the constructDn of the <code>Authenticator</code>.
+ *
+ * @return <code>boolean</code> - whether the DN will be constructed
+ */
+ public boolean getConstructDn()
+ {
+ return
+ this.dnResolver != null &&
+ this.dnResolver.getClass().isAssignableFrom(ConstructDnResolver.class);
+ }
+
+
+ /**
+ * This returns the allowMultipleDns of the <code>Authenticator</code>.
+ *
+ * @return <code>boolean</code> - whether an exception will be thrown if
+ * multiple DNs are found
+ */
+ public boolean getAllowMultipleDns()
+ {
+ return this.allowMultipleDns;
+ }
+
+
+ /**
+ * This returns the subtreeSearch of the <code>Authenticator</code>.
+ *
+ * @return <code>boolean</code> - whether the DN will be searched for over
+ * the entire base
+ */
+ public boolean getSubtreeSearch()
+ {
+ return SearchScope.SUBTREE == this.getSearchScope();
+ }
+
+
+ /**
+ * This returns the DN resolver.
+ *
+ * @return <code>DnResolver</code>
+ */
+ public DnResolver getDnResolver()
+ {
+ return this.dnResolver;
+ }
+
+
+ /**
+ * This returns the authentication handler.
+ *
+ * @return <code>AuthenticationHandler</code>
+ */
+ public AuthenticationHandler getAuthenticationHandler()
+ {
+ return this.authenticationHandler;
+ }
+
+
+ /**
+ * This returns the handlers to use for processing authentications.
+ *
+ * @return <code>AuthenticationResultHandler[]</code>
+ */
+ public AuthenticationResultHandler[] getAuthenticationResultHandlers()
+ {
+ return this.authenticationResultHandlers;
+ }
+
+
+ /**
+ * This returns the handlers to use for processing authorization.
+ *
+ * @return <code>AuthorizationHandler[]</code>
+ */
+ public AuthorizationHandler[] getAuthorizationHandlers()
+ {
+ return this.authorizationHandlers;
+ }
+
+
+ /**
+ * This sets the user fields for the <code>Authenticator</code>. The user
+ * field is used to lookup a user's dn.
+ *
+ * @param userField <code>String[]</code> username
+ */
+ public void setUserField(final String[] userField)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting userField: " + Arrays.toString(userField));
+ }
+ this.userField = userField;
+ }
+
+
+ /**
+ * This sets the filter used to search for users. If not set, the user field
+ * is used to build a search filter.
+ *
+ * @param userFilter <code>String</code>
+ */
+ public void setUserFilter(final String userFilter)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting userFilter: " + userFilter);
+ }
+ this.userFilter = userFilter;
+ }
+
+
+ /**
+ * This sets the filter arguments used to search for users.
+ *
+ * @param userFilterArgs <code>Object[]</code>
+ */
+ public void setUserFilterArgs(final Object[] userFilterArgs)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "setting userFilterArgs: " + Arrays.toString(userFilterArgs));
+ }
+ this.userFilterArgs = userFilterArgs;
+ }
+
+
+ /**
+ * This sets the username for the <code>Authenticator</code> to use for
+ * authentication.
+ *
+ * @param user <code>String</code> username
+ */
+ public void setUser(final String user)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting user: " + user);
+ }
+ this.user = user;
+ }
+
+ /**
+ * This sets the credential for the <code>Authenticator</code> to use for
+ * authentication.
+ *
+ * @param credential <code>Object</code>
+ */
+ public void setCredential(final Object credential)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ if (this.getLogCredentials()) {
+ this.logger.trace("setting credential: " + credential);
+ } else {
+ this.logger.trace("setting credential: <suppressed>");
+ }
+ }
+ this.credential = credential;
+ }
+
+
+ /**
+ * This sets the filter used to authorize users. If not set, no authorization
+ * is performed.
+ *
+ * @param authorizationFilter <code>String</code>
+ */
+ public void setAuthorizationFilter(final String authorizationFilter)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting authorizationFilter: " + authorizationFilter);
+ }
+ this.authorizationFilter = authorizationFilter;
+ }
+
+
+ /**
+ * This sets the filter arguments used to authorize users.
+ *
+ * @param authorizationFilterArgs <code>Object[]</code>
+ */
+ public void setAuthorizationFilterArgs(final Object[] authorizationFilterArgs)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "setting authorizationFilterArgs: " +
+ Arrays.toString(authorizationFilterArgs));
+ }
+ this.authorizationFilterArgs = authorizationFilterArgs;
+ }
+
+
+ /**
+ * This sets the constructDn for the <code>Authenticator</code>. If true, the
+ * {@link #dnResolver} is set to {@link ConstructDnResolver}. If false, the
+ * {@link #dnResolver} is set to {@link SearchDnResolver}.
+ *
+ * @param constructDn <code>boolean</code>
+ */
+ public void setConstructDn(final boolean constructDn)
+ {
+ if (constructDn) {
+ this.setDnResolver(new ConstructDnResolver());
+ } else {
+ this.setDnResolver(new SearchDnResolver());
+ }
+ }
+
+
+ /**
+ * This sets the allowMultipleDns for the <code>Authentication</code>. If
+ * false an exception will be thrown if {@link Authenticator#getDn(String)}
+ * finds more than one DN matching it's filter. Otherwise the first DN found
+ * is returned.
+ *
+ * @param allowMultipleDns <code>boolean</code>
+ */
+ public void setAllowMultipleDns(final boolean allowMultipleDns)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting allowMultipleDns: " + allowMultipleDns);
+ }
+ this.allowMultipleDns = allowMultipleDns;
+ }
+
+
+ /**
+ * This sets the subtreeSearch for the <code>Authenticator</code>. If true,
+ * the DN used for authenticating will be searched for over the entire {@link
+ * LdapConfig#getBaseDn()}. Otherwise the DN will be search for in the {@link
+ * LdapConfig#getBaseDn()} context.
+ *
+ * @param subtreeSearch <code>boolean</code>
+ */
+ public void setSubtreeSearch(final boolean subtreeSearch)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting subtreeSearch: " + subtreeSearch);
+ }
+ if (subtreeSearch) {
+ this.setSearchScope(SearchScope.SUBTREE);
+ } else {
+ this.setSearchScope(SearchScope.ONELEVEL);
+ }
+ }
+
+
+ /**
+ * This sets the DN resolver.
+ *
+ * @param resolver <code>DnResolver</code>
+ */
+ public void setDnResolver(final DnResolver resolver)
+ {
+
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting dnResolver: " + resolver);
+ }
+ this.dnResolver = resolver;
+ if (this.dnResolver != null) {
+ this.dnResolver.setAuthenticatorConfig(this);
+ }
+ }
+
+
+ /**
+ * This sets the authentication handler.
+ *
+ * @param handler <code>AuthenticationHandler</code>
+ */
+ public void setAuthenticationHandler(final AuthenticationHandler handler)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting authenticationHandler: " + handler);
+ }
+ this.authenticationHandler = handler;
+ if (this.authenticationHandler != null) {
+ this.authenticationHandler.setAuthenticatorConfig(this);
+ }
+ }
+
+
+ /**
+ * This sets the handlers for processing authentications.
+ *
+ * @param handlers <code>AuthenticationResultHandler[]</code>
+ */
+ public void setAuthenticationResultHandlers(
+ final AuthenticationResultHandler[] handlers)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting authenticationResultHandlers: " + handlers);
+ }
+ this.authenticationResultHandlers = handlers;
+ }
+
+
+ /**
+ * This sets the handlers for processing authorization.
+ *
+ * @param handlers <code>AuthorizationHandler[]</code>
+ */
+ public void setAuthorizationHandlers(final AuthorizationHandler[] handlers)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting authorizationHandlers: " + handlers);
+ }
+ this.authorizationHandlers = handlers;
+ }
+
+
+ /** {@inheritDoc} */
+ public String getPropertiesDomain()
+ {
+ return PROPERTIES_DOMAIN;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setEnvironmentProperties(final String name, final String value)
+ {
+ checkImmutable();
+ if (name != null && value != null) {
+ if (PROPERTIES.hasProperty(name)) {
+ PROPERTIES.setProperty(this, name, value);
+ } else {
+ super.setEnvironmentProperties(name, value);
+ }
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean hasEnvironmentProperty(final String name)
+ {
+ return PROPERTIES.hasProperty(name);
+ }
+
+
+ /**
+ * Create an instance of this class initialized with properties from the input
+ * stream. If the input stream is null, load properties from the default
+ * properties file.
+ *
+ * @param is to load properties from
+ *
+ * @return <code>AuthenticatorConfig</code> initialized ldap pool config
+ */
+ public static AuthenticatorConfig createFromProperties(final InputStream is)
+ {
+ final AuthenticatorConfig authConfig = new AuthenticatorConfig();
+ LdapProperties properties = null;
+ if (is != null) {
+ properties = new LdapProperties(authConfig, is);
+ } else {
+ properties = new LdapProperties(authConfig);
+ properties.useDefaultPropertiesFile();
+ }
+ properties.configure();
+ return authConfig;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/AuthorizationException.java b/src/main/java/edu/vt/middleware/ldap/auth/AuthorizationException.java
new file mode 100644
index 0000000..d0cf3a6
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/AuthorizationException.java
@@ -0,0 +1,49 @@
+/*
+ $Id: AuthorizationException.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import javax.naming.NamingException;
+
+/**
+ * <code>AuthorizationException</code> is thrown when an attempt to authorize a
+ * user fails.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class AuthorizationException extends NamingException
+{
+
+ /** serialVersionUID. */
+ private static final long serialVersionUID = -6290236661997869406L;
+
+
+ /** Default constructor. */
+ public AuthorizationException()
+ {
+ super();
+ }
+
+
+ /**
+ * This creates a new <code>AuthorizationException</code> with the supplied
+ * <code>String</code>.
+ *
+ * @param msg <code>String</code>
+ */
+ public AuthorizationException(final String msg)
+ {
+ super(msg);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/ConstructDnResolver.java b/src/main/java/edu/vt/middleware/ldap/auth/ConstructDnResolver.java
new file mode 100644
index 0000000..241f0de
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/ConstructDnResolver.java
@@ -0,0 +1,114 @@
+/*
+ $Id: ConstructDnResolver.java 1632 2010-09-28 22:42:24Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1632 $
+ Updated: $Date: 2010-09-28 23:42:24 +0100 (Tue, 28 Sep 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import java.io.Serializable;
+import javax.naming.NamingException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>ConstructDnResolver</code> creates an LDAP DN using known information
+ * about the LDAP. Specifically it concatenates the first user field with the
+ * base DN.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1632 $ $Date: 2010-09-28 23:42:24 +0100 (Tue, 28 Sep 2010) $
+ */
+public class ConstructDnResolver implements DnResolver, Serializable
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -6508789359608064771L;
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Authentication configuration. */
+ protected AuthenticatorConfig config;
+
+
+ /** Default constructor. */
+ public ConstructDnResolver() {}
+
+
+ /**
+ * This will create a new <code>ConstructDnResolver</code> with the supplied
+ * <code>AuthenticatorConfig</code>.
+ *
+ * @param authConfig <code>AuthenticatorConfig</code>
+ */
+ public ConstructDnResolver(final AuthenticatorConfig authConfig)
+ {
+ this.setAuthenticatorConfig(authConfig);
+ }
+
+
+ /**
+ * This will set the config parameters of this <code>Authenticator</code>.
+ *
+ * @param authConfig <code>AuthenticatorConfig</code>
+ */
+ public void setAuthenticatorConfig(final AuthenticatorConfig authConfig)
+ {
+ this.config = authConfig;
+ }
+
+
+ /**
+ * This returns the <code>AuthenticatorConfig</code> of the <code>
+ * Authenticator</code>.
+ *
+ * @return <code>AuthenticatorConfig</code>
+ */
+ public AuthenticatorConfig getAuthenticatorConfig()
+ {
+ return this.config;
+ }
+
+
+ /**
+ * Creates a LDAP DN by combining the userField and the base dn.
+ *
+ * @param user <code>String</code> to find dn for
+ *
+ * @return <code>String</code> - user's dn
+ *
+ * @throws NamingException if the LDAP search fails
+ */
+ public String resolve(final String user)
+ throws NamingException
+ {
+ String dn = null;
+ if (user != null && !"".equals(user)) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Constructing DN from first userfield and base");
+ }
+ dn = String.format(
+ "%s=%s,%s",
+ this.config.getUserField()[0],
+ user,
+ this.config.getBaseDn());
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("User input was empty or null");
+ }
+ }
+ return dn;
+ }
+
+
+ /** {@inheritDoc} */
+ public void close() {}
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/DnResolver.java b/src/main/java/edu/vt/middleware/ldap/auth/DnResolver.java
new file mode 100644
index 0000000..f5b188f
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/DnResolver.java
@@ -0,0 +1,58 @@
+/*
+ $Id: DnResolver.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import javax.naming.NamingException;
+
+/**
+ * <code>DnResolver</code> provides an interface for finding LDAP DNs.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface DnResolver
+{
+
+ /**
+ * Attempts to find the LDAP DN for the supplied user.
+ *
+ * @param user <code>String</code> to find dn for
+ *
+ * @return <code>String</code> - user's dn
+ *
+ * @throws NamingException if an LDAP error occurs
+ */
+ String resolve(String user)
+ throws NamingException;
+
+
+ /**
+ * Returns the authenticator config.
+ *
+ * @return authenticator configuration
+ */
+ AuthenticatorConfig getAuthenticatorConfig();
+
+
+ /**
+ * Sets the authenticator config.
+ *
+ * @param config authenticator configuration
+ */
+ void setAuthenticatorConfig(AuthenticatorConfig config);
+
+
+ /** This will close any resources associated with this resolver. */
+ void close();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/NoopDnResolver.java b/src/main/java/edu/vt/middleware/ldap/auth/NoopDnResolver.java
new file mode 100644
index 0000000..3fd6a01
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/NoopDnResolver.java
@@ -0,0 +1,73 @@
+/*
+ $Id: NoopDnResolver.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import java.io.Serializable;
+import javax.naming.NamingException;
+
+/**
+ * <code>NoopDnResolver</code> returns the user as the LDAP DN.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class NoopDnResolver implements DnResolver, Serializable
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -7832850056696716639L;
+
+
+ /** Default constructor. */
+ public NoopDnResolver() {}
+
+
+ /**
+ * This method is not implemented.
+ *
+ * @param authConfig <code>AuthenticatorConfig</code>
+ */
+ public void setAuthenticatorConfig(final AuthenticatorConfig authConfig) {}
+
+
+ /**
+ * This method is not implemented.
+ *
+ * @return null
+ */
+ public AuthenticatorConfig getAuthenticatorConfig()
+ {
+ return null;
+ }
+
+
+ /**
+ * Returns the user as the LDAP DN.
+ *
+ * @param user <code>String</code> to find dn for
+ *
+ * @return <code>String</code> - user's dn
+ *
+ * @throws NamingException if the LDAP search fails
+ */
+ public String resolve(final String user)
+ throws NamingException
+ {
+ return user;
+ }
+
+
+ /** {@inheritDoc} */
+ public void close() {}
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/SearchDnResolver.java b/src/main/java/edu/vt/middleware/ldap/auth/SearchDnResolver.java
new file mode 100644
index 0000000..81eef35
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/SearchDnResolver.java
@@ -0,0 +1,185 @@
+/*
+ $Id: SearchDnResolver.java 1634 2010-09-29 20:03:09Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1634 $
+ Updated: $Date: 2010-09-29 21:03:09 +0100 (Wed, 29 Sep 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.AbstractLdap;
+import edu.vt.middleware.ldap.SearchFilter;
+
+/**
+ * <code>SearchDnResolver</code> looks up a user's DN using an LDAP search.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1634 $ $Date: 2010-09-29 21:03:09 +0100 (Wed, 29 Sep 2010) $
+ */
+public class SearchDnResolver extends AbstractLdap<AuthenticatorConfig>
+ implements DnResolver, Serializable
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -7615995272176088807L;
+
+
+ /** Default constructor. */
+ public SearchDnResolver() {}
+
+
+ /**
+ * This will create a new <code>SearchDnResolver</code> with the supplied
+ * <code>AuthenticatorConfig</code>.
+ *
+ * @param authConfig <code>AuthenticatorConfig</code>
+ */
+ public SearchDnResolver(final AuthenticatorConfig authConfig)
+ {
+ this.setAuthenticatorConfig(authConfig);
+ }
+
+
+ /**
+ * This will set the config parameters of this <code>Authenticator</code>.
+ *
+ * @param authConfig <code>AuthenticatorConfig</code>
+ */
+ public void setAuthenticatorConfig(final AuthenticatorConfig authConfig)
+ {
+ super.setLdapConfig(authConfig);
+ }
+
+
+ /**
+ * This returns the <code>AuthenticatorConfig</code> of the <code>
+ * Authenticator</code>.
+ *
+ * @return <code>AuthenticatorConfig</code>
+ */
+ public AuthenticatorConfig getAuthenticatorConfig()
+ {
+ return this.config;
+ }
+
+
+ /**
+ * This will attempt to find the dn for the supplied user. {@link
+ * AuthenticatorConfig#getUserFilter()} or {@link
+ * AuthenticatorConfig#getUserField()} is used to look up the dn. If a filter
+ * is used, the user is provided as the {0} variable filter argument. If a
+ * field is used, the filter is built by ORing the fields together. If more
+ * than one entry matches the search, the result is controlled by {@link
+ * AuthenticatorConfig#setAllowMultipleDns(boolean)}.
+ *
+ * @param user <code>String</code> to find dn for
+ *
+ * @return <code>String</code> - user's dn
+ *
+ * @throws NamingException if the LDAP search fails
+ */
+ public String resolve(final String user)
+ throws NamingException
+ {
+ String dn = null;
+ if (user != null && !"".equals(user)) {
+ // create the search filter
+ final SearchFilter filter = new SearchFilter();
+ if (this.config.getUserFilter() != null) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Looking up DN using userFilter");
+ }
+ filter.setFilter(this.config.getUserFilter());
+ filter.setFilterArgs(this.config.getUserFilterArgs());
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Looking up DN using userField");
+ }
+ if (
+ this.config.getUserField() == null ||
+ this.config.getUserField().length == 0) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Invalid userField, cannot be null or empty.");
+ }
+ } else {
+ final StringBuffer searchFilter = new StringBuffer();
+ if (this.config.getUserField().length > 1) {
+ searchFilter.append("(|");
+ for (int i = 0; i < this.config.getUserField().length; i++) {
+ searchFilter.append("(").append(this.config.getUserField()[i])
+ .append("={0})");
+ }
+ searchFilter.append(")");
+ } else {
+ searchFilter.append("(").append(this.config.getUserField()[0])
+ .append("={0})");
+ }
+ filter.setFilter(searchFilter.toString());
+ }
+ }
+
+ if (filter.getFilter() != null) {
+ // make user the first filter arg
+ final List<Object> filterArgs = new ArrayList<Object>();
+ filterArgs.add(user);
+ filterArgs.addAll(filter.getFilterArgs());
+
+ final Iterator<SearchResult> answer = this.search(
+ this.config.getBaseDn(),
+ filter.getFilter(),
+ filterArgs.toArray(),
+ this.config.getSearchControls(new String[0]),
+ this.config.getSearchResultHandlers());
+ // return first match, otherwise user doesn't exist
+ if (answer != null && answer.hasNext()) {
+ final SearchResult sr = answer.next();
+ dn = sr.getName();
+ if (answer.hasNext()) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "Multiple results found for user: " + user + " using filter: " +
+ filter);
+ }
+ if (!this.config.getAllowMultipleDns()) {
+ throw new NamingException("Found more than (1) DN for: " + user);
+ }
+ }
+ } else {
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info(
+ "Search for user: " + user + " failed using filter: " +
+ filter.getFilter());
+ }
+ }
+ } else {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("DN search filter not found, no search performed");
+ }
+ }
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("User input was empty or null");
+ }
+ }
+ return dn;
+ }
+
+
+ /** {@inheritDoc} */
+ public void close()
+ {
+ super.close();
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/handler/AbstractAuthenticationHandler.java b/src/main/java/edu/vt/middleware/ldap/auth/handler/AbstractAuthenticationHandler.java
new file mode 100644
index 0000000..514e867
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/handler/AbstractAuthenticationHandler.java
@@ -0,0 +1,56 @@
+/*
+ $Id: AbstractAuthenticationHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth.handler;
+
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.auth.AuthenticatorConfig;
+import edu.vt.middleware.ldap.handler.ConnectionHandler;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * AbstractAuthenticationHandler provides a base implementation for
+ * authentication handlers.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public abstract class AbstractAuthenticationHandler
+ implements AuthenticationHandler
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Authenticator configuration. */
+ protected AuthenticatorConfig config;
+
+
+ /** {@inheritDoc} */
+ public void setAuthenticatorConfig(final AuthenticatorConfig ac)
+ {
+ this.config = ac;
+ }
+
+
+ /** {@inheritDoc} */
+ public abstract void authenticate(
+ final ConnectionHandler ch,
+ final AuthenticationCriteria ac)
+ throws NamingException;
+
+
+ /** {@inheritDoc} */
+ public abstract AuthenticationHandler newInstance();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthenticationCriteria.java b/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthenticationCriteria.java
new file mode 100644
index 0000000..5241ee7
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthenticationCriteria.java
@@ -0,0 +1,102 @@
+/*
+ $Id: AuthenticationCriteria.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth.handler;
+
+/**
+ * <code>AuthenticationCriteria</code> contains the attributes used to perform
+ * authentications.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class AuthenticationCriteria
+{
+
+ /** dn. */
+ private String dn;
+
+ /** credential. */
+ private Object credential;
+
+
+ /** Default constructor. */
+ public AuthenticationCriteria() {}
+
+
+ /**
+ * Creates a new authentication criteria with the supplied dn.
+ *
+ * @param s to set dn
+ */
+ public AuthenticationCriteria(final String s)
+ {
+ this.dn = s;
+ }
+
+
+ /**
+ * Gets the dn.
+ *
+ * @return dn
+ */
+ public String getDn()
+ {
+ return this.dn;
+ }
+
+
+ /**
+ * Sets the dn.
+ *
+ * @param s to set dn
+ */
+ public void setDn(final String s)
+ {
+ this.dn = s;
+ }
+
+
+ /**
+ * Gets the credential.
+ *
+ * @return credential
+ */
+ public Object getCredential()
+ {
+ return this.credential;
+ }
+
+
+ /**
+ * Sets the credential.
+ *
+ * @param o to set credential
+ */
+ public void setCredential(final Object o)
+ {
+ this.credential = o;
+ }
+
+
+ /**
+ * This returns a string representation of this search criteria.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return String.format("dn=%s,credential=%s", this.dn, this.credential);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthenticationHandler.java b/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthenticationHandler.java
new file mode 100644
index 0000000..8286c61
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthenticationHandler.java
@@ -0,0 +1,62 @@
+/*
+ $Id: AuthenticationHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth.handler;
+
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.auth.AuthenticatorConfig;
+import edu.vt.middleware.ldap.handler.ConnectionHandler;
+
+/**
+ * <code>AuthenticationHandler</code> provides an interface for LDAP
+ * authentication implementations.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public interface AuthenticationHandler
+{
+
+
+ /**
+ * Sets the authenticator configuration.
+ *
+ * @param ac authenticator config
+ */
+ void setAuthenticatorConfig(AuthenticatorConfig ac);
+
+
+ /**
+ * Perform an ldap authentication. Implementations should throw <code>
+ * AuthenticationException</code> to indicate an authentication failure. The
+ * resulting <code>LdapContext</code> can be retrieved from the connection
+ * handler if it is needed.
+ *
+ * @param ch <code>ConnectionHandler</code> to communicate with the LDAP
+ * @param ac <code>AuthenticationCriteria</code> to perform the
+ * authentication with
+ *
+ * @throws AuthenticationException if authentication fails
+ * @throws NamingException if an LDAP error occurs
+ */
+ void authenticate(ConnectionHandler ch, AuthenticationCriteria ac)
+ throws NamingException;
+
+
+ /**
+ * Returns a separate instance of this authentication handler.
+ *
+ * @return authentication handler
+ */
+ AuthenticationHandler newInstance();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthenticationResultHandler.java b/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthenticationResultHandler.java
new file mode 100644
index 0000000..11ee9d6
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthenticationResultHandler.java
@@ -0,0 +1,35 @@
+/*
+ $Id: AuthenticationResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth.handler;
+
+/**
+ * AuthenticationResultHandler provides post processing of authentication
+ * results.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public interface AuthenticationResultHandler
+{
+
+
+ /**
+ * Process the results from an ldap authentication.
+ *
+ * @param ac <code>AuthenticationCriteria</code> used to perform the
+ * authentication
+ * @param success <code>boolean</code> whether the authentication succeeded
+ */
+ void process(AuthenticationCriteria ac, boolean success);
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthorizationHandler.java b/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthorizationHandler.java
new file mode 100644
index 0000000..bb49179
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/handler/AuthorizationHandler.java
@@ -0,0 +1,46 @@
+/*
+ $Id: AuthorizationHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth.handler;
+
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+
+/**
+ * AuthorizationHandler provides processing of authorization queries after
+ * authentication has succeeded.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public interface AuthorizationHandler
+{
+
+
+ /**
+ * Process an authorization after an ldap authentication. The supplied
+ * LdapContext should <b>not</b> be closed in this method. Implementations
+ * should throw <code>AuthorizationException</code> to indicate an
+ * authorization failure.
+ *
+ * @param ac <code>AuthenticationCriteria</code> used to perform the
+ * authentication
+ * @param ctx <code>LdapContext</code> authenticated context used to perform
+ * the bind
+ *
+ * @throws AuthorizationException if authorization fails
+ * @throws NamingException if an LDAP error occurs
+ */
+ void process(AuthenticationCriteria ac, LdapContext ctx)
+ throws NamingException;
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/handler/BindAuthenticationHandler.java b/src/main/java/edu/vt/middleware/ldap/auth/handler/BindAuthenticationHandler.java
new file mode 100644
index 0000000..8540abb
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/handler/BindAuthenticationHandler.java
@@ -0,0 +1,62 @@
+/*
+ $Id: BindAuthenticationHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth.handler;
+
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.auth.AuthenticatorConfig;
+import edu.vt.middleware.ldap.handler.ConnectionHandler;
+
+/**
+ * <code>BindAuthenticationHandler</code> provides an LDAP authentication
+ * implementation that leverages the LDAP bind operation.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class BindAuthenticationHandler extends AbstractAuthenticationHandler
+{
+
+
+ /** Default constructor. */
+ public BindAuthenticationHandler() {}
+
+
+ /**
+ * Creates a new <code>BindAuthenticationHandler</code> with the supplied
+ * authenticator config.
+ *
+ * @param ac authenticator config
+ */
+ public BindAuthenticationHandler(final AuthenticatorConfig ac)
+ {
+ this.setAuthenticatorConfig(ac);
+ }
+
+
+ /** {@inheritDoc} */
+ public void authenticate(
+ final ConnectionHandler ch,
+ final AuthenticationCriteria ac)
+ throws NamingException
+ {
+ ch.connect(ac.getDn(), ac.getCredential());
+ }
+
+
+ /** {@inheritDoc} */
+ public BindAuthenticationHandler newInstance()
+ {
+ return new BindAuthenticationHandler(this.config);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/handler/CompareAuthenticationHandler.java b/src/main/java/edu/vt/middleware/ldap/auth/handler/CompareAuthenticationHandler.java
new file mode 100644
index 0000000..50e1129
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/handler/CompareAuthenticationHandler.java
@@ -0,0 +1,128 @@
+/*
+ $Id: CompareAuthenticationHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth.handler;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import javax.naming.AuthenticationException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.LdapConfig;
+import edu.vt.middleware.ldap.LdapUtil;
+import edu.vt.middleware.ldap.auth.AuthenticatorConfig;
+import edu.vt.middleware.ldap.handler.ConnectionHandler;
+
+/**
+ * <code>CompareAuthenticationHandler</code> provides an LDAP authentication
+ * implementation that leverages a compare operation against the userPassword
+ * attribute. The default password scheme used is 'SHA'.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class CompareAuthenticationHandler extends AbstractAuthenticationHandler
+{
+
+ /** Maximum digest size. Value is {@value}. */
+ private static final int DIGEST_SIZE = 256;
+
+ /** Password scheme. Default value is {@value}. */
+ private String passwordScheme = "SHA";
+
+
+ /** Default constructor. */
+ public CompareAuthenticationHandler() {}
+
+
+ /**
+ * Creates a new <code>CompareAuthenticationHandler</code> with the supplied
+ * authenticator config.
+ *
+ * @param ac authenticator config
+ */
+ public CompareAuthenticationHandler(final AuthenticatorConfig ac)
+ {
+ this.setAuthenticatorConfig(ac);
+ }
+
+
+ /**
+ * Returns the password scheme.
+ *
+ * @return password scheme
+ */
+ public String getPasswordScheme()
+ {
+ return this.passwordScheme;
+ }
+
+
+ /**
+ * Sets the password scheme. Must equal a known message digest algorithm.
+ *
+ * @param s password scheme
+ */
+ public void setPasswordScheme(final String s)
+ {
+ this.passwordScheme = s;
+ }
+
+
+ /** {@inheritDoc} */
+ public void authenticate(
+ final ConnectionHandler ch,
+ final AuthenticationCriteria ac)
+ throws NamingException
+ {
+ byte[] hash = new byte[DIGEST_SIZE];
+ try {
+ final MessageDigest md = MessageDigest.getInstance(this.passwordScheme);
+ md.update(((String) ac.getCredential()).getBytes());
+ hash = md.digest();
+ } catch (NoSuchAlgorithmException e) {
+ throw new NamingException(e.getMessage());
+ }
+
+ ch.connect(this.config.getBindDn(), this.config.getBindCredential());
+
+ NamingEnumeration<SearchResult> en = null;
+ try {
+ en = ch.getLdapContext().search(
+ ac.getDn(),
+ "userPassword={0}",
+ new Object[] {
+ String.format(
+ "{%s}%s",
+ this.passwordScheme,
+ LdapUtil.base64Encode(hash)).getBytes(),
+ },
+ LdapConfig.getCompareSearchControls());
+ if (!en.hasMore()) {
+ throw new AuthenticationException("Compare authentication failed.");
+ }
+ } finally {
+ if (en != null) {
+ en.close();
+ }
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public CompareAuthenticationHandler newInstance()
+ {
+ return new CompareAuthenticationHandler(this.config);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/auth/handler/CompareAuthorizationHandler.java b/src/main/java/edu/vt/middleware/ldap/auth/handler/CompareAuthorizationHandler.java
new file mode 100644
index 0000000..e9da2b3
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/auth/handler/CompareAuthorizationHandler.java
@@ -0,0 +1,124 @@
+/*
+ $Id: CompareAuthorizationHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth.handler;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapContext;
+import edu.vt.middleware.ldap.LdapConfig;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.auth.AuthorizationException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * CompareAuthorizationHandler performs a compare operation with a custom
+ * filter. The DN of the authenticated user is automatically provided as the {0}
+ * variable in the search filter arguments.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class CompareAuthorizationHandler implements AuthorizationHandler
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+
+ /** Search filter. */
+ private SearchFilter searchFilter;
+
+
+ /** Default constructor. */
+ public CompareAuthorizationHandler() {}
+
+
+ /**
+ * Creates a new <code>CompareAuthorizationHandler</code> with the supplied
+ * search filter.
+ *
+ * @param sf <code>SearchFilter</code>
+ */
+ public CompareAuthorizationHandler(final SearchFilter sf)
+ {
+ this.searchFilter = sf;
+ }
+
+
+ /**
+ * Returns the search filter.
+ *
+ * @return <code>SearchFilter</code>
+ */
+ public SearchFilter getSearchFilter()
+ {
+ return this.searchFilter;
+ }
+
+
+ /**
+ * Sets the search filter.
+ *
+ * @param sf <code>SearchFilter</code>
+ */
+ public void setSearchFilter(final SearchFilter sf)
+ {
+ this.searchFilter = sf;
+ }
+
+
+ /** {@inheritDoc} */
+ public void process(final AuthenticationCriteria ac, final LdapContext ctx)
+ throws NamingException
+ {
+ // make DN the first filter arg
+ final List<Object> filterArgs = new ArrayList<Object>();
+ filterArgs.add(ac.getDn());
+ filterArgs.addAll(this.searchFilter.getFilterArgs());
+
+ // perform ldap compare operation
+ NamingEnumeration<SearchResult> results = null;
+ try {
+ results = ctx.search(
+ ac.getDn(),
+ this.searchFilter.getFilter(),
+ filterArgs.toArray(),
+ LdapConfig.getCompareSearchControls());
+ if (!results.hasMore()) {
+ throw new AuthorizationException("Compare failed");
+ }
+ } finally {
+ if (results != null) {
+ results.close();
+ }
+ }
+ }
+
+
+ /**
+ * Provides a descriptive string representation of this authorization handler.
+ *
+ * @return String of the form $Classname::$filter.
+ */
+ @Override
+ public String toString()
+ {
+ return
+ String.format("%s::%s", this.getClass().getName(), this.searchFilter);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapAttribute.java b/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapAttribute.java
new file mode 100644
index 0000000..1acb8c1
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapAttribute.java
@@ -0,0 +1,160 @@
+/*
+ $Id: AbstractLdapAttribute.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import java.util.Set;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.BasicAttribute;
+import edu.vt.middleware.ldap.LdapUtil;
+
+/**
+ * <code>AbstractLdapAttribute</code> provides a base implementation of <code>
+ * LdapAttribute</code> where the underlying values are backed by a <code>
+ * Set</code>.
+ *
+ * @param <T> type of backing set
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public abstract class AbstractLdapAttribute<T extends Set<Object>>
+ extends AbstractLdapBean implements LdapAttribute
+{
+
+ /** hash code seed. */
+ protected static final int HASH_CODE_SEED = 41;
+
+ /** Name for this attribute. */
+ protected String name;
+
+ /** Values for this attribute. */
+ protected Set<Object> values;
+
+
+ /**
+ * Creates a new <code>AbstractLdapAttribute</code> with the supplied ldap
+ * bean factory.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public AbstractLdapAttribute(final LdapBeanFactory lbf)
+ {
+ super(lbf);
+ }
+
+
+ /** {@inheritDoc} */
+ public String getName()
+ {
+ return this.name;
+ }
+
+
+ /** {@inheritDoc} */
+ public Set<Object> getValues()
+ {
+ return this.values;
+ }
+
+
+ /** {@inheritDoc} */
+ public abstract Set<String> getStringValues();
+
+
+ /** {@inheritDoc} */
+ public void setAttribute(final Attribute attribute)
+ throws NamingException
+ {
+ this.setName(attribute.getID());
+
+ final NamingEnumeration<?> ne = attribute.getAll();
+ while (ne.hasMore()) {
+ this.values.add(ne.next());
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void setName(final String name)
+ {
+ this.name = name;
+ }
+
+
+ /** {@inheritDoc} */
+ public int hashCode()
+ {
+ int hc = HASH_CODE_SEED;
+ if (this.name != null) {
+ hc += this.name.hashCode();
+ }
+ for (String s : this.getStringValues()) {
+ if (s != null) {
+ hc += s.hashCode();
+ }
+ }
+ return hc;
+ }
+
+
+ /**
+ * This returns a string representation of this object.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s%s", this.name, this.values);
+ }
+
+
+ /** {@inheritDoc} */
+ public Attribute toAttribute()
+ {
+ final Attribute attribute = new BasicAttribute(this.name);
+ for (Object o : this.values) {
+ attribute.add(o);
+ }
+ return attribute;
+ }
+
+
+ /**
+ * Converts the underlying set of objects to a set of strings. Objects of type
+ * byte[] are base64 encoded. Objects which are not of type String or byte[]
+ * are converted using Object.toString().
+ *
+ * @param stringValues <code>Set</code> to populate with strings
+ */
+ protected void convertValuesToString(final Set<String> stringValues)
+ {
+ for (Object o : this.values) {
+ if (o != null) {
+ if (o instanceof String) {
+ stringValues.add((String) o);
+ } else if (o instanceof byte[]) {
+ final String encodedValue = LdapUtil.base64Encode((byte[]) o);
+ if (encodedValue != null) {
+ stringValues.add(encodedValue);
+ }
+ } else {
+ stringValues.add(o.toString());
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapAttributes.java b/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapAttributes.java
new file mode 100644
index 0000000..ba967b0
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapAttributes.java
@@ -0,0 +1,216 @@
+/*
+ $Id: AbstractLdapAttributes.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+
+/**
+ * <code>AbstractLdapAttributes</code> provides a base implementation of <code>
+ * LdapAttributes</code> where the underlying attributes are backed by a <code>
+ * Map</code>.
+ *
+ * @param <T> type of backing map
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public abstract class
+AbstractLdapAttributes<T extends Map<String, LdapAttribute>>
+ extends AbstractLdapBean implements LdapAttributes
+{
+
+ /** Whether to ignore case when creating <code>BasicAttributes</code>. */
+ public static final boolean DEFAULT_IGNORE_CASE = true;
+
+ /** hash code seed. */
+ protected static final int HASH_CODE_SEED = 42;
+
+ /** Attributes contained in this bean. */
+ protected T attributes;
+
+
+ /**
+ * Creates a new <code>AbstractLdapAttributes</code> with the supplied ldap
+ * bean factory.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public AbstractLdapAttributes(final LdapBeanFactory lbf)
+ {
+ super(lbf);
+ }
+
+
+ /** {@inheritDoc} */
+ public Collection<LdapAttribute> getAttributes()
+ {
+ return this.attributes.values();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapAttribute getAttribute(final String name)
+ {
+ return this.attributes.get(name);
+ }
+
+
+ /** {@inheritDoc} */
+ public String[] getAttributeNames()
+ {
+ return this.attributes.keySet().toArray(new String[0]);
+ }
+
+
+ /** {@inheritDoc} */
+ public void addAttribute(final LdapAttribute a)
+ {
+ this.attributes.put(a.getName(), a);
+ }
+
+
+ /** {@inheritDoc} */
+ public void addAttribute(final String name, final Object value)
+ {
+ final LdapAttribute la = this.beanFactory.newLdapAttribute();
+ la.setName(name);
+ la.getValues().add(value);
+ this.addAttribute(la);
+ }
+
+
+ /** {@inheritDoc} */
+ public void addAttribute(final String name, final List<?> values)
+ {
+ final LdapAttribute la = this.beanFactory.newLdapAttribute();
+ la.setName(name);
+ la.getValues().addAll(values);
+ this.addAttribute(la);
+ }
+
+
+ /** {@inheritDoc} */
+ public void addAttributes(final Collection<LdapAttribute> c)
+ {
+ for (LdapAttribute la : c) {
+ this.addAttribute(la);
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void addAttributes(final Attributes a)
+ throws NamingException
+ {
+ final NamingEnumeration<? extends Attribute> ne = a.getAll();
+ while (ne.hasMore()) {
+ final LdapAttribute la = this.beanFactory.newLdapAttribute();
+ la.setAttribute(ne.next());
+ this.addAttribute(la);
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void removeAttribute(final LdapAttribute a)
+ {
+ this.attributes.remove(a.getName());
+ }
+
+
+ /** {@inheritDoc} */
+ public void removeAttribute(final String name)
+ {
+ this.attributes.remove(name);
+ }
+
+
+ /** {@inheritDoc} */
+ public void removeAttributes(final Collection<LdapAttribute> c)
+ {
+ for (LdapAttribute la : c) {
+ this.removeAttribute(la);
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void removeAttributes(final Attributes a)
+ throws NamingException
+ {
+ final NamingEnumeration<? extends Attribute> ne = a.getAll();
+ while (ne.hasMore()) {
+ final LdapAttribute la = this.beanFactory.newLdapAttribute();
+ la.setAttribute(ne.next());
+ this.removeAttribute(la);
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public int size()
+ {
+ return this.attributes.size();
+ }
+
+
+ /** {@inheritDoc} */
+ public void clear()
+ {
+ this.attributes.clear();
+ }
+
+
+ /** {@inheritDoc} */
+ public int hashCode()
+ {
+ int hc = HASH_CODE_SEED;
+ for (LdapAttribute a : this.attributes.values()) {
+ if (a != null) {
+ hc += a.hashCode();
+ }
+ }
+ return hc;
+ }
+
+
+ /**
+ * This returns a string representation of this object.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s", this.attributes.values());
+ }
+
+
+ /** {@inheritDoc} */
+ public Attributes toAttributes()
+ {
+ final Attributes attributes = new BasicAttributes(DEFAULT_IGNORE_CASE);
+ for (LdapAttribute a : this.attributes.values()) {
+ attributes.put(a.toAttribute());
+ }
+ return attributes;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapBean.java b/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapBean.java
new file mode 100644
index 0000000..c68e6b6
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapBean.java
@@ -0,0 +1,73 @@
+/*
+ $Id: AbstractLdapBean.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AbstractLdapBean</code> provides common implementations to other bean
+ * objects.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public abstract class AbstractLdapBean
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /** Factory for creating ldap beans. */
+ protected final LdapBeanFactory beanFactory;
+
+
+ /**
+ * Creates a new <code>AbstractLdapBean</code> with the supplied ldap bean
+ * factory.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public AbstractLdapBean(final LdapBeanFactory lbf)
+ {
+ this.beanFactory = lbf;
+ }
+
+
+ /**
+ * Returns whether the supplied <code>Object</code> contains the same data as
+ * this bean.
+ *
+ * @param o <code>Object</code>
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean equals(final Object o)
+ {
+ if (o == null) {
+ return false;
+ }
+ return
+ o == this ||
+ (this.getClass() == o.getClass() && o.hashCode() == this.hashCode());
+ }
+
+
+ /**
+ * This returns the hash code for this object.
+ *
+ * @return <code>int</code>
+ */
+ public abstract int hashCode();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapEntry.java b/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapEntry.java
new file mode 100644
index 0000000..49fa16a
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapEntry.java
@@ -0,0 +1,123 @@
+/*
+ $Id: AbstractLdapEntry.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+
+/**
+ * <code>AbstractLdapEntry</code> provides a base implementation of <code>
+ * LdapEntry</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public abstract class AbstractLdapEntry extends AbstractLdapBean
+ implements LdapEntry
+{
+
+ /** hash code seed. */
+ protected static final int HASH_CODE_SEED = 43;
+
+ /** Distinguished name for this entry. */
+ protected String dn;
+
+ /** Attributes contained in this entry. */
+ protected LdapAttributes ldapAttributes;
+
+
+ /**
+ * Creates a new <code>AbstractLdapEntry</code> with the supplied ldap bean
+ * factory.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public AbstractLdapEntry(final LdapBeanFactory lbf)
+ {
+ super(lbf);
+ }
+
+
+ /** {@inheritDoc} */
+ public String getDn()
+ {
+ return this.dn;
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapAttributes getLdapAttributes()
+ {
+ return this.ldapAttributes;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setEntry(final SearchResult sr)
+ throws NamingException
+ {
+ this.setDn(sr.getName());
+
+ final LdapAttributes la = this.beanFactory.newLdapAttributes();
+ la.addAttributes(sr.getAttributes());
+ this.setLdapAttributes(la);
+ }
+
+
+ /** {@inheritDoc} */
+ public void setDn(final String dn)
+ {
+ this.dn = dn;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setLdapAttributes(final LdapAttributes a)
+ {
+ if (a != null) {
+ this.ldapAttributes = a;
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public int hashCode()
+ {
+ int hc = HASH_CODE_SEED;
+ if (this.getDn() != null) {
+ hc += this.getDn().hashCode();
+ }
+ hc += this.getLdapAttributes().hashCode();
+ return hc;
+ }
+
+
+ /**
+ * This returns a string representation of this object.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return String.format("dn=>%s%s", this.dn, this.ldapAttributes);
+ }
+
+
+ /** {@inheritDoc} */
+ public SearchResult toSearchResult()
+ {
+ return new SearchResult(this.dn, null, this.ldapAttributes.toAttributes());
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapResult.java b/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapResult.java
new file mode 100644
index 0000000..f39ed6d
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/AbstractLdapResult.java
@@ -0,0 +1,171 @@
+/*
+ $Id: AbstractLdapResult.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+
+/**
+ * <code>AbstractLdapResult</code> provides a base implementation of <code>
+ * LdapResult</code> where the underlying entries are backed by a <code>
+ * Map</code>.
+ *
+ * @param <T> type of backing map
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public abstract class AbstractLdapResult<T extends Map<String, LdapEntry>>
+ extends AbstractLdapBean implements LdapResult
+{
+
+ /** hash code seed. */
+ protected static final int HASH_CODE_SEED = 44;
+
+ /** Entries contained in this result. */
+ protected T entries;
+
+
+ /**
+ * Creates a new <code>AbstractLdapResult</code> with the supplied ldap bean
+ * factory.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public AbstractLdapResult(final LdapBeanFactory lbf)
+ {
+ super(lbf);
+ }
+
+
+ /** {@inheritDoc} */
+ public Collection<LdapEntry> getEntries()
+ {
+ return this.entries.values();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapEntry getEntry(final String dn)
+ {
+ return this.entries.get(dn);
+ }
+
+
+ /** {@inheritDoc} */
+ public void addEntry(final LdapEntry e)
+ {
+ this.entries.put(e.getDn(), e);
+ }
+
+
+ /** {@inheritDoc} */
+ public void addEntry(final SearchResult sr)
+ throws NamingException
+ {
+ final LdapEntry le = this.beanFactory.newLdapEntry();
+ le.setEntry(sr);
+ this.addEntry(le);
+ }
+
+
+ /** {@inheritDoc} */
+ public void addEntries(final Collection<LdapEntry> c)
+ {
+ for (LdapEntry e : c) {
+ this.entries.put(e.getDn(), e);
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void addEntries(final NamingEnumeration<SearchResult> ne)
+ throws NamingException
+ {
+ while (ne.hasMore()) {
+ final LdapEntry le = this.beanFactory.newLdapEntry();
+ le.setEntry(ne.next());
+ this.addEntry(le);
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void addEntries(final Iterator<SearchResult> i)
+ throws NamingException
+ {
+ while (i.hasNext()) {
+ final LdapEntry le = this.beanFactory.newLdapEntry();
+ le.setEntry(i.next());
+ this.addEntry(le);
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public int size()
+ {
+ return this.entries.size();
+ }
+
+
+ /** {@inheritDoc} */
+ public void clear()
+ {
+ this.entries.clear();
+ }
+
+
+ /** {@inheritDoc} */
+ public int hashCode()
+ {
+ int hc = HASH_CODE_SEED;
+ for (LdapEntry e : this.entries.values()) {
+ if (e != null) {
+ hc += e.hashCode();
+ }
+ }
+ return hc;
+ }
+
+
+ /**
+ * This returns a string representation of this object.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s", this.entries.values());
+ }
+
+
+ /** {@inheritDoc} */
+ public List<SearchResult> toSearchResults()
+ {
+ final List<SearchResult> results = new ArrayList<SearchResult>(
+ this.entries.size());
+ for (LdapEntry e : this.entries.values()) {
+ results.add(e.toSearchResult());
+ }
+ return results;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/LdapAttribute.java b/src/main/java/edu/vt/middleware/ldap/bean/LdapAttribute.java
new file mode 100644
index 0000000..2912a4a
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/LdapAttribute.java
@@ -0,0 +1,84 @@
+/*
+ $Id: LdapAttribute.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import java.util.Set;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+
+/**
+ * <code>LdapAttribute</code> represents a single ldap attribute. Ldap attribute
+ * values must be unique per http://tools.ietf.org/html/rfc4512#section-2.3. For
+ * any given attribute, the values must all be of the same type.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface LdapAttribute
+{
+
+
+ /**
+ * This returns the name of this <code>LdapAttribute</code>.
+ *
+ * @return <code>String</code>
+ */
+ String getName();
+
+
+ /**
+ * This returns the value(s) of this <code>LdapAttribute</code>.
+ *
+ * @return <code>Set</code>
+ */
+ Set<Object> getValues();
+
+
+ /**
+ * This returns the value(s) of this <code>LdapAttribute</code> Values are
+ * encoded in base64 format if the underlying value is of type byte[]. The
+ * returned set is unmodifiable.
+ *
+ * @return unmodifiable <code>Set</code>
+ */
+ Set<String> getStringValues();
+
+
+ /**
+ * This sets this <code>LdapAttribute</code> using the supplied attribute.
+ *
+ * @param attribute <code>Attribute</code>
+ *
+ * @throws NamingException if the attribute values cannot be read
+ */
+ void setAttribute(final Attribute attribute)
+ throws NamingException;
+
+
+ /**
+ * This sets the name of this <code>LdapAttribute</code>.
+ *
+ * @param name <code>String</code>
+ */
+ void setName(final String name);
+
+
+ /**
+ * This returns an <code>Attribute</code> that represents the values in this
+ * <code>LdapAttribute</code>.
+ *
+ * @return <code>Attribute</code>
+ */
+ Attribute toAttribute();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/LdapAttributes.java b/src/main/java/edu/vt/middleware/ldap/bean/LdapAttributes.java
new file mode 100644
index 0000000..0185e54
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/LdapAttributes.java
@@ -0,0 +1,166 @@
+/*
+ $Id: LdapAttributes.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import java.util.Collection;
+import java.util.List;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+
+/**
+ * <code>LdapAttributes</code> represents a collection of ldap attribute.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface LdapAttributes
+{
+
+
+ /**
+ * This returns a <code>Collection</code> of <code>LdapAttribute</code> for
+ * this <code>LdapAttributes</code>.
+ *
+ * @return <code>List</code>
+ */
+ Collection<LdapAttribute> getAttributes();
+
+
+ /**
+ * This returns the <code>LdapAttribute</code> for this <code>
+ * LdapAttributes</code> with the supplied name.
+ *
+ * @param name <code>String</code>
+ *
+ * @return <code>LdapAttribute</code>
+ */
+ LdapAttribute getAttribute(final String name);
+
+
+ /**
+ * This returns an array of all the attribute names for this <code>
+ * LdapAttributes</code>.
+ *
+ * @return <code>String[]</code>
+ */
+ String[] getAttributeNames();
+
+
+ /**
+ * This adds a new attribute to this <code>LdapAttributes</code>.
+ *
+ * @param a <code>LdapAttribute</code>
+ */
+ void addAttribute(final LdapAttribute a);
+
+
+ /**
+ * This adds a new attribute to this <code>LdapAttributes</code> with the
+ * supplied name and value.
+ *
+ * @param name <code>String</code>
+ * @param value <code>Object</code>
+ */
+ void addAttribute(final String name, final Object value);
+
+
+ /**
+ * This adds a new attribute to this <code>LdapAttributes</code> with the
+ * supplied name and values.
+ *
+ * @param name <code>String</code>
+ * @param values <code>List</code>
+ */
+ void addAttribute(final String name, final List<?> values);
+
+
+ /**
+ * This adds a <code>Collection</code> of attributes to this <code>
+ * LdapAttributes</code>. The collection should contain <code>
+ * LdapAttribute</code> objects.
+ *
+ * @param c <code>Collection</code>
+ */
+ void addAttributes(final Collection<LdapAttribute> c);
+
+
+ /**
+ * This adds the attributes in the supplied <code>Attributes</code> to this
+ * <code>LdapAttributes</code>.
+ *
+ * @param a <code>Attributes</code>
+ *
+ * @throws NamingException if the attributes cannot be read
+ */
+ void addAttributes(final Attributes a)
+ throws NamingException;
+
+
+ /**
+ * This removes an attribute from this <code>LdapAttributes</code>.
+ *
+ * @param a <code>LdapAttribute</code>
+ */
+ void removeAttribute(final LdapAttribute a);
+
+
+ /**
+ * This removes the attribute with the supplied name.
+ *
+ * @param name <code>String</code>
+ */
+ void removeAttribute(final String name);
+
+
+ /**
+ * This removes a <code>Collection</code> of attributes from this <code>
+ * LdapAttributes</code>. The collection should contain <code>
+ * LdapAttribute</code> objects.
+ *
+ * @param c <code>Collection</code>
+ */
+ void removeAttributes(final Collection<LdapAttribute> c);
+
+
+ /**
+ * This removes the attributes in the supplied <code>Attributes</code> from
+ * this <code>LdapAttributes</code>.
+ *
+ * @param a <code>Attributes</code>
+ *
+ * @throws NamingException if the attributes cannot be read
+ */
+ void removeAttributes(final Attributes a)
+ throws NamingException;
+
+
+ /**
+ * This returns the number of attributes in this attributes.
+ *
+ * @return <code>int</code>
+ */
+ int size();
+
+
+ /** This removes all attributes from this <code>LdapAttributes</code>. */
+ void clear();
+
+
+ /**
+ * This returns an <code>Attributes</code> that represents this entry.
+ *
+ * @return <code>Attributes</code>
+ */
+ Attributes toAttributes();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/LdapBeanFactory.java b/src/main/java/edu/vt/middleware/ldap/bean/LdapBeanFactory.java
new file mode 100644
index 0000000..bbb4f62
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/LdapBeanFactory.java
@@ -0,0 +1,57 @@
+/*
+ $Id: LdapBeanFactory.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+/**
+ * <code>LdapBeanFactory</code> provides an interface for ldap bean type
+ * factories.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface LdapBeanFactory
+{
+
+
+ /**
+ * Create a new instance of <code>LdapResult</code>.
+ *
+ * @return <code>LdapResult</code>
+ */
+ LdapResult newLdapResult();
+
+
+ /**
+ * Create a new instance of <code>LdapEntry</code>.
+ *
+ * @return <code>LdapEntry</code>
+ */
+ LdapEntry newLdapEntry();
+
+
+ /**
+ * Create a new instance of <code>LdapAttributes</code>.
+ *
+ * @return <code>LdapAttributes</code>
+ */
+ LdapAttributes newLdapAttributes();
+
+
+ /**
+ * Create a new instance of <code>LdapAttribute</code>.
+ *
+ * @return <code>LdapAttribute</code>
+ */
+ LdapAttribute newLdapAttribute();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/LdapBeanProvider.java b/src/main/java/edu/vt/middleware/ldap/bean/LdapBeanProvider.java
new file mode 100644
index 0000000..a8c9bfd
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/LdapBeanProvider.java
@@ -0,0 +1,107 @@
+/*
+ $Id: LdapBeanProvider.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>LdapBeanProvider</code> provides a single source for ldap bean types
+ * and configuration.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class LdapBeanProvider
+{
+
+ /** bean factory class name. */
+ public static final String BEAN_FACTORY =
+ "edu.vt.middleware.ldap.beanFactory";
+
+ /** Log for this class. */
+ private static final Log LOG = LogFactory.getLog(LdapBeanProvider.class);
+
+ /** single instance of the ldap bean provider. */
+ private static final LdapBeanProvider INSTANCE = new LdapBeanProvider();
+
+ /** factory used to create ldap beans. */
+ private static LdapBeanFactory beanFactory;
+
+
+ /** Default constructor. */
+ private LdapBeanProvider()
+ {
+ final String beanFactoryClass = System.getProperty(BEAN_FACTORY);
+ if (beanFactoryClass != null) {
+ try {
+ beanFactory = (LdapBeanFactory) Class.forName(beanFactoryClass)
+ .newInstance();
+ if (LOG.isInfoEnabled()) {
+ LOG.info("Set provider bean factory to " + beanFactoryClass);
+ }
+ } catch (ClassNotFoundException e) {
+ if (LOG.isErrorEnabled()) {
+ LOG.error("Error instantiating " + beanFactoryClass, e);
+ }
+ } catch (InstantiationException e) {
+ if (LOG.isErrorEnabled()) {
+ LOG.error("Error instantiating " + beanFactoryClass, e);
+ }
+ } catch (IllegalAccessException e) {
+ if (LOG.isErrorEnabled()) {
+ LOG.error("Error instantiating " + beanFactoryClass, e);
+ }
+ }
+ } else {
+ // set default ldap bean factory to unordered
+ beanFactory = new UnorderedLdapBeanFactory();
+ }
+ }
+
+
+ /**
+ * Returns the instance of this <code>LdapBeanProvider</code>.
+ *
+ * @return <code>LdapBeanProvider</code>
+ */
+ public static LdapBeanProvider getInstance()
+ {
+ return INSTANCE;
+ }
+
+
+ /**
+ * Returns the factory for creating ldap beans.
+ *
+ * @return <code>LdapBeanFactory</code>
+ */
+ public static LdapBeanFactory getLdapBeanFactory()
+ {
+ return beanFactory;
+ }
+
+
+ /**
+ * Sets the factory for creating ldap beans.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public static void setLdapBeanFactory(final LdapBeanFactory lbf)
+ {
+ if (lbf != null) {
+ beanFactory = lbf;
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/LdapEntry.java b/src/main/java/edu/vt/middleware/ldap/bean/LdapEntry.java
new file mode 100644
index 0000000..c7fe3da
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/LdapEntry.java
@@ -0,0 +1,79 @@
+/*
+ $Id: LdapEntry.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+
+/**
+ * <code>LdapEntry</code> represents a single ldap entry.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface LdapEntry
+{
+
+
+ /**
+ * This returns the DN for this <code>LdapEntry</code>.
+ *
+ * @return <code>String</code>
+ */
+ String getDn();
+
+
+ /**
+ * This returns the <code>LdapAttributes</code> for this <code>
+ * LdapEntry</code>.
+ *
+ * @return <code>LdapAttributes</code>
+ */
+ LdapAttributes getLdapAttributes();
+
+
+ /**
+ * This sets this <code>LdapEntry</code> with the supplied search result.
+ *
+ * @param sr <code>SearchResult</code>
+ *
+ * @throws NamingException if the search result cannot be read
+ */
+ void setEntry(final SearchResult sr)
+ throws NamingException;
+
+
+ /**
+ * This sets the DN for this <code>LdapEntry</code>.
+ *
+ * @param dn <code>String</code>
+ */
+ void setDn(final String dn);
+
+
+ /**
+ * This sets the attributes for this <code>LdapEntry</code>.
+ *
+ * @param a <code>LdapAttribute</code>
+ */
+ void setLdapAttributes(final LdapAttributes a);
+
+
+ /**
+ * This returns a <code>SearchResult</code> that represents this entry.
+ *
+ * @return <code>SearchResult</code>
+ */
+ SearchResult toSearchResult();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/LdapResult.java b/src/main/java/edu/vt/middleware/ldap/bean/LdapResult.java
new file mode 100644
index 0000000..6bc5b20
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/LdapResult.java
@@ -0,0 +1,124 @@
+/*
+ $Id: LdapResult.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+
+/**
+ * <code>LdapResult</code> represents a collection of ldap entries.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface LdapResult
+{
+
+
+ /**
+ * This returns a <code>Collection</code> of <code>LdapEntry</code> for this
+ * <code>LdapResult</code>.
+ *
+ * @return <code>Collection</code>
+ */
+ Collection<LdapEntry> getEntries();
+
+
+ /**
+ * This returns the <code>LdapEntry</code> for this <code>LdapResult</code>
+ * with the supplied DN.
+ *
+ * @param dn <code>String</code>
+ *
+ * @return <code>LdapEntry</code>
+ */
+ LdapEntry getEntry(final String dn);
+
+
+ /**
+ * This adds a new entry to this <code>LdapResult</code>.
+ *
+ * @param e <code>LdapEntry</code>
+ */
+ void addEntry(final LdapEntry e);
+
+
+ /**
+ * This adds a new entry to this <code>LdapResult</code>.
+ *
+ * @param sr <code>SearchResult</code>
+ *
+ * @throws NamingException if the search results cannot be read
+ */
+ void addEntry(final SearchResult sr)
+ throws NamingException;
+
+
+ /**
+ * This adds a <code>Collection</code> of entries to this <code>
+ * LdapResult</code>. The list should contain <code>LdapEntry</code> objects.
+ *
+ * @param c <code>Collection</code>
+ */
+ void addEntries(final Collection<LdapEntry> c);
+
+
+ /**
+ * This adds a <code>NamingEnumeration</code> of <code>SearchResult</code> to
+ * this <code>LdapResult</code>.
+ *
+ * @param ne <code>NamingEnumeration</code>
+ *
+ * @throws NamingException if the search results cannot be read
+ */
+ void addEntries(final NamingEnumeration<SearchResult> ne)
+ throws NamingException;
+
+
+ /**
+ * This adds an <code>Iterator</code> of <code>SearchResult</code> to this
+ * <code>LdapResult</code>.
+ *
+ * @param i <code>Iterator</code>
+ *
+ * @throws NamingException if the search results cannot be read
+ */
+ void addEntries(final Iterator<SearchResult> i)
+ throws NamingException;
+
+
+ /**
+ * This returns the number of entries in this result.
+ *
+ * @return <code>int</code>
+ */
+ int size();
+
+
+ /** This removes all entries from this <code>LdapResult</code>. */
+ void clear();
+
+
+ /**
+ * This returns a <code>List</code> of <code>SearchResult</code> that
+ * represent the entries in this <code>LdapResult</code>.
+ *
+ * @return <code>List</code>
+ */
+ List<SearchResult> toSearchResults();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/OrderedLdapBeanFactory.java b/src/main/java/edu/vt/middleware/ldap/bean/OrderedLdapBeanFactory.java
new file mode 100644
index 0000000..2602d51
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/OrderedLdapBeanFactory.java
@@ -0,0 +1,135 @@
+/*
+ $Id: OrderedLdapBeanFactory.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * <code>OrderedLdapBeanFactory</code> provides an ldap bean factory that
+ * produces ordered ldap beans.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class OrderedLdapBeanFactory implements LdapBeanFactory
+{
+
+
+ /** {@inheritDoc} */
+ public LdapResult newLdapResult()
+ {
+ return new OrderedLdapResult();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapEntry newLdapEntry()
+ {
+ return new OrderedLdapEntry();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapAttributes newLdapAttributes()
+ {
+ return new OrderedLdapAttributes();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapAttribute newLdapAttribute()
+ {
+ return new OrderedLdapAttribute();
+ }
+
+
+ /**
+ * <code>OrderedLdapResult</code> represents a collection of ldap entries that
+ * are ordered by insertion.
+ */
+ protected class OrderedLdapResult
+ extends AbstractLdapResult<LinkedHashMap<String, LdapEntry>>
+ {
+
+
+ /** Default constructor. */
+ public OrderedLdapResult()
+ {
+ super(OrderedLdapBeanFactory.this);
+ this.entries = new LinkedHashMap<String, LdapEntry>();
+ }
+ }
+
+
+ /** <code>OrderedLdapEntry</code> represents a single ldap entry. */
+ protected class OrderedLdapEntry extends AbstractLdapEntry
+ {
+
+
+ /** Default constructor. */
+ public OrderedLdapEntry()
+ {
+ super(OrderedLdapBeanFactory.this);
+ this.ldapAttributes = new OrderedLdapAttributes();
+ }
+ }
+
+
+ /**
+ * <code>OrderedLdapAttributes</code> represents a collection of ldap
+ * attribute that are ordered by insertion.
+ */
+ protected class OrderedLdapAttributes
+ extends AbstractLdapAttributes<LinkedHashMap<String, LdapAttribute>>
+ {
+
+
+ /** Default constructor. */
+ public OrderedLdapAttributes()
+ {
+ super(OrderedLdapBeanFactory.this);
+ this.attributes = new LinkedHashMap<String, LdapAttribute>();
+ }
+ }
+
+
+ /**
+ * <code>OrderedLdapAttribute</code> represents a single ldap attribute whose
+ * values are ordered by insertion.
+ */
+ protected class OrderedLdapAttribute
+ extends AbstractLdapAttribute<LinkedHashSet<Object>>
+ {
+
+
+ /** Default constructor. */
+ public OrderedLdapAttribute()
+ {
+ super(OrderedLdapBeanFactory.this);
+ this.values = new LinkedHashSet<Object>();
+ }
+
+
+ /** {@inheritDoc} */
+ public Set<String> getStringValues()
+ {
+ final Set<String> s = new LinkedHashSet<String>();
+ this.convertValuesToString(s);
+ return Collections.unmodifiableSet(s);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/SortedLdapBeanFactory.java b/src/main/java/edu/vt/middleware/ldap/bean/SortedLdapBeanFactory.java
new file mode 100644
index 0000000..c747801
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/SortedLdapBeanFactory.java
@@ -0,0 +1,137 @@
+/*
+ $Id: SortedLdapBeanFactory.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * <code>SortedLdapBeanFactory</code> provides an ldap bean factory that
+ * produces sorted ldap beans.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class SortedLdapBeanFactory implements LdapBeanFactory
+{
+
+
+ /** {@inheritDoc} */
+ public LdapResult newLdapResult()
+ {
+ return new SortedLdapResult();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapEntry newLdapEntry()
+ {
+ return new SortedLdapEntry();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapAttributes newLdapAttributes()
+ {
+ return new SortedLdapAttributes();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapAttribute newLdapAttribute()
+ {
+ return new SortedLdapAttribute();
+ }
+
+
+ /**
+ * <code>SortedLdapResult</code> represents a collection of ldap entries that
+ * are sorted by their DN.
+ */
+ protected class SortedLdapResult
+ extends AbstractLdapResult<TreeMap<String, LdapEntry>>
+ {
+
+
+ /** Default constructor. */
+ public SortedLdapResult()
+ {
+ super(SortedLdapBeanFactory.this);
+ this.entries = new TreeMap<String, LdapEntry>(
+ String.CASE_INSENSITIVE_ORDER);
+ }
+ }
+
+
+ /** <code>SortedLdapEntry</code> represents a single ldap entry. */
+ protected class SortedLdapEntry extends AbstractLdapEntry
+ {
+
+
+ /** Default constructor. */
+ public SortedLdapEntry()
+ {
+ super(SortedLdapBeanFactory.this);
+ this.ldapAttributes = new SortedLdapAttributes();
+ }
+ }
+
+
+ /**
+ * <code>SortedLdapAttributes</code> represents a collection of ldap attribute
+ * that are sorted by their name.
+ */
+ protected class SortedLdapAttributes
+ extends AbstractLdapAttributes<TreeMap<String, LdapAttribute>>
+ {
+
+
+ /** Default constructor. */
+ public SortedLdapAttributes()
+ {
+ super(SortedLdapBeanFactory.this);
+ this.attributes = new TreeMap<String, LdapAttribute>(
+ String.CASE_INSENSITIVE_ORDER);
+ }
+ }
+
+
+ /**
+ * <code>SortedLdapAttribute</code> represents a single ldap attribute whose
+ * values are sorted.
+ */
+ protected class SortedLdapAttribute
+ extends AbstractLdapAttribute<TreeSet<Object>>
+ {
+
+
+ /** Default constructor. */
+ public SortedLdapAttribute()
+ {
+ super(SortedLdapBeanFactory.this);
+ this.values = new TreeSet<Object>();
+ }
+
+
+ /** {@inheritDoc} */
+ public Set<String> getStringValues()
+ {
+ final Set<String> s = new TreeSet<String>();
+ this.convertValuesToString(s);
+ return Collections.unmodifiableSet(s);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/bean/UnorderedLdapBeanFactory.java b/src/main/java/edu/vt/middleware/ldap/bean/UnorderedLdapBeanFactory.java
new file mode 100644
index 0000000..23eb1ad
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/bean/UnorderedLdapBeanFactory.java
@@ -0,0 +1,135 @@
+/*
+ $Id: UnorderedLdapBeanFactory.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <code>UnorderedLdapBeanFactory</code> provides an ldap bean factory that
+ * produces unordered ldap beans.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class UnorderedLdapBeanFactory implements LdapBeanFactory
+{
+
+
+ /** {@inheritDoc} */
+ public LdapResult newLdapResult()
+ {
+ return new UnorderedLdapResult();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapEntry newLdapEntry()
+ {
+ return new UnorderedLdapEntry();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapAttributes newLdapAttributes()
+ {
+ return new UnorderedLdapAttributes();
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapAttribute newLdapAttribute()
+ {
+ return new UnorderedLdapAttribute();
+ }
+
+
+ /**
+ * <code>UnorderedLdapResult</code> represents a collection of ldap entries
+ * that are unordered.
+ */
+ protected class UnorderedLdapResult
+ extends AbstractLdapResult<HashMap<String, LdapEntry>>
+ {
+
+
+ /** Default constructor. */
+ public UnorderedLdapResult()
+ {
+ super(UnorderedLdapBeanFactory.this);
+ this.entries = new HashMap<String, LdapEntry>();
+ }
+ }
+
+
+ /** <code>UnorderedLdapEntry</code> represents a single ldap entry. */
+ protected class UnorderedLdapEntry extends AbstractLdapEntry
+ {
+
+
+ /** Default constructor. */
+ public UnorderedLdapEntry()
+ {
+ super(UnorderedLdapBeanFactory.this);
+ this.ldapAttributes = new UnorderedLdapAttributes();
+ }
+ }
+
+
+ /**
+ * <code>UnorderedLdapAttributes</code> represents a collection of ldap
+ * attribute that are unordered.
+ */
+ protected class UnorderedLdapAttributes
+ extends AbstractLdapAttributes<HashMap<String, LdapAttribute>>
+ {
+
+
+ /** Default constructor. */
+ public UnorderedLdapAttributes()
+ {
+ super(UnorderedLdapBeanFactory.this);
+ this.attributes = new HashMap<String, LdapAttribute>();
+ }
+ }
+
+
+ /**
+ * <code>UnorderedLdapAttribute</code> represents a single ldap attribute
+ * whose values are unordered.
+ */
+ protected class UnorderedLdapAttribute
+ extends AbstractLdapAttribute<HashSet<Object>>
+ {
+
+
+ /** Default constructor. */
+ public UnorderedLdapAttribute()
+ {
+ super(UnorderedLdapBeanFactory.this);
+ this.values = new HashSet<Object>();
+ }
+
+
+ /** {@inheritDoc} */
+ public Set<String> getStringValues()
+ {
+ final Set<String> s = new HashSet<String>();
+ this.convertValuesToString(s);
+ return Collections.unmodifiableSet(s);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/dsml/AbstractDsml.java b/src/main/java/edu/vt/middleware/ldap/dsml/AbstractDsml.java
new file mode 100644
index 0000000..c2a9199
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/dsml/AbstractDsml.java
@@ -0,0 +1,387 @@
+/*
+ $Id: AbstractDsml.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.dsml;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.LdapUtil;
+import edu.vt.middleware.ldap.bean.LdapAttribute;
+import edu.vt.middleware.ldap.bean.LdapAttributes;
+import edu.vt.middleware.ldap.bean.LdapBeanFactory;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.Namespace;
+import org.dom4j.QName;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.SAXReader;
+import org.dom4j.io.XMLWriter;
+
+/**
+ * <code>AbstractDsml</code> contains functions for converting LDAP search
+ * result sets into DSML.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public abstract class AbstractDsml implements Serializable
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -5626050181955100494L;
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Ldap bean factory. */
+ protected LdapBeanFactory beanFactory = LdapBeanProvider.getLdapBeanFactory();
+
+
+ /**
+ * Returns the factory for creating ldap beans.
+ *
+ * @return <code>LdapBeanFactory</code>
+ */
+ public LdapBeanFactory getLdapBeanFactory()
+ {
+ return this.beanFactory;
+ }
+
+
+ /**
+ * Sets the factory for creating ldap beans.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public void setLdapBeanFactory(final LdapBeanFactory lbf)
+ {
+ if (lbf != null) {
+ this.beanFactory = lbf;
+ }
+ }
+
+
+ /**
+ * This will take the results of a prior LDAP query and convert it to a DSML
+ * <code>Document</code>.
+ *
+ * @param results <code>Iterator</code> of LDAP search results
+ *
+ * @return <code>Document</code>
+ */
+ public abstract Document createDsml(final Iterator<SearchResult> results);
+
+
+ /**
+ * This will take the results of a prior LDAP query and convert it to a DSML
+ * <code>Document</code>.
+ *
+ * @param result <code>LdapResult</code>
+ *
+ * @return <code>Document</code>
+ */
+ public abstract Document createDsml(final LdapResult result);
+
+
+ /**
+ * This will take an LDAP search result and convert it to a DSML entry
+ * element.
+ *
+ * @param entryName <code>QName</code> name of element to create
+ * @param ldapEntry <code>LdapEntry</code> to convert
+ * @param ns <code>Namespace</code> of DSML
+ *
+ * @return <code>Document</code>
+ */
+ protected Element createDsmlEntry(
+ final QName entryName,
+ final LdapEntry ldapEntry,
+ final Namespace ns)
+ {
+ // create Element to hold result content
+ final Element entryElement = DocumentHelper.createElement(entryName);
+
+ if (ldapEntry != null) {
+
+ final String dn = ldapEntry.getDn();
+ if (dn != null) {
+ entryElement.addAttribute("dn", dn);
+ }
+
+ for (Element e :
+ createDsmlAttributes(ldapEntry.getLdapAttributes(), ns)) {
+ entryElement.add(e);
+ }
+ }
+
+ return entryElement;
+ }
+
+
+ /**
+ * This will return a list of DSML attribute elements from the supplied <code>
+ * LdapAttributes</code>.
+ *
+ * @param ldapAttributes <code>LdapAttributes</code>
+ * @param ns <code>Namespace</code> of DSML
+ *
+ * @return <code>List</code> of elements
+ */
+ protected List<Element> createDsmlAttributes(
+ final LdapAttributes ldapAttributes,
+ final Namespace ns)
+ {
+ final List<Element> attrElements = new ArrayList<Element>();
+ for (LdapAttribute attr : ldapAttributes.getAttributes()) {
+ final String attrName = attr.getName();
+ final Set<?> attrValues = attr.getValues();
+ final Element attrElement = createDsmlAttribute(
+ attrName,
+ attrValues,
+ ns,
+ "attr",
+ "name",
+ "value");
+ if (attrElement.hasContent()) {
+ attrElements.add(attrElement);
+ }
+ }
+ return attrElements;
+ }
+
+
+ /**
+ * This will take an attribute name and it's values and return a DSML
+ * attribute element.
+ *
+ * @param attrName <code>String</code>
+ * @param attrValues <code>Set</code>
+ * @param ns <code>Namespace</code> of DSML
+ * @param elementName <code>String</code> of the attribute element
+ * @param elementAttrName <code>String</code> of the attribute element
+ * @param elementValueName <code>String</code> of the value element
+ *
+ * @return <code>Element</code>
+ */
+ protected Element createDsmlAttribute(
+ final String attrName,
+ final Set<?> attrValues,
+ final Namespace ns,
+ final String elementName,
+ final String elementAttrName,
+ final String elementValueName)
+ {
+ final Element attrElement = DocumentHelper.createElement("");
+
+ if (attrName != null) {
+
+ attrElement.setQName(new QName(elementName, ns));
+ if (elementAttrName != null) {
+ attrElement.addAttribute(elementAttrName, attrName);
+ }
+ if (attrValues != null) {
+ final Iterator<?> i = attrValues.iterator();
+ while (i.hasNext()) {
+ final Object rawValue = i.next();
+ String value = null;
+ boolean isBase64 = false;
+ if (rawValue instanceof String) {
+ value = (String) rawValue;
+ } else if (rawValue instanceof byte[]) {
+ value = LdapUtil.base64Encode((byte[]) rawValue);
+ isBase64 = true;
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn(
+ "Could not cast attribute value as a byte[]" +
+ " or a String");
+ }
+ }
+ if (value != null) {
+ final Element valueElement = attrElement.addElement(
+ new QName(elementValueName, ns));
+ valueElement.addText(value);
+ if (isBase64) {
+ valueElement.addAttribute("encoding", "base64");
+ }
+ }
+ }
+ }
+ }
+
+ return attrElement;
+ }
+
+
+ /**
+ * This will write the supplied LDAP search results to the supplied writer in
+ * the form of DSML.
+ *
+ * @param results <code>Iterator</code> of LDAP search results
+ * @param writer <code>Writer</code> to write to
+ *
+ * @throws IOException if an error occurs while writing
+ */
+ public void outputDsml(
+ final Iterator<SearchResult> results,
+ final Writer writer)
+ throws IOException
+ {
+ final XMLWriter xmlWriter = new XMLWriter(
+ writer,
+ OutputFormat.createPrettyPrint());
+ xmlWriter.write(createDsml(results));
+ writer.flush();
+ }
+
+
+ /**
+ * This will write the supplied LDAP result to the supplied writer in the form
+ * of DSML.
+ *
+ * @param result <code>LdapResult</code>
+ * @param writer <code>Writer</code> to write to
+ *
+ * @throws IOException if an error occurs while writing
+ */
+ public void outputDsml(final LdapResult result, final Writer writer)
+ throws IOException
+ {
+ final XMLWriter xmlWriter = new XMLWriter(
+ writer,
+ OutputFormat.createPrettyPrint());
+ xmlWriter.write(createDsml(result));
+ writer.flush();
+ }
+
+
+ /**
+ * This will take a Reader containing a DSML <code>Document</code> and convert
+ * it to an Iterator of LDAP search results.
+ *
+ * @param reader <code>Reader</code> containing DSML content
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws DocumentException if an error occurs building a document from the
+ * reader
+ * @throws IOException if an I/O error occurs
+ */
+ public Iterator<SearchResult> importDsml(final Reader reader)
+ throws DocumentException, IOException
+ {
+ final Document dsml = new SAXReader().read(reader);
+ return createLdapResult(dsml).toSearchResults().iterator();
+ }
+
+
+ /**
+ * This will take a Reader containing a DSML <code>Document</code> and convert
+ * it to an <code>LdapResult</code>.
+ *
+ * @param reader <code>Reader</code> containing DSML content
+ *
+ * @return <code>LdapResult</code>
+ *
+ * @throws DocumentException if an error occurs building a document from the
+ * reader
+ * @throws IOException if an I/O error occurs
+ */
+ public LdapResult importDsmlToLdapResult(final Reader reader)
+ throws DocumentException, IOException
+ {
+ final Document dsml = new SAXReader().read(reader);
+ return createLdapResult(dsml);
+ }
+
+
+ /**
+ * This will take a DSML <code>Document</code> and convert it to an Iterator
+ * of LDAP search results.
+ *
+ * @param doc <code>Document</code> of DSML
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ */
+ protected abstract LdapResult createLdapResult(final Document doc);
+
+
+ /**
+ * This will take a DSML <code>Element</code> containing an entry of type
+ * <entry/> and convert it to an LDAP entry.
+ *
+ * @param entryElement <code>Element</code> of DSML content
+ *
+ * @return <code>LdapEntry</code>
+ */
+ protected LdapEntry createLdapEntry(final Element entryElement)
+ {
+ final LdapEntry ldapEntry = this.beanFactory.newLdapEntry();
+ ldapEntry.setDn("");
+
+ if (entryElement != null) {
+
+ final String name = entryElement.attributeValue("dn");
+ if (name != null) {
+ ldapEntry.setDn(name);
+ }
+
+ if (entryElement.hasContent()) {
+
+ // load the attribute elements
+ final Iterator<?> attrIterator = entryElement.elementIterator("attr");
+ while (attrIterator.hasNext()) {
+ final Element attrElement = (Element) attrIterator.next();
+ final String attrName = attrElement.attributeValue("name");
+ if (attrName != null && attrElement.hasContent()) {
+ final LdapAttribute ldapAttribute = this.beanFactory
+ .newLdapAttribute();
+ ldapAttribute.setName(attrName);
+
+ final Iterator<?> valueIterator = attrElement.elementIterator(
+ "value");
+ while (valueIterator.hasNext()) {
+ final Element valueElement = (Element) valueIterator.next();
+ final String value = valueElement.getText();
+ if (value != null) {
+ final String encoding = valueElement.attributeValue("encoding");
+ if (encoding != null && "base64".equals(encoding)) {
+ ldapAttribute.getValues().add(LdapUtil.base64Decode(value));
+ } else {
+ ldapAttribute.getValues().add(value);
+ }
+ }
+ }
+ ldapEntry.getLdapAttributes().addAttribute(ldapAttribute);
+ }
+ }
+ }
+ }
+
+ return ldapEntry;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/dsml/DsmlResultConverter.java b/src/main/java/edu/vt/middleware/ldap/dsml/DsmlResultConverter.java
new file mode 100644
index 0000000..bddd1c6
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/dsml/DsmlResultConverter.java
@@ -0,0 +1,174 @@
+/*
+ $Id: DsmlResultConverter.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.dsml;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.bean.LdapBeanFactory;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.DocumentException;
+
+/**
+ * <code>DsmlResultConverter</code> provides utility methods for converting
+ * <code>LdapResult</code> to and from DSML in string format.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class DsmlResultConverter
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /** Ldap bean factory. */
+ protected LdapBeanFactory beanFactory = LdapBeanProvider.getLdapBeanFactory();
+
+ /** Class for outputting version 1 DSML. */
+ private Dsmlv1 dsmlv1 = new Dsmlv1();
+
+ /** Class for outputting version 2 DSML. */
+ private Dsmlv2 dsmlv2 = new Dsmlv2();
+
+
+ /**
+ * Returns the factory for creating ldap beans.
+ *
+ * @return <code>LdapBeanFactory</code>
+ */
+ public LdapBeanFactory getLdapBeanFactory()
+ {
+ return this.beanFactory;
+ }
+
+
+ /**
+ * Sets the factory for creating ldap beans.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public void setLdapBeanFactory(final LdapBeanFactory lbf)
+ {
+ if (lbf != null) {
+ this.beanFactory = lbf;
+ this.dsmlv1.setLdapBeanFactory(lbf);
+ this.dsmlv2.setLdapBeanFactory(lbf);
+ }
+ }
+
+
+ /**
+ * This returns this <code>DsmlResult</code> as version 1 DSML.
+ *
+ * @param result <code>LdapResult</code> to convert
+ *
+ * @return <code>String</code>
+ */
+ public String toDsmlv1(final LdapResult result)
+ {
+ final StringWriter writer = new StringWriter();
+ try {
+ this.dsmlv1.outputDsml(result.toSearchResults().iterator(), writer);
+ } catch (IOException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("Could not write dsml to StringWriter", e);
+ }
+ }
+ return writer.toString();
+ }
+
+
+ /**
+ * This reads any entries in the supplied DSML into this <code>
+ * DsmlResult</code>.
+ *
+ * @param dsml <code>String</code> to read
+ *
+ * @return <code>LdapResult</code>
+ *
+ * @throws DocumentException if an error occurs reading the supplied DSML
+ */
+ public LdapResult fromDsmlv1(final String dsml)
+ throws DocumentException
+ {
+ final LdapResult result = this.beanFactory.newLdapResult();
+ try {
+ result.addEntries(this.dsmlv1.importDsml(new StringReader(dsml)));
+ } catch (IOException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("Could not read dsml from StringReader", e);
+ }
+ } catch (NamingException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Unexpected naming exception occurred", e);
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * This returns this <code>DsmlResult</code> as version 2 DSML.
+ *
+ * @param result <code>LdapResult</code> to convert
+ *
+ * @return <code>String</code>
+ */
+ public String toDsmlv2(final LdapResult result)
+ {
+ final StringWriter writer = new StringWriter();
+ try {
+ this.dsmlv2.outputDsml(result.toSearchResults().iterator(), writer);
+ } catch (IOException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("Could not write dsml to StringWriter", e);
+ }
+ }
+ return writer.toString();
+ }
+
+
+ /**
+ * This reads any entries in the supplied DSML into this <code>
+ * DsmlResult</code>.
+ *
+ * @param dsml <code>String</code> to read
+ *
+ * @return <code>LdapResult</code>
+ *
+ * @throws DocumentException if an error occurs reading the supplied DSML
+ */
+ public LdapResult fromDsmlv2(final String dsml)
+ throws DocumentException
+ {
+ final LdapResult result = this.beanFactory.newLdapResult();
+ try {
+ result.addEntries(this.dsmlv2.importDsml(new StringReader(dsml)));
+ } catch (IOException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("Could not read dsml from StringReader", e);
+ }
+ } catch (NamingException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Unexpected naming exception occurred", e);
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/dsml/DsmlSearch.java b/src/main/java/edu/vt/middleware/ldap/dsml/DsmlSearch.java
new file mode 100644
index 0000000..f1a59e8
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/dsml/DsmlSearch.java
@@ -0,0 +1,112 @@
+/*
+ $Id: DsmlSearch.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.dsml;
+
+import java.io.IOException;
+import java.io.Writer;
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.LdapSearch;
+import edu.vt.middleware.ldap.pool.LdapPool;
+
+/**
+ * <code>DsmlSearch</code> queries an LDAP and returns the result as DSML. Each
+ * instance of <code>DsmlSearch</code> maintains it's own pool of LDAP
+ * connections.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class DsmlSearch extends LdapSearch
+{
+
+ /** Valid DSML versions. */
+ public enum Version {
+
+ /** DSML version 1. */
+ ONE,
+
+ /** DSML version 2. */
+ TWO
+ }
+
+ /** Version of DSML to produce, default is 1. */
+ private Version version = Version.ONE;
+
+ /** Dsml version 1 object. */
+ private Dsmlv1 dsmlv1 = new Dsmlv1();
+
+ /** Dsml version 2 object. */
+ private Dsmlv2 dsmlv2 = new Dsmlv2();
+
+
+ /**
+ * This creates a new <code>DsmlSearch</code> with the supplied pool.
+ *
+ * @param pool <code>LdapPool</code>
+ */
+ public DsmlSearch(final LdapPool<Ldap> pool)
+ {
+ super(pool);
+ }
+
+
+ /**
+ * This gets the version of dsml to produce.
+ *
+ * @return <code>Version</code> of DSML to produce
+ */
+ public Version getVersion()
+ {
+ return this.version;
+ }
+
+
+ /**
+ * This sets the version of dsml to produce.
+ *
+ * @param v <code>Version</code> of DSML to produce
+ */
+ public void setVersion(final Version v)
+ {
+ this.version = v;
+ }
+
+
+ /**
+ * This will perform an LDAP search with the supplied query and return
+ * attributes. The results will be written to the supplied <code>
+ * Writer</code>. Use {@link #version} to control which version of DSML is
+ * written.
+ *
+ * @param query <code>String</code> to search for
+ * @param attrs <code>String[]</code> to return
+ * @param writer <code>Writer</code> to write to
+ *
+ * @throws NamingException if an error occurs while searching
+ * @throws IOException if an error occurs while writing search results
+ */
+ public void search(
+ final String query,
+ final String[] attrs,
+ final Writer writer)
+ throws NamingException, IOException
+ {
+ if (this.version == Version.TWO) {
+ this.dsmlv2.outputDsml(this.search(query, attrs), writer);
+ } else {
+ this.dsmlv1.outputDsml(this.search(query, attrs), writer);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/dsml/Dsmlv1.java b/src/main/java/edu/vt/middleware/ldap/dsml/Dsmlv1.java
new file mode 100644
index 0000000..dec8ec5
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/dsml/Dsmlv1.java
@@ -0,0 +1,247 @@
+/*
+ $Id: Dsmlv1.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.dsml;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.LdapUtil;
+import edu.vt.middleware.ldap.bean.LdapAttribute;
+import edu.vt.middleware.ldap.bean.LdapAttributes;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.Namespace;
+import org.dom4j.QName;
+
+/**
+ * <code>Dsmlv1</code> contains functions for converting LDAP search result sets
+ * into DSML version 1.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class Dsmlv1 extends AbstractDsml
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = 1047858330816575821L;
+
+
+ /** Default constructor. */
+ public Dsmlv1() {}
+
+
+ /**
+ * This will take the results of a prior LDAP query and convert it to a DSML
+ * <code>Document</code>.
+ *
+ * @param results <code>Iterator</code> of LDAP search results
+ *
+ * @return <code>Document</code>
+ */
+ public Document createDsml(final Iterator<SearchResult> results)
+ {
+ Document dsml = null;
+ try {
+ final LdapResult lr = this.beanFactory.newLdapResult();
+ lr.addEntries(results);
+ dsml = this.createDsml(lr);
+ } catch (NamingException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error creating Element from SearchResult", e);
+ }
+ }
+ return dsml;
+ }
+
+
+ /**
+ * This will take the results of a prior LDAP query and convert it to a DSML
+ * <code>Document</code>.
+ *
+ * @param result <code>LdapResult</code>
+ *
+ * @return <code>Document</code>
+ */
+ public Document createDsml(final LdapResult result)
+ {
+ final Namespace ns = new Namespace("dsml", "http://www.dsml.org/DSML");
+ final Document doc = DocumentHelper.createDocument();
+ final Element dsmlElement = doc.addElement(new QName("dsml", ns));
+ final Element entriesElement = dsmlElement.addElement(
+ new QName("directory-entries", ns));
+
+ // build document object from result
+ if (result != null) {
+ for (LdapEntry le : result.getEntries()) {
+ final Element entryElement = this.createDsmlEntry(
+ new QName("entry", ns),
+ le,
+ ns);
+ entriesElement.add(entryElement);
+ }
+ }
+
+ return doc;
+ }
+
+
+ /** {@inheritDoc} */
+ protected List<Element> createDsmlAttributes(
+ final LdapAttributes ldapAttributes,
+ final Namespace ns)
+ {
+ final List<Element> attrElements = new ArrayList<Element>();
+ for (LdapAttribute attr : ldapAttributes.getAttributes()) {
+ final String attrName = attr.getName();
+ final Set<?> attrValues = attr.getValues();
+ Element attrElement = null;
+ if (attrName.equalsIgnoreCase("objectclass")) {
+ attrElement = createDsmlAttribute(
+ attrName,
+ attrValues,
+ ns,
+ "objectclass",
+ null,
+ "oc-value");
+ if (attrElement.hasContent()) {
+ attrElements.add(0, attrElement);
+ }
+ } else {
+ attrElement = createDsmlAttribute(
+ attrName,
+ attrValues,
+ ns,
+ "attr",
+ "name",
+ "value");
+ if (attrElement.hasContent()) {
+ attrElements.add(attrElement);
+ }
+ }
+ }
+ return attrElements;
+ }
+
+
+ /**
+ * This will take a DSML <code>Document</code> and convert it to an Iterator
+ * of LDAP search results.
+ *
+ * @param doc <code>Document</code> of DSML
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ */
+ public Iterator<SearchResult> createSearchResults(final Document doc)
+ {
+ return this.createLdapResult(doc).toSearchResults().iterator();
+ }
+
+
+ /**
+ * This will take a DSML <code>Document</code> and convert it to an <code>
+ * LdapResult</code>.
+ *
+ * @param doc <code>Document</code> of DSML
+ *
+ * @return <code>LdapResult</code>
+ */
+ public LdapResult createLdapResult(final Document doc)
+ {
+ final LdapResult result = this.beanFactory.newLdapResult();
+
+ if (doc != null && doc.hasContent()) {
+ final Iterator<?> entryIterator = doc.selectNodes(
+ "/dsml:dsml/dsml:directory-entries/dsml:entry").iterator();
+ while (entryIterator.hasNext()) {
+ final LdapEntry le = this.createLdapEntry(
+ (Element) entryIterator.next());
+ if (result != null) {
+ result.addEntry(le);
+ }
+ }
+ }
+
+ return result;
+ }
+
+
+ /**
+ * This will take a DSML <code>Element</code> containing an entry of type
+ * <dsml:entry name="name"/> and convert it to an LDAP entry.
+ *
+ * @param entryElement <code>Element</code> of DSML content
+ *
+ * @return <code>LdapEntry</code>
+ */
+ protected LdapEntry createLdapEntry(final Element entryElement)
+ {
+ final LdapEntry ldapEntry = this.beanFactory.newLdapEntry();
+ ldapEntry.setDn("");
+
+ if (entryElement != null) {
+
+ final String name = entryElement.attributeValue("dn");
+ if (name != null) {
+ ldapEntry.setDn(name);
+ }
+
+ if (entryElement.hasContent()) {
+
+ final Iterator<?> ocIterator = entryElement.elementIterator(
+ "objectclass");
+ while (ocIterator.hasNext()) {
+ final Element ocElement = (Element) ocIterator.next();
+ if (ocElement != null && ocElement.hasContent()) {
+ final String ocName = "objectClass";
+ final LdapAttribute ldapAttribute = this.beanFactory
+ .newLdapAttribute();
+ ldapAttribute.setName(ocName);
+
+ final Iterator<?> valueIterator = ocElement.elementIterator(
+ "oc-value");
+ while (valueIterator.hasNext()) {
+ final Element valueElement = (Element) valueIterator.next();
+ if (valueElement != null) {
+ final String value = valueElement.getText();
+ if (value != null) {
+ final String encoding = valueElement.attributeValue(
+ "encoding");
+ if (encoding != null && "base64".equals(encoding)) {
+ ldapAttribute.getValues().add(LdapUtil.base64Decode(value));
+ } else {
+ ldapAttribute.getValues().add(value);
+ }
+ }
+ }
+ }
+ ldapEntry.getLdapAttributes().addAttribute(ldapAttribute);
+ }
+ }
+
+ ldapEntry.getLdapAttributes().addAttributes(
+ super.createLdapEntry(entryElement).getLdapAttributes()
+ .getAttributes());
+ }
+ }
+
+ return ldapEntry;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/dsml/Dsmlv2.java b/src/main/java/edu/vt/middleware/ldap/dsml/Dsmlv2.java
new file mode 100644
index 0000000..184ddfa
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/dsml/Dsmlv2.java
@@ -0,0 +1,148 @@
+/*
+ $Id: Dsmlv2.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.dsml;
+
+import java.util.Iterator;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.Namespace;
+import org.dom4j.QName;
+
+/**
+ * <code>Dsmlv2</code> contains functions for converting LDAP search result sets
+ * into DSML version 2.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class Dsmlv2 extends AbstractDsml
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -1503268164295032020L;
+
+
+ /** Default constructor. */
+ public Dsmlv2() {}
+
+
+ /**
+ * This will take the results of a prior LDAP query and convert it to a DSML
+ * <code>Document</code>.
+ *
+ * @param results <code>Iterator</code> of LDAP search results
+ *
+ * @return <code>Document</code>
+ */
+ public Document createDsml(final Iterator<SearchResult> results)
+ {
+ Document dsml = null;
+ try {
+ final LdapResult lr = this.beanFactory.newLdapResult();
+ lr.addEntries(results);
+ dsml = this.createDsml(lr);
+ } catch (NamingException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error creating Element from SearchResult", e);
+ }
+ }
+ return dsml;
+ }
+
+
+ /**
+ * This will take the results of a prior LDAP query and convert it to a DSML
+ * <code>Document</code>.
+ *
+ * @param result <code>LdapResult</code>
+ *
+ * @return <code>Document</code>
+ */
+ public Document createDsml(final LdapResult result)
+ {
+ final Namespace ns = new Namespace("", "urn:oasis:names:tc:DSML:2:0:core");
+ final Document doc = DocumentHelper.createDocument();
+ final Element dsmlElement = doc.addElement(new QName("batchResponse", ns));
+ final Element entriesElement = dsmlElement.addElement(
+ new QName("searchResponse", ns));
+
+ // build document object from results
+ if (result != null) {
+ for (LdapEntry le : result.getEntries()) {
+ final Element entryElement = this.createDsmlEntry(
+ new QName("searchResultEntry", ns),
+ le,
+ ns);
+ entriesElement.add(entryElement);
+ }
+ }
+
+ final Element doneElement = entriesElement.addElement(
+ new QName("searchResultDone", ns));
+ final Element codeElement = doneElement.addElement(
+ new QName("resultCode", ns));
+ codeElement.addAttribute("code", "0");
+
+ return doc;
+ }
+
+
+ /**
+ * This will take a DSML <code>Document</code> and convert it to an Iterator
+ * of LDAP search results.
+ *
+ * @param doc <code>Document</code> of DSML
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ */
+ public Iterator<SearchResult> createSearchResults(final Document doc)
+ {
+ return this.createLdapResult(doc).toSearchResults().iterator();
+ }
+
+
+ /**
+ * This will take a DSML <code>Document</code> and convert it to a <code>
+ * LdapResult</code>.
+ *
+ * @param doc <code>Document</code> of DSML
+ *
+ * @return <code>LdapResult</code>
+ */
+ public LdapResult createLdapResult(final Document doc)
+ {
+ final LdapResult result = this.beanFactory.newLdapResult();
+
+ if (doc != null && doc.hasContent()) {
+ final Iterator<?> entryIterator = doc.selectNodes(
+ "/*[name()='batchResponse']" +
+ "/*[name()='searchResponse']" +
+ "/*[name()='searchResultEntry']").iterator();
+ while (entryIterator.hasNext()) {
+ final LdapEntry le = this.createLdapEntry(
+ (Element) entryIterator.next());
+ if (le != null) {
+ result.addEntry(le);
+ }
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/AbstractConnectionHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/AbstractConnectionHandler.java
new file mode 100644
index 0000000..efd8764
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/AbstractConnectionHandler.java
@@ -0,0 +1,331 @@
+/*
+ $Id: AbstractConnectionHandler.java 2397 2012-05-23 20:05:27Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2397 $
+ Updated: $Date: 2012-05-23 21:05:27 +0100 (Wed, 23 May 2012) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.StringTokenizer;
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+import edu.vt.middleware.ldap.LdapConfig;
+import edu.vt.middleware.ldap.LdapConstants;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AbstractConnectionHandler</code> provides a basic implementation for
+ * other connection handlers to inherit.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2397 $
+ */
+public abstract class AbstractConnectionHandler implements ConnectionHandler
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Ldap configuration. */
+ protected LdapConfig config;
+
+ /** Ldap context. */
+ protected LdapContext context;
+
+ /** Ldap connection strategy. */
+ protected ConnectionStrategy connectionStrategy = ConnectionStrategy.DEFAULT;
+
+ /** Exception types to retry connections on. */
+ protected Class<?>[] connectionRetryExceptions = new Class[] {
+ NamingException.class,
+ };
+
+ /** Number of connections made. */
+ private ConnectionCount connectionCount = new ConnectionCount();
+
+
+ /**
+ * Returns the connection count.
+ *
+ * @return connection count
+ */
+ protected ConnectionCount getConnectionCount()
+ {
+ return this.connectionCount;
+ }
+
+
+ /**
+ * Sets the connection count.
+ *
+ * @param cc connection count
+ */
+ protected void setConnectionCount(final ConnectionCount cc)
+ {
+ this.connectionCount = cc;
+ }
+
+
+ /** {@inheritDoc} */
+ public ConnectionStrategy getConnectionStrategy()
+ {
+ return this.connectionStrategy;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setConnectionStrategy(final ConnectionStrategy strategy)
+ {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting connectionStrategy: " + strategy);
+ }
+ this.connectionStrategy = strategy;
+ }
+
+
+ /** {@inheritDoc} */
+ public Class<?>[] getConnectionRetryExceptions()
+ {
+ return this.connectionRetryExceptions;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setConnectionRetryExceptions(final Class<?>[] exceptions)
+ {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "setting connectionRetryExceptions: " + Arrays.toString(exceptions));
+ }
+ this.connectionRetryExceptions = exceptions;
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapConfig getLdapConfig()
+ {
+ return this.config;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setLdapConfig(final LdapConfig lc)
+ {
+ this.config = lc;
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapContext getLdapContext()
+ {
+ return this.context;
+ }
+
+
+ /** {@inheritDoc} */
+ public void connect(final String dn, final Object credential)
+ throws NamingException
+ {
+ NamingException lastThrown = null;
+ final String[] urls = this.parseLdapUrl(
+ this.config.getLdapUrl(),
+ this.connectionStrategy);
+ for (String url : urls) {
+ final Hashtable<String, Object> env = new Hashtable<String, Object>(
+ this.config.getEnvironment());
+ env.put(LdapConstants.PROVIDER_URL, url);
+ try {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "{" + this.connectionCount + "} Attempting connection to " + url +
+ " for strategy " + this.connectionStrategy);
+ }
+ this.connectInternal(this.config.getAuthtype(), dn, credential, env);
+ this.connectionCount.incrementCount();
+ lastThrown = null;
+ break;
+ } catch (NamingException e) {
+ lastThrown = e;
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Error connecting to LDAP URL: " + url, e);
+ }
+
+ boolean ignoreException = false;
+ if (
+ this.connectionRetryExceptions != null &&
+ this.connectionRetryExceptions.length > 0) {
+ for (Class<?> ne : this.connectionRetryExceptions) {
+ if (ne.isInstance(e)) {
+ ignoreException = true;
+ break;
+ }
+ }
+ }
+ if (!ignoreException) {
+ break;
+ }
+ }
+ }
+ if (lastThrown != null) {
+ throw lastThrown;
+ }
+ }
+
+
+ /**
+ * Create the initial ldap context and prepare the connection for use.
+ *
+ * @param authtype security mechanism to bind with
+ * @param dn to bind as
+ * @param credential to bind with in conjunction with dn
+ * @param env to pass to the initial ldap context
+ *
+ * @throws NamingException if a connection cannot be established
+ */
+ protected abstract void connectInternal(
+ final String authtype,
+ final String dn,
+ final Object credential,
+ final Hashtable<String, Object> env)
+ throws NamingException;
+
+
+ /** {@inheritDoc} */
+ public boolean isConnected()
+ {
+ return this.context != null;
+ }
+
+
+ /** {@inheritDoc} */
+ public void close()
+ throws NamingException
+ {
+ try {
+ if (this.context != null) {
+ this.context.close();
+ }
+ } finally {
+ this.context = null;
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public abstract ConnectionHandler newInstance();
+
+
+ /**
+ * Parses the supplied ldap url and splits it into separate URLs if it is
+ * space delimited.
+ *
+ * @param ldapUrl to parse
+ * @param strategy of ordered array to return
+ *
+ * @return array of ldap URLs
+ */
+ protected String[] parseLdapUrl(
+ final String ldapUrl,
+ final ConnectionStrategy strategy)
+ {
+ String[] urls = null;
+ if (strategy == ConnectionStrategy.DEFAULT) {
+ urls = new String[] {ldapUrl};
+ } else if (strategy == ConnectionStrategy.ACTIVE_PASSIVE) {
+ final List<String> l = this.splitLdapUrl(ldapUrl);
+ urls = l.toArray(new String[l.size()]);
+ } else if (strategy == ConnectionStrategy.ROUND_ROBIN) {
+ final List<String> l = this.splitLdapUrl(ldapUrl);
+ for (int i = 0; i < this.connectionCount.getCount() % l.size(); i++) {
+ l.add(l.remove(0));
+ }
+ urls = l.toArray(new String[l.size()]);
+ } else if (strategy == ConnectionStrategy.RANDOM) {
+ final List<String> l = this.splitLdapUrl(ldapUrl);
+ Collections.shuffle(l);
+ urls = l.toArray(new String[l.size()]);
+ }
+ return urls;
+ }
+
+
+ /**
+ * Takes a space delimited string of URLs and returns a list of URLs.
+ *
+ * @param url to split
+ *
+ * @return list of URLs
+ */
+ private List<String> splitLdapUrl(final String url)
+ {
+ final List<String> urls = new ArrayList<String>();
+ if (url != null) {
+ final StringTokenizer st = new StringTokenizer(url);
+ while (st.hasMoreTokens()) {
+ urls.add(st.nextToken());
+ }
+ } else {
+ urls.add(null);
+ }
+ return urls;
+ }
+
+
+ /**
+ * <code>ConnectionCount</code> provides an object to track the connection
+ * count.
+ */
+ private class ConnectionCount
+ {
+
+ /** connection count. */
+ private int count;
+
+
+ /**
+ * Returns the connection count.
+ *
+ * @return count
+ */
+ public int getCount()
+ {
+ return this.count;
+ }
+
+
+ /** Increments the connection count. */
+ public void incrementCount()
+ {
+ this.count++;
+ // reset the count if it exceeds the size of an integer
+ if (this.count < 0) {
+ this.count = 0;
+ }
+ }
+
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return count as a string
+ */
+ @Override
+ public String toString()
+ {
+ return Integer.toString(this.count);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/AbstractResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/AbstractResultHandler.java
new file mode 100644
index 0000000..1348376
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/AbstractResultHandler.java
@@ -0,0 +1,150 @@
+/*
+ $Id: AbstractResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AbstractResultHandler</code> implements common handler functionality.
+ *
+ * @param <R> type of result
+ * @param <O> type of output
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public abstract class AbstractResultHandler<R, O> implements ResultHandler<R, O>
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+
+ /**
+ * This will enumerate through the supplied <code>NamingEnumeration</code> and
+ * return a List of those results. The results are unaltered and the dn is
+ * ignored.
+ *
+ * @param sc <code>SearchCriteria</code> used to find enumeration
+ * @param en <code>NamingEnumeration</code> LDAP results
+ *
+ * @return <code>List</code> - LDAP results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public List<O> process(
+ final SearchCriteria sc,
+ final NamingEnumeration<? extends R> en)
+ throws NamingException
+ {
+ return this.process(sc, en, null);
+ }
+
+
+ /**
+ * This will enumerate through the supplied <code>NamingEnumeration</code> and
+ * return a List of those results. The results are unaltered and the dn is
+ * ignored. Any exceptions passed into this method will be ignored and results
+ * will be returned as if no exception occurred.
+ *
+ * @param sc <code>SearchCriteria</code> used to find enumeration
+ * @param en <code>NamingEnumeration</code> LDAP results
+ * @param ignore <code>Class[]</code> of exception types to ignore
+ *
+ * @return <code>List</code> - LDAP results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public List<O> process(
+ final SearchCriteria sc,
+ final NamingEnumeration<? extends R> en,
+ final Class<?>[] ignore)
+ throws NamingException
+ {
+ final List<O> results = new ArrayList<O>();
+ if (en != null) {
+ try {
+ while (en.hasMore()) {
+ final O o = processResult(sc, en.next());
+ if (o != null) {
+ results.add(o);
+ }
+ }
+ } catch (NamingException e) {
+ boolean ignoreException = false;
+ if (ignore != null && ignore.length > 0) {
+ for (Class<?> ne : ignore) {
+ if (ne.isInstance(e)) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Ignoring naming exception", e);
+ }
+ ignoreException = true;
+ break;
+ }
+ }
+ }
+ if (!ignoreException) {
+ throw e;
+ }
+ }
+ }
+ return results;
+ }
+
+
+ /**
+ * This will enumerate through the supplied <code>List</code> and return a
+ * List of those results. The results are unaltered and the dn is ignored.
+ *
+ * @param sc <code>SearchCriteria</code> used to find enumeration
+ * @param l <code>List</code> LDAP results
+ *
+ * @return <code>List</code> - LDAP results
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public List<O> process(final SearchCriteria sc, final List<? extends R> l)
+ throws NamingException
+ {
+ final List<O> results = new ArrayList<O>();
+ if (l != null) {
+ for (R r : l) {
+ final O o = processResult(sc, r);
+ if (o != null) {
+ results.add(o);
+ }
+ }
+ }
+ return results;
+ }
+
+
+ /**
+ * Processes the supplied result.
+ *
+ * @param sc <code>SearchCriteria</code> used to retrieve the result
+ * @param r <code>R</code> result to process
+ *
+ * @return <code>O</code> processed result
+ *
+ * @throws NamingException if the supplied result cannot be read
+ */
+ protected abstract O processResult(final SearchCriteria sc, final R r)
+ throws NamingException;
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/AttributeHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/AttributeHandler.java
new file mode 100644
index 0000000..5fb740d
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/AttributeHandler.java
@@ -0,0 +1,24 @@
+/*
+ $Id: AttributeHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.directory.Attribute;
+
+/**
+ * AttributeHandler provides post search processing of an ldap attribute.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public interface AttributeHandler extends ResultHandler<Attribute, Attribute> {}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/AttributesProcessor.java b/src/main/java/edu/vt/middleware/ldap/handler/AttributesProcessor.java
new file mode 100644
index 0000000..1da47e7
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/AttributesProcessor.java
@@ -0,0 +1,89 @@
+/*
+ $Id: AttributesProcessor.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+
+/**
+ * <code>AttributesProcessor</code> provides methods to help with the processing
+ * of Attributes objects using an AttributeHandler.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class AttributesProcessor
+{
+
+
+ /** Default constructor. */
+ private AttributesProcessor() {}
+
+
+ /**
+ * Process the attributes of an ldap search search.
+ *
+ * @param sc <code>SearchCriteria</code> used to find search result
+ * @param attrs <code>Attributes</code> to pass to the handler
+ * @param handler <code>AttributeHandler</code> to process attributes
+ *
+ * @return <code>Attributes</code> handler processed attributes
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public static Attributes executeHandler(
+ final SearchCriteria sc,
+ final Attributes attrs,
+ final AttributeHandler handler)
+ throws NamingException
+ {
+ return executeHandler(sc, attrs, handler, null);
+ }
+
+
+ /**
+ * Process the attributes of an ldap search search. Any exceptions passed into
+ * this method will be ignored and results will be returned as if no exception
+ * occurred.
+ *
+ * @param sc <code>SearchCriteria</code> used to find search result
+ * @param attrs <code>Attributes</code> to pass to the handler
+ * @param handler <code>AttributeHandler</code> to process attributes
+ * @param ignore <code>Class[]</code> of exception types to ignore
+ *
+ * @return <code>Attributes</code> handler processed attributes
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ public static Attributes executeHandler(
+ final SearchCriteria sc,
+ final Attributes attrs,
+ final AttributeHandler handler,
+ final Class<?>[] ignore)
+ throws NamingException
+ {
+ Attributes newAttrs = null;
+ if (handler != null) {
+ newAttrs = new BasicAttributes(attrs.isCaseIgnored());
+ for (Attribute attr : handler.process(sc, attrs.getAll(), ignore)) {
+ newAttrs.put(attr);
+ }
+ } else {
+ newAttrs = attrs;
+ }
+ return newAttrs;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/BinaryAttributeHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/BinaryAttributeHandler.java
new file mode 100644
index 0000000..6de2159
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/BinaryAttributeHandler.java
@@ -0,0 +1,45 @@
+/*
+ $Id: BinaryAttributeHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import edu.vt.middleware.ldap.LdapUtil;
+
+/**
+ * <code>BinaryAttributeHandler</code> ensures that any attribute that contains
+ * a value of type byte[] is base64 encoded.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class BinaryAttributeHandler extends CopyAttributeHandler
+{
+
+
+ /**
+ * This base64 encodes the supplied value if it is of type byte[].
+ *
+ * @param sc <code>SearchCriteria</code> used to find enumeration
+ * @param value <code>Object</code> to process
+ *
+ * @return <code>Object</code>
+ */
+ protected Object processValue(final SearchCriteria sc, final Object value)
+ {
+ if (value instanceof byte[]) {
+ return LdapUtil.base64Encode((byte[]) value);
+ } else {
+ return value;
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/BinarySearchResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/BinarySearchResultHandler.java
new file mode 100644
index 0000000..61b2f39
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/BinarySearchResultHandler.java
@@ -0,0 +1,33 @@
+/*
+ $Id: BinarySearchResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+/**
+ * <code>BinarySearchResultHandler</code> provides a search result handler which
+ * uses {@link BinaryAttributeHandler}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class BinarySearchResultHandler extends CopySearchResultHandler
+{
+
+
+ /** Creates a new <code>BinarySearchResultHandler</code>. */
+ public BinarySearchResultHandler()
+ {
+ this.setAttributeHandler(
+ new AttributeHandler[] {new BinaryAttributeHandler()});
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/CaseChangeAttributeHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/CaseChangeAttributeHandler.java
new file mode 100644
index 0000000..6a52604
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/CaseChangeAttributeHandler.java
@@ -0,0 +1,113 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.BasicAttribute;
+import edu.vt.middleware.ldap.handler.CaseChangeSearchResultHandler.CaseChange;
+
+/**
+ * <code>CaseChangeAttributeHandler</code> provides the ability to modify the
+ * case of attribute names and attribute values.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 18:10:53 -0400 (Sun, 23 May 2010) $
+ */
+public class CaseChangeAttributeHandler extends CopyAttributeHandler
+{
+
+ /** Type of case modification to make to the attribute names. */
+ private CaseChange attributeNameCaseChange = CaseChange.NONE;
+
+ /** Type of case modification to make to the attributes values. */
+ private CaseChange attributeValueCaseChange = CaseChange.NONE;
+
+
+ /**
+ * Returns the attribute name case change.
+ *
+ * @return <code>CaseChange</code>
+ */
+ public CaseChange getAttributeNameCaseChange()
+ {
+ return this.attributeNameCaseChange;
+ }
+
+
+ /**
+ * Sets the attribute name case change.
+ *
+ * @param caseChange <code>CaseChange</code>
+ */
+ public void setAttributeNameCaseChange(final CaseChange caseChange)
+ {
+ this.attributeNameCaseChange = caseChange;
+ }
+
+
+ /**
+ * Returns the attribute value case change.
+ *
+ * @return <code>CaseChange</code>
+ */
+ public CaseChange getAttributeValueCaseChange()
+ {
+ return this.attributeValueCaseChange;
+ }
+
+
+ /**
+ * Sets the attribute value case change.
+ *
+ * @param caseChange <code>CaseChange</code>
+ */
+ public void setAttributeValueCaseChange(final CaseChange caseChange)
+ {
+ this.attributeValueCaseChange = caseChange;
+ }
+
+
+ /** {@inheritDoc} */
+ protected Attribute processResult(
+ final SearchCriteria sc,
+ final Attribute attr)
+ throws NamingException
+ {
+ Attribute newAttr = null;
+ if (attr != null) {
+ newAttr = new BasicAttribute(
+ CaseChange.perform(this.attributeNameCaseChange, attr.getID()),
+ attr.isOrdered());
+
+ final NamingEnumeration<?> en = attr.getAll();
+ while (en.hasMore()) {
+ newAttr.add(this.processValue(sc, en.next()));
+ }
+ }
+ return newAttr;
+ }
+
+
+ /** {@inheritDoc} */
+ protected Object processValue(final SearchCriteria sc, final Object value)
+ {
+ if (value instanceof String) {
+ return CaseChange.perform(this.attributeValueCaseChange, (String) value);
+ } else {
+ return value;
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/CaseChangeSearchResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/CaseChangeSearchResultHandler.java
new file mode 100644
index 0000000..3553a8f
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/CaseChangeSearchResultHandler.java
@@ -0,0 +1,150 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.directory.SearchResult;
+
+/**
+ * <code>CaseSearchResultHandler</code> provides the ability to modify the case
+ * of ldap search result DNs, attribute names, and attribute values.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 18:10:53 -0400 (Sun, 23 May 2010) $
+ */
+public class CaseChangeSearchResultHandler extends CopySearchResultHandler
+{
+
+ /** Enum to define the type of case change. */
+ public enum CaseChange {
+
+ /** no case change. */
+ NONE,
+
+ /** lower case. */
+ LOWER,
+
+ /** upper case. */
+ UPPER;
+
+
+ /**
+ * This changes the supplied string based on the supplied case change.
+ *
+ * @param cc <code>CaseChange</code> to perform
+ * @param string <code>String</code> to modify
+ *
+ * @return <code>String</code> that has been changed
+ */
+ public static String perform(final CaseChange cc, final String string)
+ {
+ String s = null;
+ if (CaseChange.LOWER == cc) {
+ s = string.toLowerCase();
+ } else if (CaseChange.UPPER == cc) {
+ s = string.toUpperCase();
+ } else if (CaseChange.NONE == cc) {
+ s = string;
+ }
+ return s;
+ }
+ }
+
+
+ /** Type of case modification to make to the entry DN. */
+ private CaseChange dnCaseChange = CaseChange.NONE;
+
+ /** Attribute handler for case modifications. */
+ private CaseChangeAttributeHandler attributeHandler =
+ new CaseChangeAttributeHandler();
+
+
+ /** Creates a new <code>CaseSearchResultHandler</code>. */
+ public CaseChangeSearchResultHandler()
+ {
+ this.setAttributeHandler(new AttributeHandler[] {this.attributeHandler});
+ }
+
+
+ /**
+ * Returns the DN case change.
+ *
+ * @return <code>CaseChange</code>
+ */
+ public CaseChange getDnCaseChange()
+ {
+ return this.dnCaseChange;
+ }
+
+
+ /**
+ * Sets the DN case change.
+ *
+ * @param caseChange <code>CaseChange</code>
+ */
+ public void setDnCaseChange(final CaseChange caseChange)
+ {
+ this.dnCaseChange = caseChange;
+ }
+
+
+ /**
+ * Returns the attribute name case change.
+ *
+ * @return <code>CaseChange</code>
+ */
+ public CaseChange getAttributeNameCaseChange()
+ {
+ return this.attributeHandler.getAttributeNameCaseChange();
+ }
+
+
+ /**
+ * Sets the attribute name case change.
+ *
+ * @param caseChange <code>CaseChange</code>
+ */
+ public void setAttributeNameCaseChange(final CaseChange caseChange)
+ {
+ this.attributeHandler.setAttributeNameCaseChange(caseChange);
+ }
+
+
+ /**
+ * Returns the attribute value case change.
+ *
+ * @return <code>CaseChange</code>
+ */
+ public CaseChange getAttributeValueCaseChange()
+ {
+ return this.attributeHandler.getAttributeValueCaseChange();
+ }
+
+
+ /**
+ * Sets the attribute value case change.
+ *
+ * @param caseChange <code>CaseChange</code>
+ */
+ public void setAttributeValueCaseChange(final CaseChange caseChange)
+ {
+ this.attributeHandler.setAttributeValueCaseChange(caseChange);
+ }
+
+
+ /** {@inheritDoc} */
+ protected String processDn(final SearchCriteria sc, final SearchResult sr)
+ {
+ return CaseChange.perform(this.dnCaseChange, sr.getName());
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/ConnectionHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/ConnectionHandler.java
new file mode 100644
index 0000000..e2f4876
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/ConnectionHandler.java
@@ -0,0 +1,143 @@
+/*
+ $Id: ConnectionHandler.java 1616 2010-09-21 17:22:27Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1616 $
+ Updated: $Date: 2010-09-21 18:22:27 +0100 (Tue, 21 Sep 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+import edu.vt.middleware.ldap.LdapConfig;
+
+/**
+ * ConnectionHandler provides an interface for creating and closing LDAP
+ * connections.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1616 $
+ */
+public interface ConnectionHandler
+{
+
+ /** Enum to define the type of connection strategy. */
+ public enum ConnectionStrategy {
+
+ /** default strategy. */
+ DEFAULT,
+
+ /** active-passive strategy. */
+ ACTIVE_PASSIVE,
+
+ /** round robin strategy. */
+ ROUND_ROBIN,
+
+ /** random strategy. */
+ RANDOM,
+ }
+
+
+ /**
+ * Returns the connection strategy.
+ *
+ * @return strategy for making connections
+ */
+ ConnectionStrategy getConnectionStrategy();
+
+
+ /**
+ * Sets the connection strategy.
+ *
+ * @param strategy for making connections
+ */
+ void setConnectionStrategy(ConnectionStrategy strategy);
+
+
+ /**
+ * This returns the exception types to retry connections on.
+ *
+ * @return <code>Class[]</code>
+ */
+ Class<?>[] getConnectionRetryExceptions();
+
+
+ /**
+ * This sets the exception types to retry connections on.
+ *
+ * @param exceptions <code>Class[]</code>
+ */
+ void setConnectionRetryExceptions(Class<?>[] exceptions);
+
+
+ /**
+ * Returns the ldap configuration.
+ *
+ * @return ldap config
+ */
+ LdapConfig getLdapConfig();
+
+
+ /**
+ * Sets the ldap configuration.
+ *
+ * @param lc ldap config
+ */
+ void setLdapConfig(LdapConfig lc);
+
+
+ /**
+ * Open a connection to an LDAP.
+ *
+ * @param dn to attempt bind with
+ * @param credential to attempt bind with
+ *
+ * @throws NamingException if an LDAP error occurs
+ */
+ void connect(String dn, Object credential)
+ throws NamingException;
+
+
+ /**
+ * Returns whether the underlying context has been established.
+ *
+ * @return whether a connection has been made
+ */
+ boolean isConnected();
+
+
+ /**
+ * Returns an ldap context to use for ldap operations. {@link #connect(String,
+ * Object)} must be called prior to invoking this.
+ *
+ * @return ldap context
+ *
+ * @throws NamingException if an LDAP error occurs
+ */
+ LdapContext getLdapContext()
+ throws NamingException;
+
+
+ /**
+ * Close a connection to an LDAP.
+ *
+ * @throws NamingException if an LDAP error occurs
+ */
+ void close()
+ throws NamingException;
+
+
+ /**
+ * Returns a separate instance of this connection handler with the same
+ * underlying ldap configuration.
+ *
+ * @return connection handler
+ */
+ ConnectionHandler newInstance();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/CopyAttributeHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/CopyAttributeHandler.java
new file mode 100644
index 0000000..e935163
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/CopyAttributeHandler.java
@@ -0,0 +1,73 @@
+/*
+ $Id: CopyAttributeHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.BasicAttribute;
+
+/**
+ * <code>CopyAttributeHandler</code> converts a NamingEnumeration of attribute
+ * into a List of attribute.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class CopyAttributeHandler extends CopyResultHandler<Attribute>
+ implements AttributeHandler
+{
+
+
+ /**
+ * This will return a deep copy of the supplied <code>Attribute</code>.
+ *
+ * @param sc <code>SearchCriteria</code> used to find enumeration
+ * @param attr <code>Attribute</code> to copy
+ *
+ * @return <code>Attribute</code>
+ *
+ * @throws NamingException if the attribute values cannot be read
+ */
+ protected Attribute processResult(
+ final SearchCriteria sc,
+ final Attribute attr)
+ throws NamingException
+ {
+ Attribute newAttr = null;
+ if (attr != null) {
+ newAttr = new BasicAttribute(attr.getID(), attr.isOrdered());
+
+ final NamingEnumeration<?> en = attr.getAll();
+ while (en.hasMore()) {
+ newAttr.add(this.processValue(sc, en.next()));
+ }
+ }
+ return newAttr;
+ }
+
+
+ /**
+ * This returns the supplied value unaltered.
+ *
+ * @param sc <code>LdapSearchCritieria</code> used to find enumeration
+ * @param value <code>Object</code> to process
+ *
+ * @return <code>Object</code>
+ */
+ protected Object processValue(final SearchCriteria sc, final Object value)
+ {
+ return value;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/CopyResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/CopyResultHandler.java
new file mode 100644
index 0000000..6c4826c
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/CopyResultHandler.java
@@ -0,0 +1,46 @@
+/*
+ $Id: CopyResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.NamingException;
+
+/**
+ * <code>CopyResultHandler</code> converts a NamingEnumeration into a List of
+ * ldap results.
+ *
+ * @param <T> type of result
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class CopyResultHandler<T> extends AbstractResultHandler<T, T>
+{
+
+
+ /**
+ * Returns the supplied result unaltered.
+ *
+ * @param sc <code>SearchCriteria</code> used to retrieve the result
+ * @param r <code>T</code> to process
+ *
+ * @return <code>T</code> result that was supplied
+ *
+ * @throws NamingException if the supplied result cannot be read
+ */
+ protected T processResult(final SearchCriteria sc, final T r)
+ throws NamingException
+ {
+ return r;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/CopySearchResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/CopySearchResultHandler.java
new file mode 100644
index 0000000..2c5b29c
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/CopySearchResultHandler.java
@@ -0,0 +1,111 @@
+/*
+ $Id: CopySearchResultHandler.java 1786 2011-01-05 14:45:07Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1786 $
+ Updated: $Date: 2011-01-05 14:45:07 +0000 (Wed, 05 Jan 2011) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchResult;
+
+/**
+ * <code>CopySearchResultHandler</code> converts a NamingEnumeration of search
+ * results into a List of search results.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1786 $ $Date: 2011-01-05 14:45:07 +0000 (Wed, 05 Jan 2011) $
+ */
+public class CopySearchResultHandler extends CopyResultHandler<SearchResult>
+ implements SearchResultHandler
+{
+
+ /** Attribute handler. */
+ private AttributeHandler[] attributeHandler;
+
+
+ /** {@inheritDoc} */
+ public AttributeHandler[] getAttributeHandler()
+ {
+ return this.attributeHandler;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setAttributeHandler(final AttributeHandler[] ah)
+ {
+ this.attributeHandler = ah;
+ }
+
+
+ /**
+ * This will return a deep copy of the supplied <code>SearchResult</code>.
+ *
+ * @param sc <code>SearchCriteria</code> used to find enumeration
+ * @param sr <code>SearchResult</code> to copy
+ *
+ * @return <code>SearchResult</code>
+ *
+ * @throws NamingException if the result cannot be read
+ */
+ protected SearchResult processResult(
+ final SearchCriteria sc,
+ final SearchResult sr)
+ throws NamingException
+ {
+ return
+ new SearchResult(
+ this.processDn(sc, sr),
+ sr.getClassName(),
+ sr.getObject(),
+ this.processAttributes(sc, sr),
+ sr.isRelative());
+ }
+
+
+ /**
+ * Process the dn of an ldap search result.
+ *
+ * @param sc <code>SearchCriteria</code> used to find search result
+ * @param sr <code>SearchResult</code> to extract the dn from
+ *
+ * @return <code>String</code> processed dn
+ */
+ protected String processDn(final SearchCriteria sc, final SearchResult sr)
+ {
+ return sr.getName();
+ }
+
+
+ /**
+ * Process the attributes of an ldap search.
+ *
+ * @param sc <code>SearchCriteria</code> used to find search result
+ * @param sr <code>SearchResult</code> to extract the attributes from
+ *
+ * @return <code>Attributes</code> processed attributes
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ protected Attributes processAttributes(
+ final SearchCriteria sc,
+ final SearchResult sr)
+ throws NamingException
+ {
+ Attributes newAttrs = sr.getAttributes();
+ if (this.attributeHandler != null && this.attributeHandler.length > 0) {
+ for (AttributeHandler ah : this.attributeHandler) {
+ newAttrs = AttributesProcessor.executeHandler(sc, newAttrs, ah);
+ }
+ }
+ return newAttrs;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/DefaultConnectionHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/DefaultConnectionHandler.java
new file mode 100644
index 0000000..d9ebc95
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/DefaultConnectionHandler.java
@@ -0,0 +1,153 @@
+/*
+ $Id: DefaultConnectionHandler.java 2231 2012-02-02 15:46:27Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2231 $
+ Updated: $Date: 2012-02-02 15:46:27 +0000 (Thu, 02 Feb 2012) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import java.util.Hashtable;
+import javax.naming.NamingException;
+import javax.naming.ldap.InitialLdapContext;
+import edu.vt.middleware.ldap.LdapConfig;
+import edu.vt.middleware.ldap.LdapConstants;
+import edu.vt.middleware.ldap.ssl.ThreadLocalTLSSocketFactory;
+
+/**
+ * <code>DefaultConnectionHandler</code> creates a new <code>LdapContext</code>
+ * using environment properties obtained from {@link
+ * LdapConfig#getEnvironment()}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2231 $
+ */
+public class DefaultConnectionHandler extends AbstractConnectionHandler
+{
+
+
+ /** Default constructor. */
+ public DefaultConnectionHandler() {}
+
+
+ /**
+ * Creates a new <code>DefaultConnectionHandler</code> with the supplied ldap
+ * config.
+ *
+ * @param lc ldap config
+ */
+ public DefaultConnectionHandler(final LdapConfig lc)
+ {
+ this.setLdapConfig(lc);
+ }
+
+
+ /**
+ * Copy constructor for <code>DefaultConnectionHandler</code>.
+ *
+ * @param ch to copy properties from
+ */
+ public DefaultConnectionHandler(final DefaultConnectionHandler ch)
+ {
+ this.setLdapConfig(ch.getLdapConfig());
+ this.setConnectionStrategy(ch.getConnectionStrategy());
+ this.setConnectionRetryExceptions(ch.getConnectionRetryExceptions());
+ this.setConnectionCount(ch.getConnectionCount());
+ }
+
+
+ /** {@inheritDoc} */
+ protected void connectInternal(
+ final String authtype,
+ final String dn,
+ final Object credential,
+ final Hashtable<String, Object> env)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Bind with the following parameters:");
+ this.logger.debug(" authtype = " + authtype);
+ this.logger.debug(" dn = " + dn);
+ if (this.config.getLogCredentials()) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(" credential = " + credential);
+ }
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(" credential = <suppressed>");
+ }
+ }
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" env = " + env);
+ }
+ }
+
+ // note that when using simple authentication (the default),
+ // if the credential is null the provider will automatically revert the
+ // authentication to none
+ env.put(LdapConstants.AUTHENTICATION, authtype);
+ if (dn != null) {
+ env.put(LdapConstants.PRINCIPAL, dn);
+ if (credential != null) {
+ env.put(LdapConstants.CREDENTIALS, credential);
+ }
+ }
+
+ // JNDI does not perform hostname validation for LDAPS
+ // set a socket factory that will
+ if (LdapConstants.SSL_PROTOCOL.equals(env.get(LdapConstants.PROTOCOL)) ||
+ ((String) env.get(LdapConstants.PROVIDER_URL)).toLowerCase().contains(
+ "ldaps://")) {
+ if (env.get(LdapConstants.SOCKET_FACTORY) == null) {
+ // parse hostnames for validation
+ final String[] hostnames =
+ ((String) env.get(LdapConstants.PROVIDER_URL)).split(" ");
+ for (int i = 0; i < hostnames.length; i++) {
+ // remove scheme, if it exists
+ if (hostnames[i].startsWith("ldap://")) {
+ hostnames[i] = hostnames[i].substring("ldap://".length());
+ } else if (hostnames[i].startsWith("ldaps://")) {
+ hostnames[i] = hostnames[i].substring("ldaps://".length());
+ }
+ // remove port, if it exist
+ if (hostnames[i].indexOf(":") != -1) {
+ hostnames[i] = hostnames[i].substring(0, hostnames[i].indexOf(":"));
+ }
+ }
+ ThreadLocalTLSSocketFactory.getHostnameVerifierFactory(hostnames);
+ env.put(
+ LdapConstants.SOCKET_FACTORY,
+ ThreadLocalTLSSocketFactory.class.getName());
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Set hostname verifier for ldaps");
+ }
+ }
+ }
+
+ try {
+ this.context = new InitialLdapContext(env, null);
+ } catch (NamingException e) {
+ if (this.context != null) {
+ try {
+ this.context.close();
+ } finally {
+ this.context = null;
+ }
+ }
+ throw e;
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public DefaultConnectionHandler newInstance()
+ {
+ return new DefaultConnectionHandler(this);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/EntryDnSearchResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/EntryDnSearchResultHandler.java
new file mode 100644
index 0000000..40125ec
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/EntryDnSearchResultHandler.java
@@ -0,0 +1,101 @@
+/*
+ $Id: EntryDnSearchResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchResult;
+
+/**
+ * <code>EntryDnSearchResultHandler</code> adds the search result DN as an
+ * attribute to the result set. Provides a client side implementation of RFC
+ * 5020.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class EntryDnSearchResultHandler extends CopySearchResultHandler
+{
+
+ /**
+ * Attribute name for the entry dn. The value of this constant is {@value}.
+ */
+ private String dnAttributeName = "entryDN";
+
+ /**
+ * Whether to add the entry dn if an attribute of the same name exists. The
+ * value of this constant is {@value}.
+ */
+ private boolean addIfExists;
+
+
+ /**
+ * Returns the DN attribute name.
+ *
+ * @return <code>String</code>
+ */
+ public String getDnAttributeName()
+ {
+ return this.dnAttributeName;
+ }
+
+
+ /**
+ * Sets the DN attribute name.
+ *
+ * @param s <code>String</code>
+ */
+ public void setDnAttributeName(final String s)
+ {
+ this.dnAttributeName = s;
+ }
+
+
+ /**
+ * Returns whether to add the entryDN if an attribute of the same name exists.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isAddIfExists()
+ {
+ return this.addIfExists;
+ }
+
+
+ /**
+ * Sets whether to add the entryDN if an attribute of the same name exists.
+ *
+ * @param b <code>boolean</code>
+ */
+ public void setAddIfExists(final boolean b)
+ {
+ this.addIfExists = b;
+ }
+
+
+ /** {@inheritDoc} */
+ protected Attributes processAttributes(
+ final SearchCriteria sc,
+ final SearchResult sr)
+ throws NamingException
+ {
+ final Attributes newAttrs = sr.getAttributes();
+ if (newAttrs.get(this.dnAttributeName) == null) {
+ newAttrs.put(this.dnAttributeName, sr.getName());
+ } else if (this.addIfExists) {
+ newAttrs.get(this.dnAttributeName).add(sr.getName());
+ }
+ return newAttrs;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/ExtendedAttributeHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/ExtendedAttributeHandler.java
new file mode 100644
index 0000000..98d6906
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/ExtendedAttributeHandler.java
@@ -0,0 +1,45 @@
+/*
+ $Id: ExtendedAttributeHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import edu.vt.middleware.ldap.Ldap;
+
+/**
+ * Provides an interface for attribute handlers that require the use of the
+ * <code>Ldap</code> object that was used to perform the original search.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public interface ExtendedAttributeHandler extends AttributeHandler
+{
+
+
+ /**
+ * Gets the <code>Ldap</code> used by the search operation invoking this
+ * handler.
+ *
+ * @return <code>Ldap</code>
+ */
+ Ldap getSearchResultLdap();
+
+
+ /**
+ * Sets the <code>Ldap</code> used by the search operation invoking this
+ * handler.
+ *
+ * @param l <code>Ldap</code>
+ */
+ void setSearchResultLdap(final Ldap l);
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/ExtendedSearchResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/ExtendedSearchResultHandler.java
new file mode 100644
index 0000000..9d1982f
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/ExtendedSearchResultHandler.java
@@ -0,0 +1,45 @@
+/*
+ $Id: ExtendedSearchResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import edu.vt.middleware.ldap.Ldap;
+
+/**
+ * Provides an interface for search result handlers that require the use of the
+ * <code>Ldap</code> object that was used to perform the original search.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public interface ExtendedSearchResultHandler extends SearchResultHandler
+{
+
+
+ /**
+ * Gets the <code>Ldap</code> used by the search operation invoking this
+ * handler.
+ *
+ * @return <code>Ldap</code>
+ */
+ Ldap getSearchResultLdap();
+
+
+ /**
+ * Sets the <code>Ldap</code> used by the search operation invoking this
+ * handler.
+ *
+ * @param l <code>Ldap</code>
+ */
+ void setSearchResultLdap(final Ldap l);
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/FqdnSearchResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/FqdnSearchResultHandler.java
new file mode 100644
index 0000000..b98eb06
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/FqdnSearchResultHandler.java
@@ -0,0 +1,126 @@
+/*
+ $Id: FqdnSearchResultHandler.java 2023 2011-07-11 14:50:38Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2023 $
+ Updated: $Date: 2011-07-11 15:50:38 +0100 (Mon, 11 Jul 2011) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import java.net.URI;
+import javax.naming.CompositeName;
+import javax.naming.InvalidNameException;
+import javax.naming.directory.SearchResult;
+
+/**
+ * <code>FqdnSearchResultHandler</code> ensures that the DN of a search result
+ * is fully qualified. Any non-relative names will have the URL removed if
+ * {@link #getRemoveUrls()} is true.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2023 $ $Date: 2011-07-11 15:50:38 +0100 (Mon, 11 Jul 2011) $
+ */
+public class FqdnSearchResultHandler extends CopySearchResultHandler
+{
+
+ /** Whether to remove the URL from any DNs which are not relative. */
+ private boolean removeUrls = true;
+
+
+ /**
+ * Returns whether the URL will be removed from any DNs which are not
+ * relative. The default value is true.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean getRemoveUrls()
+ {
+ return this.removeUrls;
+ }
+
+
+ /**
+ * Sets whether the URL will be removed from any DNs which are not relative
+ * The default value is true.
+ *
+ * @param b <code>boolean</code>
+ */
+ public void setRemoveUrls(final boolean b)
+ {
+ this.removeUrls = b;
+ }
+
+
+ /** {@inheritDoc} */
+ protected String processDn(final SearchCriteria sc, final SearchResult sr)
+ {
+ String newDn = null;
+ final String resultName = sr.getName();
+ if (resultName != null) {
+ StringBuffer fqName = null;
+ if (sr.isRelative()) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("processing relative dn: " + resultName);
+ }
+ if (sc.getDn() != null) {
+ if (!"".equals(resultName)) {
+ fqName = new StringBuffer(
+ readCompositeName(resultName)).append(",").append(sc.getDn());
+ } else {
+ fqName = new StringBuffer(sc.getDn());
+ }
+ } else {
+ fqName = new StringBuffer(readCompositeName(resultName));
+ }
+ } else {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("processing non-relative dn: " + resultName);
+ }
+ if (this.removeUrls) {
+ fqName = new StringBuffer(
+ readCompositeName(URI.create(resultName).getPath().substring(1)));
+ } else {
+ fqName = new StringBuffer(readCompositeName(resultName));
+ }
+ }
+ newDn = fqName.toString();
+ }
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("processed dn: " + newDn);
+ }
+ return newDn;
+ }
+
+
+ /**
+ * Uses a <code>CompositeName</code> to parse the supplied string.
+ *
+ * @param s <code>String</code> composite name to read
+ *
+ * @return <code>String</code> ldap name
+ */
+ private String readCompositeName(final String s)
+ {
+ final StringBuffer name = new StringBuffer();
+ try {
+ final CompositeName cName = new CompositeName(s);
+ for (int i = 0; i < cName.size(); i++) {
+ name.append(cName.get(i));
+ if (i + 1 < cName.size()) {
+ name.append("/");
+ }
+ }
+ } catch (InvalidNameException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error formatting name: " + s, e);
+ }
+ }
+ return name.toString();
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/MergeSearchResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/MergeSearchResultHandler.java
new file mode 100644
index 0000000..b7a5b55
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/MergeSearchResultHandler.java
@@ -0,0 +1,137 @@
+/*
+ $Id: MergeSearchResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.SearchResult;
+
+/**
+ * <code>MergeSearchResultHandler</code> merges the attributes found in each
+ * search result into the first search result.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class MergeSearchResultHandler extends CopySearchResultHandler
+{
+
+ /** Whether to allow duplicate attribute values. */
+ private boolean allowDuplicates;
+
+
+ /**
+ * Returns whether to allow duplicate attribute values.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean getAllowDuplicates()
+ {
+ return this.allowDuplicates;
+ }
+
+
+ /**
+ * Sets whether to allow duplicate attribute values.
+ *
+ * @param b <code>boolean</code>
+ */
+ public void setAllowDuplicates(final boolean b)
+ {
+ this.allowDuplicates = b;
+ }
+
+
+ /** {@inheritDoc} */
+ public List<SearchResult> process(
+ final SearchCriteria sc,
+ final NamingEnumeration<? extends SearchResult> en,
+ final Class<?>[] ignore)
+ throws NamingException
+ {
+ return this.mergeResults(super.process(sc, en, ignore));
+ }
+
+
+ /** {@inheritDoc} */
+ public List<SearchResult> process(
+ final SearchCriteria sc,
+ final List<? extends SearchResult> l)
+ throws NamingException
+ {
+ return this.mergeResults(super.process(sc, l));
+ }
+
+
+ /**
+ * Merges the search results in the supplied list into a single search result.
+ * This method always returns a list of size zero or one.
+ *
+ * @param results <code>List</code> of search results to merge
+ *
+ * @return <code>List</code> of merged search results
+ *
+ * @throws NamingException if an error occurs reading attribute values
+ */
+ protected List<SearchResult> mergeResults(final List<SearchResult> results)
+ throws NamingException
+ {
+ final List<SearchResult> mergedResults = new ArrayList<SearchResult>();
+ SearchResult mergedResult = null;
+ for (SearchResult sr : results) {
+ if (mergedResult == null) {
+ mergedResult = sr;
+ } else {
+ final NamingEnumeration<? extends Attribute> en = sr.getAttributes()
+ .getAll();
+ while (en.hasMore()) {
+ final Attribute newAttr = en.next();
+ final Attribute oldAttr = mergedResult.getAttributes().get(
+ newAttr.getID());
+ if (oldAttr == null) {
+ mergedResult.getAttributes().put(newAttr);
+ } else {
+ final NamingEnumeration<?> newValues = newAttr.getAll();
+ while (newValues.hasMore()) {
+ final Object newValue = newValues.next();
+ if (this.allowDuplicates) {
+ oldAttr.add(newValue);
+ } else {
+ boolean add = true;
+ final NamingEnumeration<?> existingValues = oldAttr.getAll();
+ while (existingValues.hasMore()) {
+ final Object existingValue = existingValues.next();
+ if (existingValue.equals(newValue)) {
+ add = false;
+ break;
+ }
+ }
+ if (add) {
+ oldAttr.add(newValue);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (mergedResult != null) {
+ mergedResults.add(mergedResult);
+ }
+ return mergedResults;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/RecursiveAttributeHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/RecursiveAttributeHandler.java
new file mode 100644
index 0000000..6731bdc
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/RecursiveAttributeHandler.java
@@ -0,0 +1,187 @@
+/*
+ $Id: RecursiveAttributeHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import edu.vt.middleware.ldap.Ldap;
+
+/**
+ * <code>RecursiveAttributeHandler</code> will recursively search for attributes
+ * of the same name and combine them into one attribute. Attribute values must
+ * represent DNs in the LDAP.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class RecursiveAttributeHandler extends CopyAttributeHandler
+ implements ExtendedAttributeHandler
+{
+
+ /** Ldap to use for searching. */
+ private Ldap ldap;
+
+ /** Attribute name to search for. */
+ private String attributeName;
+
+
+ /**
+ * Creates a new <code>RecursiveAttributeHandler</code> with the supplied
+ * attribute name.
+ *
+ * @param attrName <code>String</code>
+ */
+ public RecursiveAttributeHandler(final String attrName)
+ {
+ this(null, attrName);
+ }
+
+
+ /**
+ * Creates a new <code>RecursiveAttributeHandler</code> with the supplied ldap
+ * and attribute name.
+ *
+ * @param l <code>Ldap</code>
+ * @param attrName <code>String</code>
+ */
+ public RecursiveAttributeHandler(final Ldap l, final String attrName)
+ {
+ this.ldap = l;
+ this.attributeName = attrName;
+ }
+
+
+ /** {@inheritDoc} */
+ public Ldap getSearchResultLdap()
+ {
+ return this.ldap;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setSearchResultLdap(final Ldap l)
+ {
+ this.ldap = l;
+ }
+
+
+ /**
+ * Returns the attribute name that will be recursively searched on.
+ *
+ * @return <code>String</code> attribute name
+ */
+ public String getAttributeName()
+ {
+ return this.attributeName;
+ }
+
+
+ /**
+ * Sets the attribute name that will be recursively searched on.
+ *
+ * @param s <code>String</code>
+ */
+ public void setAttributeName(final String s)
+ {
+ this.attributeName = s;
+ }
+
+
+ /** {@inheritDoc} */
+ protected Attribute processResult(
+ final SearchCriteria sc,
+ final Attribute attr)
+ throws NamingException
+ {
+ Attribute newAttr = null;
+ if (attr != null) {
+ newAttr = new BasicAttribute(attr.getID(), attr.isOrdered());
+ if (attr.getID().equals(this.attributeName)) {
+ final NamingEnumeration<?> en = attr.getAll();
+ while (en.hasMore()) {
+ final Object rawValue = this.processValue(sc, en.next());
+ if (rawValue instanceof String) {
+ final List<String> recursiveValues = this.recursiveSearch(
+ (String) rawValue,
+ new ArrayList<String>());
+ for (String s : recursiveValues) {
+ newAttr.add(this.processValue(sc, s));
+ }
+ } else {
+ newAttr.add(rawValue);
+ }
+ }
+ } else {
+ final NamingEnumeration<?> en = attr.getAll();
+ while (en.hasMore()) {
+ newAttr.add(this.processValue(sc, en.next()));
+ }
+ }
+ }
+ return newAttr;
+ }
+
+
+ /**
+ * Recursively gets the attribute {@link #attributeName} for the supplied dn.
+ *
+ * @param dn to get attribute for
+ * @param searchedDns list of DNs that have been searched for
+ *
+ * @return list of attribute values found by recursively searching
+ *
+ * @throws NamingException if a search error occurs
+ */
+ private List<String> recursiveSearch(
+ final String dn,
+ final List<String> searchedDns)
+ throws NamingException
+ {
+ final List<String> results = new ArrayList<String>();
+ if (!searchedDns.contains(dn)) {
+
+ Attributes attrs = null;
+ try {
+ attrs = this.ldap.getAttributes(dn, new String[] {this.attributeName});
+ results.add(dn);
+ } catch (NamingException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn(
+ "Error retreiving attribute: " + this.attributeName,
+ e);
+ }
+ }
+ searchedDns.add(dn);
+ if (attrs != null) {
+ final Attribute attr = attrs.get(this.attributeName);
+ if (attr != null) {
+ final NamingEnumeration<?> en = attr.getAll();
+ while (en.hasMore()) {
+ final Object rawValue = en.next();
+ if (rawValue instanceof String) {
+ results.addAll(
+ this.recursiveSearch((String) rawValue, searchedDns));
+ }
+ }
+ }
+ }
+ }
+ return results;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/RecursiveSearchResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/RecursiveSearchResultHandler.java
new file mode 100644
index 0000000..a0f86ff
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/RecursiveSearchResultHandler.java
@@ -0,0 +1,323 @@
+/*
+ $Id: RecursiveSearchResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.Ldap;
+
+/**
+ * <code>RecursiveSearchResultHandler</code> recursively searches based on a
+ * supplied attribute and merges those results into the original result set. For
+ * the following LDIF:
+ *
+ * <pre>
+ dn: uugid=group1,ou=groups,dc=vt,dc=edu
+ uugid: group1
+ member: uugid=group2,ou=groups,dc=vt,dc=edu
+
+ dn: uugid=group2,ou=groups,dc=vt,dc=edu
+ uugid: group2
+ * </pre>
+ *
+ * <p>With the following code:</p>
+ *
+ * <pre>
+ RecursiveSearchResultHandler rsh = new RecurseSearchResultHandler(
+ ldap, "member", new String[]{"uugid"});
+ * </pre>
+ *
+ * <p>Will produce this result for the query (uugid=group1):</p>
+ *
+ * <pre>
+ dn: uugid=group1,ou=groups,dc=vt,dc=edu
+ uugid: group1
+ uugid: group2
+ member: uugid=group2,ou=groups,dc=vt,dc=edu
+ * </pre>
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class RecursiveSearchResultHandler extends CopySearchResultHandler
+ implements ExtendedSearchResultHandler
+{
+
+ /** Ldap to use for searching. */
+ private Ldap ldap;
+
+ /** Attribute to recursively search on. */
+ private String searchAttribute;
+
+ /** Attribute(s) to merge. */
+ private String[] mergeAttributes;
+
+ /** Attributes to return when searching, mergeAttributes + searchAttribute. */
+ private String[] retAttrs;
+
+
+ /** Default constructor. */
+ public RecursiveSearchResultHandler() {}
+
+
+ /**
+ * Creates a new <code>RecursiveAttributeHandler</code> with the supplied
+ * search attribute and merge attributes.
+ *
+ * @param searchAttr <code>String</code>
+ * @param mergeAttrs <code>String[]</code>
+ */
+ public RecursiveSearchResultHandler(
+ final String searchAttr,
+ final String[] mergeAttrs)
+ {
+ this(null, searchAttr, mergeAttrs);
+ }
+
+
+ /**
+ * Creates a new <code>RecursiveAttributeHandler</code> with the supplied
+ * ldap, search attribute, and merge attributes.
+ *
+ * @param l <code>Ldap</code>
+ * @param searchAttr <code>String</code>
+ * @param mergeAttrs <code>String[]</code>
+ */
+ public RecursiveSearchResultHandler(
+ final Ldap l,
+ final String searchAttr,
+ final String[] mergeAttrs)
+ {
+ this.ldap = l;
+ this.searchAttribute = searchAttr;
+ this.mergeAttributes = mergeAttrs;
+ this.initalizeReturnAttributes();
+ }
+
+
+ /** {@inheritDoc} */
+ public Ldap getSearchResultLdap()
+ {
+ return this.ldap;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setSearchResultLdap(final Ldap l)
+ {
+ this.ldap = l;
+ }
+
+
+ /**
+ * Returns the attribute name that will be recursively searched on.
+ *
+ * @return <code>String</code> attribute name
+ */
+ public String getSearchAttribute()
+ {
+ return this.searchAttribute;
+ }
+
+
+ /**
+ * Sets the attribute name that will be recursively searched on.
+ *
+ * @param s <code>String</code>
+ */
+ public void setSearchAttribute(final String s)
+ {
+ this.searchAttribute = s;
+ this.initalizeReturnAttributes();
+ }
+
+
+ /**
+ * Returns the attribute names that will be merged by the recursive search.
+ *
+ * @return <code>String[]</code> attribute names
+ */
+ public String[] getMergeAttributes()
+ {
+ return this.mergeAttributes;
+ }
+
+
+ /**
+ * Sets the attribute name that will be merged by the recursive search.
+ *
+ * @param s <code>String[]</code>
+ */
+ public void setMergeAttributes(final String[] s)
+ {
+ this.mergeAttributes = s;
+ this.initalizeReturnAttributes();
+ }
+
+
+ /**
+ * Initializes the return attributes array. Must be called after both
+ * searchAttribute and mergeAttributes have been set.
+ */
+ protected void initalizeReturnAttributes()
+ {
+ if (this.mergeAttributes != null && this.searchAttribute != null) {
+ // return attributes must include the search attribute
+ this.retAttrs = new String[this.mergeAttributes.length + 1];
+ System.arraycopy(
+ this.mergeAttributes,
+ 0,
+ this.retAttrs,
+ 0,
+ this.mergeAttributes.length);
+ this.retAttrs[this.retAttrs.length - 1] = this.searchAttribute;
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public List<SearchResult> process(
+ final SearchCriteria sc,
+ final NamingEnumeration<? extends SearchResult> en,
+ final Class<?>[] ignore)
+ throws NamingException
+ {
+ return this.processInternal(super.process(sc, en, ignore));
+ }
+
+
+ /** {@inheritDoc} */
+ public List<SearchResult> process(
+ final SearchCriteria sc,
+ final List<? extends SearchResult> l)
+ throws NamingException
+ {
+ return this.processInternal(super.process(sc, l));
+ }
+
+
+ /**
+ * Recursively searches a list of attributes and merges those results with the
+ * existing search result set.
+ *
+ * @param results <code>List</code> of search results to merge with
+ *
+ * @return <code>List</code> of merged search results
+ *
+ * @throws NamingException if an error occurs reading attribute values
+ */
+ private List<SearchResult> processInternal(final List<SearchResult> results)
+ throws NamingException
+ {
+ for (SearchResult sr : results) {
+ final List<String> searchedDns = new ArrayList<String>();
+ if (sr.getAttributes().get(this.searchAttribute) != null) {
+ searchedDns.add(sr.getName());
+ this.readSearchAttribute(sr.getAttributes(), searchedDns);
+ } else {
+ this.recursiveSearch(sr.getName(), sr.getAttributes(), searchedDns);
+ }
+ }
+ return results;
+ }
+
+
+ /**
+ * Reads the values of {@link #searchAttribute} from the supplied attributes
+ * and calls {@link #recursiveSearch} for each.
+ *
+ * @param attrs to read
+ * @param searchedDns list of DNs whose attributes have been read
+ *
+ * @throws NamingException if a search error occurs
+ */
+ private void readSearchAttribute(
+ final Attributes attrs,
+ final List<String> searchedDns)
+ throws NamingException
+ {
+ if (attrs != null) {
+ final Attribute attr = attrs.get(this.searchAttribute);
+ if (attr != null) {
+ final NamingEnumeration<?> en = attr.getAll();
+ while (en.hasMore()) {
+ final Object rawValue = en.next();
+ if (rawValue instanceof String) {
+ this.recursiveSearch((String) rawValue, attrs, searchedDns);
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Recursively gets the attribute(s) {@link #mergeAttributes} for the supplied
+ * dn and adds the values to the supplied attributes.
+ *
+ * @param dn to get attribute(s) for
+ * @param attrs to merge with
+ * @param searchedDns list of DNs that have been searched for
+ *
+ * @throws NamingException if a search error occurs
+ */
+ private void recursiveSearch(
+ final String dn,
+ final Attributes attrs,
+ final List<String> searchedDns)
+ throws NamingException
+ {
+ if (!searchedDns.contains(dn)) {
+
+ Attributes newAttrs = null;
+ try {
+ newAttrs = this.ldap.getAttributes(dn, this.retAttrs);
+ } catch (NamingException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn(
+ "Error retreiving attribute(s): " + Arrays.toString(this.retAttrs),
+ e);
+ }
+ }
+ searchedDns.add(dn);
+
+ if (newAttrs != null) {
+ // recursively search new attributes
+ this.readSearchAttribute(newAttrs, searchedDns);
+
+ // merge new attribute values
+ for (String s : this.mergeAttributes) {
+ final Attribute newAttr = newAttrs.get(s);
+ if (newAttr != null) {
+ final Attribute oldAttr = attrs.get(s);
+ if (oldAttr == null) {
+ attrs.put(newAttr);
+ } else {
+ final NamingEnumeration<?> newValues = newAttr.getAll();
+ while (newValues.hasMore()) {
+ oldAttr.add(newValues.next());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/ResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/ResultHandler.java
new file mode 100644
index 0000000..01e5622
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/ResultHandler.java
@@ -0,0 +1,77 @@
+/*
+ $Id: ResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import java.util.List;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+
+/**
+ * ResultHandler provides post search processing of ldap results.
+ *
+ * @param <R> type of result
+ * @param <O> type of output
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public interface ResultHandler<R, O>
+{
+
+
+ /**
+ * Process the results from an ldap search.
+ *
+ * @param sc <code>SearchCriteria</code> used to perform the search
+ * @param en <code>NamingEnumeration</code> of search results
+ *
+ * @return <code>List</code> of result objects
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ List<O> process(SearchCriteria sc, NamingEnumeration<? extends R> en)
+ throws NamingException;
+
+
+ /**
+ * Process the results from an ldap search.
+ *
+ * @param sc <code>SearchCriteria</code> used to perform the search
+ * @param en <code>NamingEnumeration</code> of search results
+ * @param ignore <code>Class[]</code> of exception types to ignore results
+ *
+ * @return <code>List</code> of result objects
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ List<O> process(
+ SearchCriteria sc,
+ NamingEnumeration<? extends R> en,
+ Class<?>[] ignore)
+ throws NamingException;
+
+
+ /**
+ * Process the results from an ldap search.
+ *
+ * @param sc <code>SearchCriteria</code> used to perform the search
+ * @param l <code>List</code> of search results
+ *
+ * @return <code>List</code> of result objects
+ *
+ * @throws NamingException if the LDAP returns an error
+ */
+ List<O> process(SearchCriteria sc, List<? extends R> l)
+ throws NamingException;
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/SearchCriteria.java b/src/main/java/edu/vt/middleware/ldap/handler/SearchCriteria.java
new file mode 100644
index 0000000..4f43bde
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/SearchCriteria.java
@@ -0,0 +1,186 @@
+/*
+ $Id: SearchCriteria.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.directory.Attributes;
+
+/**
+ * <code>SearchCriteria</code> contains the attributes used to perform ldap
+ * searches.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class SearchCriteria
+{
+
+ /** dn. */
+ private String dn;
+
+ /** filter. */
+ private String filter;
+
+ /** filter arguments. */
+ private Object[] filterArgs;
+
+ /** return attributes. */
+ private String[] returnAttrs;
+
+ /** match attributes. */
+ private Attributes matchAttrs;
+
+
+ /** Default constructor. */
+ public SearchCriteria() {}
+
+
+ /**
+ * Creates a new search criteria with the supplied dn.
+ *
+ * @param s to set dn
+ */
+ public SearchCriteria(final String s)
+ {
+ this.dn = s;
+ }
+
+
+ /**
+ * Gets the dn.
+ *
+ * @return dn
+ */
+ public String getDn()
+ {
+ return this.dn;
+ }
+
+
+ /**
+ * Sets the dn.
+ *
+ * @param s to set dn
+ */
+ public void setDn(final String s)
+ {
+ this.dn = s;
+ }
+
+
+ /**
+ * Gets the filter.
+ *
+ * @return filter
+ */
+ public String getFilter()
+ {
+ return this.filter;
+ }
+
+
+ /**
+ * Sets the filter.
+ *
+ * @param s to set filter
+ */
+ public void setFilter(final String s)
+ {
+ this.filter = s;
+ }
+
+
+ /**
+ * Gets the filter arguments.
+ *
+ * @return filter args
+ */
+ public Object[] getFilterArgs()
+ {
+ return this.filterArgs;
+ }
+
+
+ /**
+ * Sets the filter arguments.
+ *
+ * @param o to set filter argumets
+ */
+ public void setFilterArgs(final Object[] o)
+ {
+ this.filterArgs = o;
+ }
+
+
+ /**
+ * Gets the return attributes.
+ *
+ * @return return attributes
+ */
+ public String[] getReturnAttrs()
+ {
+ return this.returnAttrs;
+ }
+
+
+ /**
+ * Sets the return attributes.
+ *
+ * @param s to set return attributes
+ */
+ public void setReturnAttrs(final String[] s)
+ {
+ this.returnAttrs = s;
+ }
+
+
+ /**
+ * Gets the match attributes.
+ *
+ * @return match attributes
+ */
+ public Attributes getMatchAttrs()
+ {
+ return this.matchAttrs;
+ }
+
+
+ /**
+ * Sets the match attributes.
+ *
+ * @param a to set match attributes
+ */
+ public void setMatchAttrs(final Attributes a)
+ {
+ this.matchAttrs = a;
+ }
+
+
+ /**
+ * This returns a string representation of this search criteria.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return
+ String.format(
+ "dn=%s,filter=%s,filterArgs=%s,returnAttrs=%s,matchAttrs=%s",
+ this.dn,
+ this.filter,
+ this.filterArgs,
+ this.returnAttrs,
+ this.matchAttrs);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/SearchResultHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/SearchResultHandler.java
new file mode 100644
index 0000000..4f82a37
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/SearchResultHandler.java
@@ -0,0 +1,43 @@
+/*
+ $Id: SearchResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import javax.naming.directory.SearchResult;
+
+/**
+ * SearchResultHandler provides post search processing of ldap search results.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public interface SearchResultHandler
+ extends ResultHandler<SearchResult, SearchResult>
+{
+
+
+ /**
+ * Gets the attribute handlers.
+ *
+ * @return <code>AttributeHandler[]</code>
+ */
+ AttributeHandler[] getAttributeHandler();
+
+
+ /**
+ * Sets the attribute handlers.
+ *
+ * @param ah <code>AttributeHandler[]</code>
+ */
+ void setAttributeHandler(final AttributeHandler[] ah);
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/handler/TlsConnectionHandler.java b/src/main/java/edu/vt/middleware/ldap/handler/TlsConnectionHandler.java
new file mode 100644
index 0000000..f18fee4
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/handler/TlsConnectionHandler.java
@@ -0,0 +1,266 @@
+/*
+ $Id: TlsConnectionHandler.java 1616 2010-09-21 17:22:27Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1616 $
+ Updated: $Date: 2010-09-21 18:22:27 +0100 (Tue, 21 Sep 2010) $
+*/
+package edu.vt.middleware.ldap.handler;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import javax.naming.CommunicationException;
+import javax.naming.NamingException;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+import javax.naming.ldap.StartTlsRequest;
+import javax.naming.ldap.StartTlsResponse;
+import edu.vt.middleware.ldap.LdapConfig;
+import edu.vt.middleware.ldap.LdapConstants;
+
+/**
+ * <code>TlsConnectionHandler</code> creates a new <code>LdapContext</code>
+ * using environment properties obtained from {@link
+ * LdapConfig#getEnvironment()} and then invokes the startTLS extended operation
+ * on the context. <code>SSLSocketFactory</code> and <code>
+ * HostnameVerifier</code> properties are used from the <code>
+ * LdapContext</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1616 $
+ */
+public class TlsConnectionHandler extends DefaultConnectionHandler
+{
+
+ /** Start TLS response. */
+ private StartTlsResponse startTlsResponse;
+
+ /**
+ * Whether to call {@link StartTlsResponse#close()} when {@link #close()} is
+ * called.
+ */
+ private boolean stopTlsOnClose;
+
+
+ /** Default constructor. */
+ public TlsConnectionHandler() {}
+
+
+ /**
+ * Creates a new <code>TlsConnectionHandler</code> with the supplied ldap
+ * config.
+ *
+ * @param lc ldap config
+ */
+ public TlsConnectionHandler(final LdapConfig lc)
+ {
+ super(lc);
+ }
+
+
+ /**
+ * Copy constructor for <code>TlsConnectionHandler</code>.
+ *
+ * @param ch to copy properties from
+ */
+ public TlsConnectionHandler(final TlsConnectionHandler ch)
+ {
+ this.setLdapConfig(ch.getLdapConfig());
+ this.setConnectionStrategy(ch.getConnectionStrategy());
+ this.setConnectionRetryExceptions(ch.getConnectionRetryExceptions());
+ this.setConnectionCount(ch.getConnectionCount());
+ this.setStopTlsOnClose(ch.getStopTlsOnClose());
+ }
+
+
+ /**
+ * Returns whether to call {@link StartTlsResponse#close()} when {@link
+ * #close()} is called.
+ *
+ * @return stop TLS on close
+ */
+ public boolean getStopTlsOnClose()
+ {
+ return this.stopTlsOnClose;
+ }
+
+
+ /**
+ * Sets whether to call {@link StartTlsResponse#close()} when {@link #close()}
+ * is called.
+ *
+ * @param b stop TLS on close
+ */
+ public void setStopTlsOnClose(final boolean b)
+ {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting stopTlsOnClose: " + b);
+ }
+ this.stopTlsOnClose = b;
+ }
+
+
+ /**
+ * This returns the startTLS response created by a call to {@link
+ * #connect(String, Object)}.
+ *
+ * @return start tls response
+ */
+ public StartTlsResponse getStartTlsResponse()
+ {
+ return this.startTlsResponse;
+ }
+
+
+ /** {@inheritDoc} */
+ protected void connectInternal(
+ final String authtype,
+ final String dn,
+ final Object credential,
+ final Hashtable<String, Object> env)
+ throws NamingException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Bind with the following parameters:");
+ this.logger.debug(" authtype = " + authtype);
+ this.logger.debug(" dn = " + dn);
+ if (this.config.getLogCredentials()) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(" credential = " + credential);
+ }
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(" credential = <suppressed>");
+ }
+ }
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(" env = " + env);
+ }
+ }
+
+ env.put(LdapConstants.VERSION, LdapConstants.VERSION_THREE);
+ try {
+ this.context = new InitialLdapContext(env, null);
+ this.startTlsResponse = this.startTls(this.context);
+ // note that when using simple authentication (the default),
+ // if the credential is null the provider will automatically revert the
+ // authentication to none
+ this.context.addToEnvironment(LdapConstants.AUTHENTICATION, authtype);
+ if (dn != null) {
+ this.context.addToEnvironment(LdapConstants.PRINCIPAL, dn);
+ if (credential != null) {
+ this.context.addToEnvironment(LdapConstants.CREDENTIALS, credential);
+ }
+ }
+ this.context.reconnect(null);
+ } catch (NamingException e) {
+ if (this.context != null) {
+ try {
+ this.context.close();
+ } finally {
+ this.context = null;
+ }
+ }
+ throw e;
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void close()
+ throws NamingException
+ {
+ try {
+ if (this.stopTlsOnClose) {
+ this.stopTls(this.startTlsResponse);
+ }
+ } catch (NamingException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error stopping TLS", e);
+ }
+ } finally {
+ this.startTlsResponse = null;
+ super.close();
+ }
+ }
+
+
+ /**
+ * This will attempt to StartTLS with the supplied <code>LdapContext</code>.
+ *
+ * @param ctx <code>LdapContext</code>
+ *
+ * @return <code>StartTlsResponse</code>
+ *
+ * @throws NamingException if an error occurs while requesting an extended
+ * operation
+ */
+ public StartTlsResponse startTls(final LdapContext ctx)
+ throws NamingException
+ {
+ StartTlsResponse tls = null;
+ try {
+ tls = (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest());
+ if (this.config.useHostnameVerifier()) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "TLS hostnameVerifier = " + this.config.getHostnameVerifier());
+ }
+ tls.setHostnameVerifier(this.config.getHostnameVerifier());
+ }
+ if (this.config.useSslSocketFactory()) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "TLS sslSocketFactory = " + this.config.getSslSocketFactory());
+ }
+ tls.negotiate(this.config.getSslSocketFactory());
+ } else {
+ tls.negotiate();
+ }
+ } catch (IOException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Could not negotiate TLS connection", e);
+ }
+ throw new CommunicationException(e.getMessage());
+ }
+ return tls;
+ }
+
+
+ /**
+ * This will attempt to StopTLS with the supplied <code>
+ * StartTlsResponse</code>.
+ *
+ * @param tls <code>StartTlsResponse</code>
+ *
+ * @throws NamingException if an error occurs while closing the TLS
+ * connection
+ */
+ public void stopTls(final StartTlsResponse tls)
+ throws NamingException
+ {
+ if (tls != null) {
+ try {
+ tls.close();
+ } catch (IOException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Could not close TLS connection", e);
+ }
+ throw new CommunicationException(e.getMessage());
+ }
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public TlsConnectionHandler newInstance()
+ {
+ return new TlsConnectionHandler(this);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/jaas/AbstractLoginModule.java b/src/main/java/edu/vt/middleware/ldap/jaas/AbstractLoginModule.java
new file mode 100644
index 0000000..17f9c75
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/jaas/AbstractLoginModule.java
@@ -0,0 +1,505 @@
+/*
+ $Id: AbstractLoginModule.java 1878 2011-04-05 15:15:00Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1878 $
+ Updated: $Date: 2011-04-05 16:15:00 +0100 (Tue, 05 Apr 2011) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.LdapConfig;
+import edu.vt.middleware.ldap.auth.Authenticator;
+import edu.vt.middleware.ldap.auth.AuthenticatorConfig;
+import edu.vt.middleware.ldap.bean.LdapAttribute;
+import edu.vt.middleware.ldap.bean.LdapAttributes;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+import edu.vt.middleware.ldap.props.LdapProperties;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AbstractLoginModule</code> provides functionality common to ldap based
+ * login modules.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1878 $ $Date: 2011-04-05 16:15:00 +0100 (Tue, 05 Apr 2011) $
+ */
+public abstract class AbstractLoginModule implements LoginModule
+{
+
+ /** Constant for login name stored in shared state. */
+ public static final String LOGIN_NAME = "javax.security.auth.login.name";
+
+ /** Constant for entryDn stored in shared state. */
+ public static final String LOGIN_DN =
+ "edu.vt.middleware.ldap.jaas.login.entryDn";
+
+ /** Constant for login password stored in shared state. */
+ public static final String LOGIN_PASSWORD =
+ "javax.security.auth.login.password";
+
+ /** Regular expression for ldap properties to ignore. */
+ private static final String IGNORE_LDAP_REGEX =
+ "useFirstPass|tryFirstPass|storePass|" +
+ "setLdapPrincipal|setLdapDnPrincipal|setLdapCredential|" +
+ "defaultRole|principalGroupName|roleGroupName|" +
+ "userRoleAttribute|roleFilter|roleAttribute|noResultsIsError";
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Initialized subject. */
+ protected Subject subject;
+
+ /** Initialized callback handler. */
+ protected CallbackHandler callbackHandler;
+
+ /** Shared state from other login module. */
+ @SuppressWarnings("unchecked")
+ protected Map sharedState;
+
+ /** Whether credentials from the shared state should be used. */
+ protected boolean useFirstPass;
+
+ /**
+ * Whether credentials from the shared state should be used if they are
+ * available.
+ */
+ protected boolean tryFirstPass;
+
+ /** Whether credentials should be stored in the shared state map. */
+ protected boolean storePass;
+
+ /** Whether credentials should be removed from the shared state map. */
+ protected boolean clearPass;
+
+ /** Whether ldap principal data should be set. */
+ protected boolean setLdapPrincipal;
+
+ /** Whether ldap dn principal data should be set. */
+ protected boolean setLdapDnPrincipal;
+
+ /** Whether ldap credential data should be set. */
+ protected boolean setLdapCredential;
+
+ /** Default roles. */
+ protected List<LdapRole> defaultRole = new ArrayList<LdapRole>();
+
+ /** Name of group to add all principals to. */
+ protected String principalGroupName;
+
+ /** Name of group to add all roles to. */
+ protected String roleGroupName;
+
+ /** Whether login was successful. */
+ protected boolean loginSuccess;
+
+ /** Whether commit was successful. */
+ protected boolean commitSuccess;
+
+ /** Principals to add to the subject. */
+ protected Set<Principal> principals;
+
+ /** Credentials to add to the subject. */
+ protected Set<LdapCredential> credentials;
+
+ /** Roles to add to the subject. */
+ protected Set<Principal> roles;
+
+
+ /** {@inheritDoc} */
+ public void initialize(
+ final Subject subject,
+ final CallbackHandler callbackHandler,
+ final Map<String, ?> sharedState,
+ final Map<String, ?> options)
+ {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Begin initialize");
+ }
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+ this.sharedState = sharedState;
+
+ final Iterator<String> i = options.keySet().iterator();
+ while (i.hasNext()) {
+ final String key = i.next();
+ final String value = (String) options.get(key);
+ if (key.equalsIgnoreCase("useFirstPass")) {
+ this.useFirstPass = Boolean.valueOf(value);
+ } else if (key.equalsIgnoreCase("tryFirstPass")) {
+ this.tryFirstPass = Boolean.valueOf(value);
+ } else if (key.equalsIgnoreCase("storePass")) {
+ this.storePass = Boolean.valueOf(value);
+ } else if (key.equalsIgnoreCase("clearPass")) {
+ this.clearPass = Boolean.valueOf(value);
+ } else if (key.equalsIgnoreCase("setLdapPrincipal")) {
+ this.setLdapPrincipal = Boolean.valueOf(value);
+ } else if (key.equalsIgnoreCase("setLdapDnPrincipal")) {
+ this.setLdapDnPrincipal = Boolean.valueOf(value);
+ } else if (key.equalsIgnoreCase("setLdapCredential")) {
+ this.setLdapCredential = Boolean.valueOf(value);
+ } else if (key.equalsIgnoreCase("defaultRole")) {
+ for (String s : value.split(",")) {
+ this.defaultRole.add(new LdapRole(s.trim()));
+ }
+ } else if (key.equalsIgnoreCase("principalGroupName")) {
+ this.principalGroupName = value;
+ } else if (key.equalsIgnoreCase("roleGroupName")) {
+ this.roleGroupName = value;
+ }
+ }
+
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("useFirstPass = " + this.useFirstPass);
+ this.logger.debug("tryFirstPass = " + this.tryFirstPass);
+ this.logger.debug("storePass = " + this.storePass);
+ this.logger.debug("clearPass = " + this.clearPass);
+ this.logger.debug("setLdapPrincipal = " + this.setLdapPrincipal);
+ this.logger.debug("setLdapDnPrincipal = " + this.setLdapDnPrincipal);
+ this.logger.debug("setLdapCredential = " + this.setLdapCredential);
+ this.logger.debug("defaultRole = " + this.defaultRole);
+ this.logger.debug("principalGroupName = " + this.principalGroupName);
+ this.logger.debug("roleGroupName = " + this.roleGroupName);
+ }
+
+ this.principals = new TreeSet<Principal>();
+ this.credentials = new HashSet<LdapCredential>();
+ this.roles = new TreeSet<Principal>();
+ }
+
+
+ /** {@inheritDoc} */
+ public abstract boolean login()
+ throws LoginException;
+
+
+ /** {@inheritDoc} */
+ public boolean commit()
+ throws LoginException
+ {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Begin commit");
+ }
+ if (!this.loginSuccess) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Login failed");
+ }
+ return false;
+ }
+
+ if (this.subject.isReadOnly()) {
+ this.clearState();
+ throw new LoginException("Subject is read-only.");
+ }
+ this.subject.getPrincipals().addAll(this.principals);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "Committed the following principals: " + this.principals);
+ }
+ this.subject.getPrivateCredentials().addAll(this.credentials);
+ this.subject.getPrincipals().addAll(this.roles);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Committed the following roles: " + this.roles);
+ }
+ if (this.principalGroupName != null) {
+ final LdapGroup group = new LdapGroup(this.principalGroupName);
+ for (Principal principal : this.principals) {
+ group.addMember(principal);
+ }
+ subject.getPrincipals().add(group);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "Committed the following principal group: " + group);
+ }
+ }
+ if (this.roleGroupName != null) {
+ final LdapGroup group = new LdapGroup(this.roleGroupName);
+ for (Principal role : this.roles) {
+ group.addMember(role);
+ }
+ subject.getPrincipals().add(group);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Committed the following role group: " + group);
+ }
+ }
+
+ this.clearState();
+ this.commitSuccess = true;
+ return true;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean abort()
+ throws LoginException
+ {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Begin abort");
+ }
+ if (!this.loginSuccess) {
+ return false;
+ } else if (this.loginSuccess && !this.commitSuccess) {
+ this.loginSuccess = false;
+ this.clearState();
+ } else {
+ this.logout();
+ }
+ return true;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean logout()
+ throws LoginException
+ {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Begin logout");
+ }
+ if (this.subject.isReadOnly()) {
+ this.clearState();
+ throw new LoginException("Subject is read-only.");
+ }
+
+ final Iterator<LdapPrincipal> prinIter = this.subject.getPrincipals(
+ LdapPrincipal.class).iterator();
+ while (prinIter.hasNext()) {
+ this.subject.getPrincipals().remove(prinIter.next());
+ }
+
+ final Iterator<LdapDnPrincipal> dnPrinIter = this.subject.getPrincipals(
+ LdapDnPrincipal.class).iterator();
+ while (dnPrinIter.hasNext()) {
+ this.subject.getPrincipals().remove(dnPrinIter.next());
+ }
+
+ final Iterator<LdapRole> roleIter = this.subject.getPrincipals(
+ LdapRole.class).iterator();
+ while (roleIter.hasNext()) {
+ this.subject.getPrincipals().remove(roleIter.next());
+ }
+
+ final Iterator<LdapGroup> groupIter = this.subject.getPrincipals(
+ LdapGroup.class).iterator();
+ while (groupIter.hasNext()) {
+ this.subject.getPrincipals().remove(groupIter.next());
+ }
+
+ final Iterator<LdapCredential> credIter = this.subject
+ .getPrivateCredentials(LdapCredential.class).iterator();
+ while (credIter.hasNext()) {
+ this.subject.getPrivateCredentials().remove(credIter.next());
+ }
+
+ this.clearState();
+ this.loginSuccess = false;
+ this.commitSuccess = false;
+ return true;
+ }
+
+
+ /**
+ * This constructs a new <code>Ldap</code> with the supplied jaas options.
+ *
+ * @param options <code>Map</code>
+ *
+ * @return <code>Ldap</code>
+ */
+ public static Ldap createLdap(final Map<String, ?> options)
+ {
+ final LdapConfig ldapConfig = new LdapConfig();
+ final LdapProperties ldapProperties = new LdapProperties(ldapConfig);
+ final Iterator<String> i = options.keySet().iterator();
+ while (i.hasNext()) {
+ final String key = i.next();
+ final String value = (String) options.get(key);
+ if (!key.matches(IGNORE_LDAP_REGEX)) {
+ ldapProperties.setProperty(key, value);
+ }
+ }
+ ldapProperties.configure();
+ return new Ldap(ldapConfig);
+ }
+
+
+ /**
+ * This constructs a new <code>Authenticator</code> with the supplied jaas
+ * options.
+ *
+ * @param options <code>Map</code>
+ *
+ * @return <code>Authenticator</code>
+ */
+ public static Authenticator createAuthenticator(final Map<String, ?> options)
+ {
+ final AuthenticatorConfig authConfig = new AuthenticatorConfig();
+ final LdapProperties authProperties = new LdapProperties(authConfig);
+ final Iterator<String> i = options.keySet().iterator();
+ while (i.hasNext()) {
+ final String key = i.next();
+ final String value = (String) options.get(key);
+ if (!key.matches(IGNORE_LDAP_REGEX)) {
+ authProperties.setProperty(key, value);
+ }
+ }
+ authProperties.configure();
+ return new JaasAuthenticator(authConfig);
+ }
+
+
+ /**
+ * Removes any stateful principals, credentials, or roles stored by login.
+ * Also removes shared state name, dn, and password if clearPass is set.
+ */
+ protected void clearState()
+ {
+ this.principals.clear();
+ this.credentials.clear();
+ this.roles.clear();
+ if (this.clearPass) {
+ this.sharedState.remove(LOGIN_NAME);
+ this.sharedState.remove(LOGIN_PASSWORD);
+ this.sharedState.remove(LOGIN_DN);
+ }
+ }
+
+
+ /**
+ * This attempts to retrieve credentials for the supplied name and password
+ * callbacks. If useFirstPass or tryFirstPass is set, then name and password
+ * data is retrieved from shared state. Otherwise a callback handler is used
+ * to get the data. Set useCallback to force a callback handler to be used.
+ *
+ * @param nameCb to set name for
+ * @param passCb to set password for
+ * @param useCallback whether to force a callback handler
+ *
+ * @throws LoginException if the callback handler fails
+ */
+ protected void getCredentials(
+ final NameCallback nameCb,
+ final PasswordCallback passCb,
+ final boolean useCallback)
+ throws LoginException
+ {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Begin getCredentials");
+ this.logger.trace(" useFistPass = " + this.useFirstPass);
+ this.logger.trace(" tryFistPass = " + this.tryFirstPass);
+ this.logger.trace(" useCallback = " + useCallback);
+ this.logger.trace(
+ " callbackhandler class = " +
+ this.callbackHandler.getClass().getName());
+ this.logger.trace(
+ " name callback class = " + nameCb.getClass().getName());
+ this.logger.trace(
+ " password callback class = " + passCb.getClass().getName());
+ }
+ try {
+ if ((this.useFirstPass || this.tryFirstPass) && !useCallback) {
+ nameCb.setName((String) this.sharedState.get(LOGIN_NAME));
+ passCb.setPassword((char[]) this.sharedState.get(LOGIN_PASSWORD));
+ } else if (this.callbackHandler != null) {
+ this.callbackHandler.handle(new Callback[] {nameCb, passCb});
+ } else {
+ throw new LoginException(
+ "No CallbackHandler available. " +
+ "Set useFirstPass, tryFirstPass, or provide a CallbackHandler");
+ }
+ } catch (IOException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error reading data from callback handler", e);
+ }
+ this.loginSuccess = false;
+ throw new LoginException(e.getMessage());
+ } catch (UnsupportedCallbackException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Unsupported callback", e);
+ }
+ this.loginSuccess = false;
+ throw new LoginException(e.getMessage());
+ }
+ }
+
+
+ /**
+ * This will store the supplied name, password, and entry dn in the stored
+ * state map. storePass must be set for this method to have any affect.
+ *
+ * @param nameCb to store
+ * @param passCb to store
+ * @param loginDn to store
+ */
+ @SuppressWarnings("unchecked")
+ protected void storeCredentials(
+ final NameCallback nameCb,
+ final PasswordCallback passCb,
+ final String loginDn)
+ {
+ if (this.storePass) {
+ if (nameCb != null && nameCb.getName() != null) {
+ this.sharedState.put(LOGIN_NAME, nameCb.getName());
+ }
+ if (passCb != null && passCb.getPassword() != null) {
+ this.sharedState.put(LOGIN_PASSWORD, passCb.getPassword());
+ }
+ if (loginDn != null) {
+ this.sharedState.put(LOGIN_DN, loginDn);
+ }
+ }
+ }
+
+
+ /**
+ * This parses the supplied attributes and returns them as a list of <code>
+ * LdapRole</code>s.
+ *
+ * @param attributes <code>Attributes</code>
+ *
+ * @return <code>List</code>
+ *
+ * @throws NamingException if the attributes cannot be parsed
+ */
+ protected List<LdapRole> attributesToRoles(final Attributes attributes)
+ throws NamingException
+ {
+ final List<LdapRole> roles = new ArrayList<LdapRole>();
+ if (attributes != null) {
+ final LdapAttributes ldapAttrs = LdapBeanProvider.getLdapBeanFactory()
+ .newLdapAttributes();
+ ldapAttrs.addAttributes(attributes);
+ for (LdapAttribute ldapAttr : ldapAttrs.getAttributes()) {
+ for (String attrValue : ldapAttr.getStringValues()) {
+ roles.add(new LdapRole(attrValue));
+ }
+ }
+ }
+ return roles;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/jaas/JaasAuthenticator.java b/src/main/java/edu/vt/middleware/ldap/jaas/JaasAuthenticator.java
new file mode 100644
index 0000000..b27072f
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/jaas/JaasAuthenticator.java
@@ -0,0 +1,93 @@
+/*
+ $Id: JaasAuthenticator.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import edu.vt.middleware.ldap.auth.Authenticator;
+import edu.vt.middleware.ldap.auth.AuthenticatorConfig;
+import edu.vt.middleware.ldap.auth.handler.AuthenticationResultHandler;
+import edu.vt.middleware.ldap.auth.handler.AuthorizationHandler;
+
+/**
+ * <code>JaasAuthenticator</code> is the default implementation for JAAS
+ * authentication.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class JaasAuthenticator extends Authenticator
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -7884185473690369247L;
+
+
+ /** Default constructor. */
+ public JaasAuthenticator() {}
+
+
+ /**
+ * This will create a new <code>JaasAuthenticator</code> with the supplied
+ * <code>AuthenticatorConfig</code>.
+ *
+ * @param authConfig <code>AuthenticatorConfig</code>
+ */
+ public JaasAuthenticator(final AuthenticatorConfig authConfig)
+ {
+ this.setAuthenticatorConfig(authConfig);
+ }
+
+
+ /** {@inheritDoc} */
+ public Attributes authenticate(
+ final String user,
+ final Object credential,
+ final String[] retAttrs)
+ throws NamingException
+ {
+ return super.authenticate(user, credential, retAttrs);
+ }
+
+
+ /** {@inheritDoc} */
+ public Attributes authenticate(
+ final String user,
+ final Object credential,
+ final String[] retAttrs,
+ final AuthenticationResultHandler[] authHandler,
+ final AuthorizationHandler[] authzHandler)
+ throws NamingException
+ {
+ if (retAttrs != null && retAttrs.length == 0) {
+ return
+ this.authenticateAndAuthorize(
+ this.getDn(user),
+ credential,
+ false,
+ retAttrs,
+ authHandler,
+ authzHandler);
+ } else {
+ return
+ this.authenticateAndAuthorize(
+ this.getDn(user),
+ credential,
+ true,
+ retAttrs,
+ authHandler,
+ authzHandler);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/jaas/LdapCredential.java b/src/main/java/edu/vt/middleware/ldap/jaas/LdapCredential.java
new file mode 100644
index 0000000..e5181ad
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/jaas/LdapCredential.java
@@ -0,0 +1,93 @@
+/*
+ $Id: LdapCredential.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.io.Serializable;
+
+/**
+ * <code>LdapCredential</code> provides a custom implementation for adding LDAP
+ * credentials to a <code>Subject</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class LdapCredential implements Serializable
+{
+
+ /** hash code seed. */
+ protected static final int HASH_CODE_SEED = 89;
+
+ /** serial version uid. */
+ private static final long serialVersionUID = 6571981350905290712L;
+
+ /** LDAP credential. */
+ private Object credential;
+
+
+ /**
+ * This creates a new <code>LdapCredential</code> with the supplied
+ * credential.
+ *
+ * @param credential <code>Object</code>
+ */
+ public LdapCredential(final Object credential)
+ {
+ this.credential = credential;
+ }
+
+
+ /**
+ * This returns the credential for this <code>LdapCredential</code>.
+ *
+ * @return <code>Object</code>
+ */
+ public Object getCredential()
+ {
+ return this.credential;
+ }
+
+
+ /**
+ * This returns the supplied Object is equal to this <code>
+ * LdapCredential</code>.
+ *
+ * @param o <code>Object</code>
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean equals(final Object o)
+ {
+ if (o == null) {
+ return false;
+ }
+ return
+ o == this ||
+ (this.getClass() == o.getClass() && o.hashCode() == this.hashCode());
+ }
+
+
+ /**
+ * This returns the hash code for this <code>LdapPrincipal</code>.
+ *
+ * @return <code>int</code>
+ */
+ public int hashCode()
+ {
+ int hc = HASH_CODE_SEED;
+ if (this.credential != null) {
+ hc += this.credential.hashCode();
+ }
+ return hc;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/jaas/LdapDnAuthorizationModule.java b/src/main/java/edu/vt/middleware/ldap/jaas/LdapDnAuthorizationModule.java
new file mode 100644
index 0000000..2815625
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/jaas/LdapDnAuthorizationModule.java
@@ -0,0 +1,157 @@
+/*
+ $Id: LdapDnAuthorizationModule.java 1878 2011-04-05 15:15:00Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1878 $
+ Updated: $Date: 2011-04-05 16:15:00 +0100 (Tue, 05 Apr 2011) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.security.Principal;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import javax.naming.NamingException;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import com.sun.security.auth.callback.TextCallbackHandler;
+import edu.vt.middleware.ldap.auth.Authenticator;
+
+/**
+ * <code>LdapDnAuthorizationModule</code> provides a JAAS authentication hook
+ * into LDAP DNs. No authentication is performed in this module. The LDAP entry
+ * dn can be stored and shared with other modules.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1878 $ $Date: 2011-04-05 16:15:00 +0100 (Tue, 05 Apr 2011) $
+ */
+public class LdapDnAuthorizationModule extends AbstractLoginModule
+ implements LoginModule
+{
+
+ /** Whether failing to find a DN should raise an exception. */
+ private boolean noResultsIsError;
+
+ /** Authenticator to use against the LDAP. */
+ private Authenticator auth;
+
+
+ /** {@inheritDoc} */
+ public void initialize(
+ final Subject subject,
+ final CallbackHandler callbackHandler,
+ final Map<String, ?> sharedState,
+ final Map<String, ?> options)
+ {
+ super.initialize(subject, callbackHandler, sharedState, options);
+
+ final Iterator<String> i = options.keySet().iterator();
+ while (i.hasNext()) {
+ final String key = i.next();
+ final String value = (String) options.get(key);
+ if (key.equalsIgnoreCase("noResultsIsError")) {
+ this.noResultsIsError = Boolean.valueOf(value);
+ }
+ }
+
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("noResultsIsError = " + this.noResultsIsError);
+ }
+
+ this.auth = createAuthenticator(options);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "Created authenticator: " + this.auth.getAuthenticatorConfig());
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean login()
+ throws LoginException
+ {
+ try {
+ final NameCallback nameCb = new NameCallback("Enter user: ");
+ final PasswordCallback passCb = new PasswordCallback(
+ "Enter user password: ",
+ false);
+ this.getCredentials(nameCb, passCb, false);
+
+ if (nameCb.getName() == null && this.tryFirstPass) {
+ this.getCredentials(nameCb, passCb, true);
+ }
+
+ final String loginName = nameCb.getName();
+ if (loginName != null && this.setLdapPrincipal) {
+ this.principals.add(new LdapPrincipal(loginName));
+ this.loginSuccess = true;
+ }
+
+ final String loginDn = this.auth.getDn(nameCb.getName());
+ if (loginDn == null && this.noResultsIsError) {
+ this.loginSuccess = false;
+ throw new LoginException("Could not find DN for " + nameCb.getName());
+ }
+ if (loginDn != null && this.setLdapDnPrincipal) {
+ this.principals.add(new LdapDnPrincipal(loginDn));
+ this.loginSuccess = true;
+ }
+ if (this.defaultRole != null && !this.defaultRole.isEmpty()) {
+ this.roles.addAll(this.defaultRole);
+ this.loginSuccess = true;
+ }
+ this.storeCredentials(nameCb, passCb, loginDn);
+ } catch (NamingException e) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Error occured attempting DN lookup", e);
+ }
+ this.loginSuccess = false;
+ throw new LoginException(e.getMessage());
+ } finally {
+ this.auth.close();
+ }
+ return true;
+ }
+
+
+ /**
+ * This provides command line access to a <code>LdapLoginModule</code>.
+ *
+ * @param args <code>String[]</code>
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(final String[] args)
+ throws Exception
+ {
+ String name = "vt-ldap-dn";
+ if (args.length > 0) {
+ name = args[0];
+ }
+
+ final LoginContext lc = new LoginContext(name, new TextCallbackHandler());
+ lc.login();
+ System.out.println("Authorization succeeded");
+
+ final Set<Principal> principals = lc.getSubject().getPrincipals();
+ System.out.println("Subject Principal(s): ");
+
+ final Iterator<Principal> i = principals.iterator();
+ while (i.hasNext()) {
+ final Principal p = i.next();
+ System.out.println(" " + p.getName());
+ }
+ lc.logout();
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/jaas/LdapDnPrincipal.java b/src/main/java/edu/vt/middleware/ldap/jaas/LdapDnPrincipal.java
new file mode 100644
index 0000000..d4e0168
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/jaas/LdapDnPrincipal.java
@@ -0,0 +1,138 @@
+/*
+ $Id: LdapDnPrincipal.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.io.Serializable;
+import java.security.Principal;
+import edu.vt.middleware.ldap.bean.LdapAttributes;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+
+/**
+ * <code>LdapPrincipal</code> provides a custom implementation for adding LDAP
+ * principals to a <code>Subject</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class LdapDnPrincipal
+ implements Principal, Serializable, Comparable<Principal>
+{
+
+ /** hash code seed. */
+ protected static final int HASH_CODE_SEED = 80;
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -4530972236127507368L;
+
+ /** LDAP user name. */
+ private String name;
+
+ /** User attributes. */
+ private LdapAttributes attributes = LdapBeanProvider.getLdapBeanFactory()
+ .newLdapAttributes();
+
+
+ /**
+ * This creates a new <code>LdapPrincipal</code> with the supplied name.
+ *
+ * @param name <code>String</code>
+ */
+ public LdapDnPrincipal(final String name)
+ {
+ this.name = name;
+ }
+
+
+ /**
+ * This returns the name for this <code>LdapPrincipal</code>.
+ *
+ * @return <code>String</code>
+ */
+ public String getName()
+ {
+ return this.name;
+ }
+
+
+ /**
+ * This returns the ldap attributes for this <code>LdapPrincipal</code>.
+ *
+ * @return <code>LdapAttributes</code>
+ */
+ public LdapAttributes getLdapAttributes()
+ {
+ return this.attributes;
+ }
+
+
+ /**
+ * This returns the supplied Object is equal to this <code>
+ * LdapPrincipal</code>.
+ *
+ * @param o <code>Object</code>
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean equals(final Object o)
+ {
+ if (o == null) {
+ return false;
+ }
+ return
+ o == this ||
+ (this.getClass() == o.getClass() && o.hashCode() == this.hashCode());
+ }
+
+
+ /**
+ * This returns the hash code for this <code>LdapPrincipal</code>.
+ *
+ * @return <code>int</code>
+ */
+ public int hashCode()
+ {
+ int hc = HASH_CODE_SEED;
+ if (this.name != null) {
+ hc += this.name.hashCode();
+ }
+ return hc;
+ }
+
+
+ /**
+ * This returns a String representation of this <code>LdapPrincipal</code>.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s%s", this.name, this.attributes);
+ }
+
+
+ /**
+ * This compares the supplied object for order. <code>LdapPrincipal</code> is
+ * always less than any other object. Otherwise principals are compared
+ * lexicographically on name.
+ *
+ * @param p <code>Principal</code>
+ *
+ * @return <code>int</code>
+ */
+ public int compareTo(final Principal p)
+ {
+ return this.name.compareTo(p.getName());
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/jaas/LdapGroup.java b/src/main/java/edu/vt/middleware/ldap/jaas/LdapGroup.java
new file mode 100644
index 0000000..d308e49
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/jaas/LdapGroup.java
@@ -0,0 +1,120 @@
+/*
+ $Id: LdapGroup.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <code>LdapGroup</code> provides a custom implementation for grouping
+ * principals.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class LdapGroup implements Group, Serializable
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -342760961669842632L;
+
+ /** LDAP role name. */
+ private String name;
+
+ /** Principal members. */
+ private Set<Principal> members = new HashSet<Principal>();
+
+
+ /**
+ * This creates a new <code>LdapGroup</code> with the supplied name.
+ *
+ * @param name <code>String</code>
+ */
+ public LdapGroup(final String name)
+ {
+ this.name = name;
+ }
+
+
+ /**
+ * This returns the name for this <code>LdapGroup</code>.
+ *
+ * @return <code>String</code>
+ */
+ public String getName()
+ {
+ return this.name;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean addMember(final Principal user)
+ {
+ return this.members.add(user);
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean removeMember(final Principal user)
+ {
+ return this.members.remove(user);
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean isMember(final Principal member)
+ {
+ for (Principal p : this.members) {
+ if (p.getName() != null && p.getName().equals(member.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /** {@inheritDoc} */
+ public Enumeration<? extends Principal> members()
+ {
+ return Collections.enumeration(this.members);
+ }
+
+
+ /**
+ * Returns an unmodifiable set of the members in this group.
+ *
+ * @return <code>Set</code> of member principals
+ */
+ public Set<Principal> getMembers()
+ {
+ return Collections.unmodifiableSet(this.members);
+ }
+
+
+ /**
+ * This returns a String representation of this <code>LdapGroup</code>.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s%s", this.name, this.members);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/jaas/LdapLoginModule.java b/src/main/java/edu/vt/middleware/ldap/jaas/LdapLoginModule.java
new file mode 100644
index 0000000..9762eb6
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/jaas/LdapLoginModule.java
@@ -0,0 +1,205 @@
+/*
+ $Id: LdapLoginModule.java 1878 2011-04-05 15:15:00Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1878 $
+ Updated: $Date: 2011-04-05 16:15:00 +0100 (Tue, 05 Apr 2011) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import javax.naming.AuthenticationException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import com.sun.security.auth.callback.TextCallbackHandler;
+import edu.vt.middleware.ldap.auth.Authenticator;
+
+/**
+ * <code>LdapLoginModule</code> provides a JAAS authentication hook into LDAP
+ * authentication.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1878 $ $Date: 2011-04-05 16:15:00 +0100 (Tue, 05 Apr 2011) $
+ */
+public class LdapLoginModule extends AbstractLoginModule implements LoginModule
+{
+
+ /** User attribute to add to role data. */
+ private String[] userRoleAttribute = new String[0];
+
+ /** Authenticator to use against the LDAP. */
+ private Authenticator auth;
+
+
+ /** {@inheritDoc} */
+ public void initialize(
+ final Subject subject,
+ final CallbackHandler callbackHandler,
+ final Map<String, ?> sharedState,
+ final Map<String, ?> options)
+ {
+ this.setLdapPrincipal = true;
+ this.setLdapCredential = true;
+
+ super.initialize(subject, callbackHandler, sharedState, options);
+
+ final Iterator<String> i = options.keySet().iterator();
+ while (i.hasNext()) {
+ final String key = i.next();
+ final String value = (String) options.get(key);
+ if (key.equalsIgnoreCase("userRoleAttribute")) {
+ if ("*".equals(value)) {
+ this.userRoleAttribute = null;
+ } else {
+ this.userRoleAttribute = value.split(",");
+ }
+ }
+ }
+
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "userRoleAttribute = " + Arrays.toString(this.userRoleAttribute));
+ }
+
+ this.auth = createAuthenticator(options);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "Created authenticator: " + this.auth.getAuthenticatorConfig());
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean login()
+ throws LoginException
+ {
+ try {
+ final NameCallback nameCb = new NameCallback("Enter user: ");
+ final PasswordCallback passCb = new PasswordCallback(
+ "Enter user password: ",
+ false);
+ this.getCredentials(nameCb, passCb, false);
+
+ AuthenticationException authEx = null;
+ Attributes attrs = null;
+ try {
+ attrs = this.auth.authenticate(
+ nameCb.getName(),
+ passCb.getPassword(),
+ this.userRoleAttribute);
+ this.roles.addAll(this.attributesToRoles(attrs));
+ if (this.defaultRole != null && !this.defaultRole.isEmpty()) {
+ this.roles.addAll(this.defaultRole);
+ }
+ this.loginSuccess = true;
+ } catch (AuthenticationException e) {
+ if (this.tryFirstPass) {
+ this.getCredentials(nameCb, passCb, true);
+ try {
+ attrs = this.auth.authenticate(
+ nameCb.getName(),
+ passCb.getPassword(),
+ this.userRoleAttribute);
+ this.roles.addAll(this.attributesToRoles(attrs));
+ if (this.defaultRole != null && !this.defaultRole.isEmpty()) {
+ this.roles.addAll(this.defaultRole);
+ }
+ this.loginSuccess = true;
+ } catch (AuthenticationException e2) {
+ authEx = e;
+ this.loginSuccess = false;
+ }
+ } else {
+ authEx = e;
+ this.loginSuccess = false;
+ }
+ }
+ if (!this.loginSuccess) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Authentication failed", authEx);
+ }
+ throw new LoginException(
+ authEx != null ? authEx.getMessage() : "Authentication failed");
+ } else {
+ if (this.setLdapPrincipal) {
+ final LdapPrincipal lp = new LdapPrincipal(nameCb.getName());
+ if (attrs != null) {
+ lp.getLdapAttributes().addAttributes(attrs);
+ }
+ this.principals.add(lp);
+ }
+
+ final String loginDn = this.auth.getDn(nameCb.getName());
+ if (loginDn != null && this.setLdapDnPrincipal) {
+ final LdapDnPrincipal lp = new LdapDnPrincipal(loginDn);
+ if (attrs != null) {
+ lp.getLdapAttributes().addAttributes(attrs);
+ }
+ this.principals.add(lp);
+ }
+ if (this.setLdapCredential) {
+ this.credentials.add(new LdapCredential(passCb.getPassword()));
+ }
+ this.storeCredentials(nameCb, passCb, loginDn);
+ }
+ } catch (NamingException e) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Error occured attempting authentication", e);
+ }
+ this.loginSuccess = false;
+ throw new LoginException(
+ e != null ? e.getMessage() : "Authentication Error");
+ } finally {
+ this.auth.close();
+ }
+ return true;
+ }
+
+
+ /**
+ * This provides command line access to a <code>LdapLoginModule</code>.
+ *
+ * @param args <code>String[]</code>
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(final String[] args)
+ throws Exception
+ {
+ String name = "vt-ldap";
+ if (args.length > 0) {
+ name = args[0];
+ }
+
+ final LoginContext lc = new LoginContext(name, new TextCallbackHandler());
+ lc.login();
+ System.out.println("Authentication/Authorization succeeded");
+
+ final Set<Principal> principals = lc.getSubject().getPrincipals();
+ System.out.println("Subject Principal(s): ");
+
+ final Iterator<Principal> i = principals.iterator();
+ while (i.hasNext()) {
+ final Principal p = i.next();
+ System.out.println(" " + p);
+ }
+ lc.logout();
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/jaas/LdapPrincipal.java b/src/main/java/edu/vt/middleware/ldap/jaas/LdapPrincipal.java
new file mode 100644
index 0000000..825b2bc
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/jaas/LdapPrincipal.java
@@ -0,0 +1,138 @@
+/*
+ $Id: LdapPrincipal.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.io.Serializable;
+import java.security.Principal;
+import edu.vt.middleware.ldap.bean.LdapAttributes;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+
+/**
+ * <code>LdapPrincipal</code> provides a custom implementation for adding LDAP
+ * principals to a <code>Subject</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class LdapPrincipal
+ implements Principal, Serializable, Comparable<Principal>
+{
+
+ /** hash code seed. */
+ protected static final int HASH_CODE_SEED = 79;
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -1043578648596801523L;
+
+ /** LDAP user name. */
+ private String name;
+
+ /** User attributes. */
+ private LdapAttributes attributes = LdapBeanProvider.getLdapBeanFactory()
+ .newLdapAttributes();
+
+
+ /**
+ * This creates a new <code>LdapPrincipal</code> with the supplied name.
+ *
+ * @param name <code>String</code>
+ */
+ public LdapPrincipal(final String name)
+ {
+ this.name = name;
+ }
+
+
+ /**
+ * This returns the name for this <code>LdapPrincipal</code>.
+ *
+ * @return <code>String</code>
+ */
+ public String getName()
+ {
+ return this.name;
+ }
+
+
+ /**
+ * This returns the ldap attributes for this <code>LdapPrincipal</code>.
+ *
+ * @return <code>LdapAttributes</code>
+ */
+ public LdapAttributes getLdapAttributes()
+ {
+ return this.attributes;
+ }
+
+
+ /**
+ * This returns the supplied Object is equal to this <code>
+ * LdapPrincipal</code>.
+ *
+ * @param o <code>Object</code>
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean equals(final Object o)
+ {
+ if (o == null) {
+ return false;
+ }
+ return
+ o == this ||
+ (this.getClass() == o.getClass() && o.hashCode() == this.hashCode());
+ }
+
+
+ /**
+ * This returns the hash code for this <code>LdapPrincipal</code>.
+ *
+ * @return <code>int</code>
+ */
+ public int hashCode()
+ {
+ int hc = HASH_CODE_SEED;
+ if (this.name != null) {
+ hc += this.name.hashCode();
+ }
+ return hc;
+ }
+
+
+ /**
+ * This returns a String representation of this <code>LdapPrincipal</code>.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s%s", this.name, this.attributes);
+ }
+
+
+ /**
+ * This compares the supplied object for order. <code>LdapPrincipal</code> is
+ * always less than any other object. Otherwise principals are compared
+ * lexicographically on name.
+ *
+ * @param p <code>Principal</code>
+ *
+ * @return <code>int</code>
+ */
+ public int compareTo(final Principal p)
+ {
+ return this.name.compareTo(p.getName());
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/jaas/LdapRole.java b/src/main/java/edu/vt/middleware/ldap/jaas/LdapRole.java
new file mode 100644
index 0000000..e90f943
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/jaas/LdapRole.java
@@ -0,0 +1,119 @@
+/*
+ $Id: LdapRole.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+/**
+ * <code>LdapRole</code> provides a custom implementation for adding LDAP
+ * principals to a <code>Subject</code> that represent roles.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class LdapRole implements Principal, Serializable, Comparable<Principal>
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = 1427032827399935399L;
+
+ /** LDAP role name. */
+ private String name;
+
+
+ /**
+ * This creates a new <code>LdapRole</code> with the supplied name.
+ *
+ * @param name <code>String</code>
+ */
+ public LdapRole(final String name)
+ {
+ this.name = name;
+ }
+
+
+ /**
+ * This returns the name for this <code>LdapRole</code>.
+ *
+ * @return <code>String</code>
+ */
+ public String getName()
+ {
+ return this.name;
+ }
+
+
+ /**
+ * This returns the supplied Object is equal to this <code>LdapRole</code>.
+ *
+ * @param o <code>Object</code>
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean equals(final Object o)
+ {
+ boolean b = false;
+ if (o != null) {
+ if (this != o) {
+ if (o instanceof LdapRole) {
+ if (((LdapRole) o).getName().equals(this.name)) {
+ b = true;
+ }
+ }
+ } else {
+ b = true;
+ }
+ }
+ return b;
+ }
+
+
+ /**
+ * This returns the hash code for this <code>LdapRole</code>.
+ *
+ * @return <code>int</code>
+ */
+ public int hashCode()
+ {
+ return this.name.hashCode();
+ }
+
+
+ /**
+ * This returns a String representation of this <code>LdapRole</code>.
+ *
+ * @return <code>String</code>
+ */
+ @Override
+ public String toString()
+ {
+ return this.name;
+ }
+
+
+ /**
+ * This compares the supplied object for order. <code>LdapRole</code> is
+ * always greater than any other object. Otherwise principals are compared
+ * lexicographically on name.
+ *
+ * @param p <code>Principal</code>
+ *
+ * @return <code>int</code>
+ */
+ public int compareTo(final Principal p)
+ {
+ return this.name.compareTo(p.getName());
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/jaas/LdapRoleAuthorizationModule.java b/src/main/java/edu/vt/middleware/ldap/jaas/LdapRoleAuthorizationModule.java
new file mode 100644
index 0000000..c34f5d2
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/jaas/LdapRoleAuthorizationModule.java
@@ -0,0 +1,191 @@
+/*
+ $Id: LdapRoleAuthorizationModule.java 1878 2011-04-05 15:15:00Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1878 $
+ Updated: $Date: 2011-04-05 16:15:00 +0100 (Tue, 05 Apr 2011) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import com.sun.security.auth.callback.TextCallbackHandler;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.SearchFilter;
+
+/**
+ * <code>LdapRoleAuthorizationModule</code> provides a JAAS authentication hook
+ * into LDAP roles. No authentication is performed in this module. Role data is
+ * set for the login name in the shared state or for the name returned by the
+ * CallbackHandler.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1878 $ $Date: 2011-04-05 16:15:00 +0100 (Tue, 05 Apr 2011) $
+ */
+public class LdapRoleAuthorizationModule extends AbstractLoginModule
+ implements LoginModule
+{
+
+ /** Ldap filter for role searches. */
+ private String roleFilter;
+
+ /** Role attribute to add to role data. */
+ private String[] roleAttribute = new String[0];
+
+ /** Whether failing to find any roles should raise an exception. */
+ private boolean noResultsIsError;
+
+ /** Ldap to use for searching roles against the LDAP. */
+ private Ldap ldap;
+
+
+ /** {@inheritDoc} */
+ public void initialize(
+ final Subject subject,
+ final CallbackHandler callbackHandler,
+ final Map<String, ?> sharedState,
+ final Map<String, ?> options)
+ {
+ super.initialize(subject, callbackHandler, sharedState, options);
+
+ final Iterator<String> i = options.keySet().iterator();
+ while (i.hasNext()) {
+ final String key = i.next();
+ final String value = (String) options.get(key);
+ if (key.equalsIgnoreCase("roleFilter")) {
+ this.roleFilter = value;
+ } else if (key.equalsIgnoreCase("roleAttribute")) {
+ if ("*".equals(value)) {
+ this.roleAttribute = null;
+ } else {
+ this.roleAttribute = value.split(",");
+ }
+ } else if (key.equalsIgnoreCase("noResultsIsError")) {
+ this.noResultsIsError = Boolean.valueOf(value);
+ }
+ }
+
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("roleFilter = " + this.roleFilter);
+ this.logger.debug(
+ "roleAttribute = " + Arrays.toString(this.roleAttribute));
+ this.logger.debug("noResultsIsError = " + this.noResultsIsError);
+ }
+
+ this.ldap = createLdap(options);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Created ldap: " + this.ldap.getLdapConfig());
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean login()
+ throws LoginException
+ {
+ try {
+ final NameCallback nameCb = new NameCallback("Enter user: ");
+ final PasswordCallback passCb = new PasswordCallback(
+ "Enter user password: ",
+ false);
+ this.getCredentials(nameCb, passCb, false);
+
+ if (nameCb.getName() == null && this.tryFirstPass) {
+ this.getCredentials(nameCb, passCb, true);
+ }
+
+ final String loginName = nameCb.getName();
+ if (loginName != null && this.setLdapPrincipal) {
+ this.principals.add(new LdapPrincipal(loginName));
+ this.loginSuccess = true;
+ }
+
+ final String loginDn = (String) this.sharedState.get(LOGIN_DN);
+ if (loginDn != null && this.setLdapDnPrincipal) {
+ this.principals.add(new LdapDnPrincipal(loginDn));
+ this.loginSuccess = true;
+ }
+
+ if (this.roleFilter != null) {
+ final Object[] filterArgs = new Object[] {loginDn, loginName, };
+ final Iterator<SearchResult> results = this.ldap.search(
+ new SearchFilter(this.roleFilter, filterArgs),
+ this.roleAttribute);
+ if (!results.hasNext() && this.noResultsIsError) {
+ this.loginSuccess = false;
+ throw new LoginException(
+ "Could not find roles using " + this.roleFilter);
+ }
+ while (results.hasNext()) {
+ final SearchResult sr = results.next();
+ this.roles.addAll(this.attributesToRoles(sr.getAttributes()));
+ }
+ }
+ if (this.defaultRole != null && !this.defaultRole.isEmpty()) {
+ this.roles.addAll(this.defaultRole);
+ }
+ if (!this.roles.isEmpty()) {
+ this.loginSuccess = true;
+ }
+ this.storeCredentials(nameCb, passCb, null);
+ } catch (NamingException e) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Error occured attempting role lookup", e);
+ }
+ this.loginSuccess = false;
+ throw new LoginException(e.getMessage());
+ } finally {
+ this.ldap.close();
+ }
+ return true;
+ }
+
+
+ /**
+ * This provides command line access to a <code>LdapRoleLoginModule</code>.
+ *
+ * @param args <code>String[]</code>
+ *
+ * @throws Exception if an error occurs
+ */
+ public static void main(final String[] args)
+ throws Exception
+ {
+ String name = "vt-ldap-role";
+ if (args.length > 0) {
+ name = args[0];
+ }
+
+ final LoginContext lc = new LoginContext(name, new TextCallbackHandler());
+ lc.login();
+ System.out.println("Authorization succeeded");
+
+ final Set<Principal> principals = lc.getSubject().getPrincipals();
+ System.out.println("Subject Principal(s): ");
+
+ final Iterator<Principal> i = principals.iterator();
+ while (i.hasNext()) {
+ final Principal p = i.next();
+ System.out.println(" " + p.getName());
+ }
+ lc.logout();
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ldif/Ldif.java b/src/main/java/edu/vt/middleware/ldap/ldif/Ldif.java
new file mode 100644
index 0000000..2998f07
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ldif/Ldif.java
@@ -0,0 +1,398 @@
+/*
+ $Id: Ldif.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.ldif;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.Writer;
+import java.net.URL;
+import java.util.Iterator;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.LdapUtil;
+import edu.vt.middleware.ldap.bean.LdapAttribute;
+import edu.vt.middleware.ldap.bean.LdapBeanFactory;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>Ldif</code> contains functions for converting LDAP search result sets
+ * into LDIF.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class Ldif implements Serializable
+{
+
+ /** ASCII decimal value of nul. */
+ public static final int NUL_CHAR = 0;
+
+ /** ASCII decimal value of line feed. */
+ public static final int LF_CHAR = 10;
+
+ /** ASCII decimal value of carriage return. */
+ public static final int CR_CHAR = 13;
+
+ /** ASCII decimal value of space. */
+ public static final int SP_CHAR = 32;
+
+ /** ASCII decimal value of colon. */
+ public static final int COLON_CHAR = 58;
+
+ /** ASCII decimal value of left arrow. */
+ public static final int LA_CHAR = 60;
+
+ /** ASCII decimal value of highest character. */
+ public static final int MAX_ASCII_CHAR = 127;
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -3763879179455001975L;
+
+ /** Line separator. */
+ private static final String LINE_SEPARATOR = System.getProperty(
+ "line.separator");
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Ldap bean factory. */
+ protected LdapBeanFactory beanFactory = LdapBeanProvider.getLdapBeanFactory();
+
+
+ /**
+ * Returns the factory for creating ldap beans.
+ *
+ * @return <code>LdapBeanFactory</code>
+ */
+ public LdapBeanFactory getLdapBeanFactory()
+ {
+ return this.beanFactory;
+ }
+
+
+ /**
+ * Sets the factory for creating ldap beans.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public void setLdapBeanFactory(final LdapBeanFactory lbf)
+ {
+ if (lbf != null) {
+ this.beanFactory = lbf;
+ }
+ }
+
+
+ /**
+ * This will take the results of a prior LDAP query and convert it to LDIF.
+ *
+ * @param results <code>Iterator</code> of LDAP search results
+ *
+ * @return <code>String</code>
+ */
+ public String createLdif(final Iterator<SearchResult> results)
+ {
+ String ldif = "";
+ try {
+ final LdapResult lr = this.beanFactory.newLdapResult();
+ lr.addEntries(results);
+ ldif = this.createLdif(lr);
+ } catch (NamingException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error creating String from SearchResults", e);
+ }
+ }
+ return ldif;
+ }
+
+
+ /**
+ * This will take the results of a prior LDAP query and convert it to LDIF.
+ *
+ * @param result <code>LdapResult</code>
+ *
+ * @return <code>String</code>
+ */
+ public String createLdif(final LdapResult result)
+ {
+ // build string from results
+ final StringBuffer ldif = new StringBuffer();
+ if (result != null) {
+ for (LdapEntry le : result.getEntries()) {
+ ldif.append(createLdifEntry(le));
+ }
+ }
+
+ return ldif.toString();
+ }
+
+
+ /**
+ * This will take an LDAP entry and convert it to LDIF.
+ *
+ * @param ldapEntry <code>LdapEntry</code> to convert
+ *
+ * @return <code>String</code>
+ */
+ protected String createLdifEntry(final LdapEntry ldapEntry)
+ {
+ final StringBuffer entry = new StringBuffer();
+ if (ldapEntry != null) {
+
+ final String dn = ldapEntry.getDn();
+ if (dn != null) {
+ if (encodeData(dn)) {
+ final String encodedDn = LdapUtil.base64Encode(dn);
+ if (encodedDn != null) {
+ entry.append("dn:: ").append(dn).append(LINE_SEPARATOR);
+ }
+ } else {
+ entry.append("dn: ").append(dn).append(LINE_SEPARATOR);
+ }
+ }
+
+ for (LdapAttribute attr : ldapEntry.getLdapAttributes().getAttributes()) {
+ final String attrName = attr.getName();
+ for (Object attrValue : attr.getValues()) {
+ if (encodeData(attrValue)) {
+ String encodedAttrValue = null;
+ if (attrValue instanceof String) {
+ encodedAttrValue = LdapUtil.base64Encode((String) attrValue);
+ } else if (attrValue instanceof byte[]) {
+ encodedAttrValue = LdapUtil.base64Encode((byte[]) attrValue);
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn(
+ "Could not cast attribute value as a byte[]" +
+ " or a String");
+ }
+ }
+ if (encodedAttrValue != null) {
+ entry.append(attrName).append(":: ").append(encodedAttrValue)
+ .append(LINE_SEPARATOR);
+ }
+ } else {
+ entry.append(attrName).append(": ").append(attrValue).append(
+ LINE_SEPARATOR);
+ }
+ }
+ }
+ }
+
+ if (entry.length() > 0) {
+ entry.append(LINE_SEPARATOR);
+ }
+ return entry.toString();
+ }
+
+
+ /**
+ * This determines whether the supplied data should be base64 encoded. See
+ * http://www.faqs.org/rfcs/rfc2849.html for more details.
+ *
+ * @param data <code>Object</code> to inspect
+ *
+ * @return <code>boolean</code>
+ */
+ private boolean encodeData(final Object data)
+ {
+ boolean encode = false;
+ if (data instanceof String) {
+ final String stringData = (String) data;
+ final char[] dataCharArray = stringData.toCharArray();
+ for (int i = 0; i < dataCharArray.length; i++) {
+ final int charInt = (int) dataCharArray[i];
+ // check for NUL
+ if (charInt == NUL_CHAR) {
+ encode = true;
+ // check for LF
+ } else if (charInt == LF_CHAR) {
+ encode = true;
+ // check for CR
+ } else if (charInt == CR_CHAR) {
+ encode = true;
+ // check for SP at beginning or end of string
+ } else if (
+ charInt == SP_CHAR &&
+ (i == 0 || i == dataCharArray.length - 1)) {
+ encode = true;
+ // check for colon(:) at beginning of string
+ } else if (charInt == COLON_CHAR && i == 0) {
+ encode = true;
+ // check for left arrow(<) at beginning of string
+ } else if (charInt == LA_CHAR && i == 0) {
+ encode = true;
+ // check for any character above 127
+ } else if (charInt > MAX_ASCII_CHAR) {
+ encode = true;
+ }
+ }
+ } else {
+ encode = true;
+ }
+ return encode;
+ }
+
+
+ /**
+ * This will write the supplied LDAP search results to the supplied writer in
+ * LDIF form.
+ *
+ * @param results <code>Iterator</code> of LDAP search results
+ * @param writer <code>Writer</code> to write to
+ *
+ * @throws IOException if an error occurs while writing to the output stream
+ */
+ public void outputLdif(
+ final Iterator<SearchResult> results,
+ final Writer writer)
+ throws IOException
+ {
+ writer.write(createLdif(results));
+ writer.flush();
+ }
+
+
+ /**
+ * This will write the supplied LDAP search results to the supplied writer in
+ * LDIF form.
+ *
+ * @param result <code>LdapResult</code>
+ * @param writer <code>Writer</code> to write to
+ *
+ * @throws IOException if an error occurs while writing to the output stream
+ */
+ public void outputLdif(final LdapResult result, final Writer writer)
+ throws IOException
+ {
+ writer.write(createLdif(result));
+ writer.flush();
+ }
+
+
+ /**
+ * This will take a Reader containing an LDIF and convert it to an Iterator of
+ * LDAP search results. Provides a loose implementation of RFC 2849. Should
+ * not be used to validate LDIF format as it does not enforce strictness.
+ *
+ * @param reader <code>Reader</code> containing LDIF content
+ *
+ * @return <code>Iterator</code> - of LDAP search results
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ public Iterator<SearchResult> importLdif(final Reader reader)
+ throws IOException
+ {
+ return this.importLdifToLdapResult(reader).toSearchResults().iterator();
+ }
+
+
+ /**
+ * This will take a Reader containing an LDIF and convert it to an <code>
+ * LdapResult</code>. Provides a loose implementation of RFC 2849. Should not
+ * be used to validate LDIF format as it does not enforce strictness.
+ *
+ * @param reader <code>Reader</code> containing LDIF content
+ *
+ * @return <code>LdapResult</code> - LDAP search results
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ public LdapResult importLdifToLdapResult(final Reader reader)
+ throws IOException
+ {
+ final LdapResult ldapResult = this.beanFactory.newLdapResult();
+ final BufferedReader br = new BufferedReader(reader);
+ String line = null;
+ int lineCount = 0;
+ LdapEntry ldapEntry = null;
+ StringBuffer lineValue = new StringBuffer();
+
+ while ((line = br.readLine()) != null) {
+ lineCount++;
+ if (line.startsWith("dn:")) {
+ lineValue.append(line);
+ ldapEntry = this.beanFactory.newLdapEntry();
+ break;
+ }
+ }
+
+ boolean read = true;
+ while (read) {
+ line = br.readLine();
+ if (line == null) {
+ read = false;
+ line = "";
+ }
+ if (!line.startsWith("#")) {
+ if (line.startsWith("dn:")) {
+ ldapResult.addEntry(ldapEntry);
+ ldapEntry = this.beanFactory.newLdapEntry();
+ }
+ if (line.startsWith(" ")) {
+ lineValue.append(line.substring(1));
+ } else {
+ final String s = lineValue.toString();
+ if (s.indexOf(":") != -1) {
+ boolean isBinary = false;
+ boolean isUrl = false;
+ final String[] parts = s.split(":", 2);
+ final String attrName = parts[0];
+ String attrValue = parts[1];
+ if (attrValue.startsWith(":")) {
+ isBinary = true;
+ attrValue = attrValue.substring(1);
+ } else if (attrValue.startsWith("<")) {
+ isUrl = true;
+ attrValue = attrValue.substring(1);
+ }
+ if (attrValue.startsWith(" ")) {
+ attrValue = attrValue.substring(1);
+ }
+ if ("dn".equals(attrName)) {
+ ldapEntry.setDn(attrValue);
+ } else {
+ LdapAttribute ldapAttr = ldapEntry.getLdapAttributes()
+ .getAttribute(attrName);
+ if (ldapAttr == null) {
+ ldapAttr = this.beanFactory.newLdapAttribute();
+ ldapAttr.setName(attrName);
+ ldapEntry.getLdapAttributes().addAttribute(ldapAttr);
+ }
+ if (isBinary) {
+ ldapAttr.getValues().add(LdapUtil.base64Decode(attrValue));
+ } else if (isUrl) {
+ ldapAttr.getValues().add(LdapUtil.readURL(new URL(attrValue)));
+ } else {
+ ldapAttr.getValues().add(attrValue);
+ }
+ }
+ }
+ lineValue = new StringBuffer(line);
+ }
+ }
+ }
+ if (ldapEntry != null) {
+ ldapResult.addEntry(ldapEntry);
+ }
+ return ldapResult;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ldif/LdifResultConverter.java b/src/main/java/edu/vt/middleware/ldap/ldif/LdifResultConverter.java
new file mode 100644
index 0000000..da6d522
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ldif/LdifResultConverter.java
@@ -0,0 +1,116 @@
+/*
+ $Id: LdifResultConverter.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.ldif;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.bean.LdapBeanFactory;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>LdifResultConverter</code> provides utility methods for converting
+ * <code>LdapResult</code> to and from LDIF in string format.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class LdifResultConverter
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /** Ldap bean factory. */
+ protected LdapBeanFactory beanFactory = LdapBeanProvider.getLdapBeanFactory();
+
+ /** Class for outputting LDIF. */
+ private Ldif ldif = new Ldif();
+
+
+ /**
+ * Returns the factory for creating ldap beans.
+ *
+ * @return <code>LdapBeanFactory</code>
+ */
+ public LdapBeanFactory getLdapBeanFactory()
+ {
+ return this.beanFactory;
+ }
+
+
+ /**
+ * Sets the factory for creating ldap beans.
+ *
+ * @param lbf <code>LdapBeanFactory</code>
+ */
+ public void setLdapBeanFactory(final LdapBeanFactory lbf)
+ {
+ if (lbf != null) {
+ this.beanFactory = lbf;
+ this.ldif.setLdapBeanFactory(lbf);
+ }
+ }
+
+
+ /**
+ * This returns this <code>LdifResult</code> as LDIF.
+ *
+ * @param result <code>LdapResult</code> to convert
+ *
+ * @return <code>String</code>
+ */
+ public String toLdif(final LdapResult result)
+ {
+ final StringWriter writer = new StringWriter();
+ try {
+ this.ldif.outputLdif(result.toSearchResults().iterator(), writer);
+ } catch (IOException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("Could not write ldif to StringWriter", e);
+ }
+ }
+ return writer.toString();
+ }
+
+
+ /**
+ * This reads any entries in the supplied LDIF into this <code>
+ * LdifResult</code>.
+ *
+ * @param ldif <code>String</code> to read
+ *
+ * @return <code>LdapResult</code>
+ */
+ public LdapResult fromLdif(final String ldif)
+ {
+ final LdapResult result = this.beanFactory.newLdapResult();
+ try {
+ result.addEntries(this.ldif.importLdif(new StringReader(ldif)));
+ } catch (IOException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("Could not read ldif from StringReader", e);
+ }
+ } catch (NamingException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Unexpected naming exception occurred", e);
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ldif/LdifSearch.java b/src/main/java/edu/vt/middleware/ldap/ldif/LdifSearch.java
new file mode 100644
index 0000000..5732833
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ldif/LdifSearch.java
@@ -0,0 +1,69 @@
+/*
+ $Id: LdifSearch.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.ldif;
+
+import java.io.IOException;
+import java.io.Writer;
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.LdapSearch;
+import edu.vt.middleware.ldap.pool.LdapPool;
+
+/**
+ * <code>LdifSearch</code> queries an LDAP and returns the result as an LDIF.
+ * Each instance of <code>LdifSearch</code> maintains it's own pool of LDAP
+ * connections.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class LdifSearch extends LdapSearch
+{
+
+ /** Ldif object. */
+ private Ldif ldif = new Ldif();
+
+
+ /**
+ * This creates a new <code>LdifSearch</code> with the supplied pool.
+ *
+ * @param pool <code>LdapPool</code>
+ */
+ public LdifSearch(final LdapPool<Ldap> pool)
+ {
+ super(pool);
+ }
+
+
+ /**
+ * This will perform an LDAP search with the supplied query and return
+ * attributes. The results will be written to the supplied <code>
+ * Writer</code>.
+ *
+ * @param query <code>String</code> to search for
+ * @param attrs <code>String[]</code> to return
+ * @param writer <code>Writer</code> to write to
+ *
+ * @throws NamingException if an error occurs while searching
+ * @throws IOException if an error occurs while writing search results
+ */
+ public void search(
+ final String query,
+ final String[] attrs,
+ final Writer writer)
+ throws NamingException, IOException
+ {
+ this.ldif.outputLdif(this.search(query, attrs), writer);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/AbstractLdapFactory.java b/src/main/java/edu/vt/middleware/ldap/pool/AbstractLdapFactory.java
new file mode 100644
index 0000000..b24aade
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/AbstractLdapFactory.java
@@ -0,0 +1,175 @@
+/*
+ $Id: AbstractLdapFactory.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import edu.vt.middleware.ldap.BaseLdap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AbstractLdapFactory</code> provides a basic implementation of an ldap
+ * factory.
+ *
+ * @param <T> type of ldap object
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public abstract class AbstractLdapFactory<T extends BaseLdap>
+ implements LdapFactory<T>
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** For activating ldap objects. */
+ protected LdapActivator<T> activator;
+
+ /** For passivating ldap objects. */
+ protected LdapPassivator<T> passivator;
+
+ /** For validating ldap objects. */
+ protected LdapValidator<T> validator;
+
+
+ /**
+ * Sets the ldap activator for this factory.
+ *
+ * @param la ldap activator
+ */
+ public void setLdapActivator(final LdapActivator<T> la)
+ {
+ this.activator = la;
+ }
+
+
+ /**
+ * Returns the ldap activator for this factory.
+ *
+ * @return ldap activator
+ */
+ public LdapActivator<T> getLdapActivator()
+ {
+ return this.activator;
+ }
+
+
+ /**
+ * Sets the ldap passivator for this factory.
+ *
+ * @param lp ldap passivator
+ */
+ public void setLdapPassivator(final LdapPassivator<T> lp)
+ {
+ this.passivator = lp;
+ }
+
+
+ /**
+ * Returns the ldap passivator for this factory.
+ *
+ * @return ldap passivator
+ */
+ public LdapPassivator<T> getLdapPassivator()
+ {
+ return this.passivator;
+ }
+
+
+ /**
+ * Sets the ldap validator for this factory.
+ *
+ * @param lv ldap validator
+ */
+ public void setLdapValidator(final LdapValidator<T> lv)
+ {
+ this.validator = lv;
+ }
+
+
+ /**
+ * Returns the ldap validator for this factory.
+ *
+ * @return ldap validator
+ */
+ public LdapValidator<T> getLdapValidator()
+ {
+ return this.validator;
+ }
+
+
+ /** {@inheritDoc} */
+ public abstract T create();
+
+
+ /** {@inheritDoc} */
+ public abstract void destroy(final T t);
+
+
+ /** {@inheritDoc} */
+ public boolean activate(final T t)
+ {
+ boolean success = false;
+ if (this.activator == null) {
+ success = true;
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("no activator configured");
+ }
+ } else {
+ success = this.activator.activate(t);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("activation for " + t + " = " + success);
+ }
+ }
+ return success;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean passivate(final T t)
+ {
+ boolean success = false;
+ if (this.passivator == null) {
+ success = true;
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("no passivator configured");
+ }
+ } else {
+ success = this.passivator.passivate(t);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("passivation for " + t + " = " + success);
+ }
+ }
+ return success;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean validate(final T t)
+ {
+ boolean success = false;
+ if (this.validator == null) {
+ success = true;
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("validate called, but no validator configured");
+ }
+ } else {
+ success = this.validator.validate(t);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("validation for " + t + " = " + success);
+ }
+ }
+ return success;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/AbstractLdapPool.java b/src/main/java/edu/vt/middleware/ldap/pool/AbstractLdapPool.java
new file mode 100644
index 0000000..61a422c
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/AbstractLdapPool.java
@@ -0,0 +1,664 @@
+/*
+ $Id: AbstractLdapPool.java 2241 2012-02-07 20:08:51Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2241 $
+ Updated: $Date: 2012-02-07 20:08:51 +0000 (Tue, 07 Feb 2012) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.Timer;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+import edu.vt.middleware.ldap.BaseLdap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AbstractLdapPool</code> contains the basic implementation for pooling
+ * ldap objects. The main design objective for the supplied pooling
+ * implementations is to provide a pool that does not block on object creation
+ * or destruction. This is what accounts for the multiple locks available on
+ * this class. The pool is backed by two queues, one for available objects and
+ * one for active objects. Objects that are available for {@link #checkOut()}
+ * exist in the available queue. Objects that are actively in use exist in the
+ * active queue. Note that depending on the implementation an object can exist
+ * in both queues at the same time.
+ *
+ * @param <T> type of ldap object
+ *
+ * @author Middleware Services
+ * @version $Revision: 2241 $ $Date: 2012-02-07 20:08:51 +0000 (Tue, 07 Feb 2012) $
+ */
+public abstract class AbstractLdapPool<T extends BaseLdap>
+ implements LdapPool<T>
+{
+
+ /** Lock for the entire pool. */
+ protected final ReentrantLock poolLock = new ReentrantLock();
+
+ /** Condition for notifying threads that an object was returned. */
+ protected final Condition poolNotEmpty = poolLock.newCondition();
+
+ /** Lock for check ins. */
+ protected final ReentrantLock checkInLock = new ReentrantLock();
+
+ /** Lock for check outs. */
+ protected final ReentrantLock checkOutLock = new ReentrantLock();
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** List of available ldap objects in the pool. */
+ protected Queue<PooledLdap<T>> available = new LinkedList<PooledLdap<T>>();
+
+ /** List of ldap objects in use. */
+ protected Queue<PooledLdap<T>> active = new LinkedList<PooledLdap<T>>();
+
+ /** Ldap pool config. */
+ protected LdapPoolConfig poolConfig;
+
+ /** Factory to create ldap objects. */
+ protected LdapFactory<T> ldapFactory;
+
+ /** Timer for scheduling pool tasks. */
+ private Timer poolTimer = new Timer(true);
+
+
+ /**
+ * Creates a new pool with the supplied pool configuration and ldap factory.
+ * The pool configuration will be marked as immutable by this pool.
+ *
+ * @param lpc <code>LdapPoolConfig</code>
+ * @param lf <code>LdapFactory</code>
+ */
+ public AbstractLdapPool(final LdapPoolConfig lpc, final LdapFactory<T> lf)
+ {
+ this.poolConfig = lpc;
+ this.poolConfig.makeImmutable();
+ this.ldapFactory = lf;
+ }
+
+
+ /** {@inheritDoc} */
+ public LdapPoolConfig getLdapPoolConfig()
+ {
+ return this.poolConfig;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setPoolTimer(final Timer t)
+ {
+ this.poolTimer = t;
+ }
+
+
+ /** {@inheritDoc} */
+ public void initialize()
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("beginning pool initialization");
+ }
+
+ this.poolTimer.scheduleAtFixedRate(
+ new PrunePoolTask<T>(this),
+ this.poolConfig.getPruneTimerPeriod(),
+ this.poolConfig.getPruneTimerPeriod());
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("prune pool task scheduled");
+ }
+
+ this.poolTimer.scheduleAtFixedRate(
+ new ValidatePoolTask<T>(this),
+ this.poolConfig.getValidateTimerPeriod(),
+ this.poolConfig.getValidateTimerPeriod());
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("validate pool task scheduled");
+ }
+
+ this.initializePool();
+
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("pool initialized to size " + this.available.size());
+ }
+ }
+
+
+ /**
+ * Attempts to fill the pool to its minimum size.
+ *
+ * @throws IllegalStateException if the pool does not contain at least one
+ * connection and it's minimum size is greater than zero
+ */
+ private void initializePool()
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "checking ldap pool size >= " + this.poolConfig.getMinPoolSize());
+ }
+
+ int count = 0;
+ this.poolLock.lock();
+ try {
+ while (
+ this.available.size() < this.poolConfig.getMinPoolSize() &&
+ count < this.poolConfig.getMinPoolSize() * 2) {
+ final T t = this.createAvailable();
+ if (this.poolConfig.isValidateOnCheckIn()) {
+ if (this.ldapFactory.validate(t)) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "ldap object passed initialize validation: " + t);
+ }
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn(
+ "ldap object failed initialize validation: " + t);
+ }
+ this.removeAvailable(t);
+ }
+ }
+ count++;
+ }
+ if (this.available.size() == 0 && this.poolConfig.getMinPoolSize() > 0) {
+ throw new IllegalStateException("Could not initialize pool");
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void close()
+ {
+ this.poolLock.lock();
+ try {
+ while (this.available.size() > 0) {
+ final PooledLdap<T> pl = this.available.remove();
+ this.ldapFactory.destroy(pl.getLdap());
+ }
+ while (this.active.size() > 0) {
+ final PooledLdap<T> pl = this.active.remove();
+ this.ldapFactory.destroy(pl.getLdap());
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("pool closed");
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+
+ this.poolTimer.cancel();
+ }
+
+
+ /**
+ * Create a new ldap object and place it in the available pool.
+ *
+ * @return ldap object that was placed in the available pool
+ */
+ protected T createAvailable()
+ {
+ final T t = this.ldapFactory.create();
+ if (t != null) {
+ final PooledLdap<T> pl = new PooledLdap<T>(t);
+ this.poolLock.lock();
+ try {
+ this.available.add(pl);
+ } finally {
+ this.poolLock.unlock();
+ }
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("unable to create available ldap object");
+ }
+ }
+ return t;
+ }
+
+
+ /**
+ * Create a new ldap object and place it in the active pool.
+ *
+ * @return ldap object that was placed in the active pool
+ */
+ protected T createActive()
+ {
+ final T t = this.ldapFactory.create();
+ if (t != null) {
+ final PooledLdap<T> pl = new PooledLdap<T>(t);
+ this.poolLock.lock();
+ try {
+ this.active.add(pl);
+ } finally {
+ this.poolLock.unlock();
+ }
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("unable to create active ldap object");
+ }
+ }
+ return t;
+ }
+
+
+ /**
+ * Create a new ldap object and place it in both the available and active
+ * pools.
+ *
+ * @return ldap object that was placed in the available and active pools
+ */
+ protected T createAvailableAndActive()
+ {
+ final T t = this.ldapFactory.create();
+ if (t != null) {
+ final PooledLdap<T> pl = new PooledLdap<T>(t);
+ this.poolLock.lock();
+ try {
+ this.available.add(pl);
+ this.active.add(pl);
+ } finally {
+ this.poolLock.unlock();
+ }
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("unable to create available and active ldap object");
+ }
+ }
+ return t;
+ }
+
+
+ /**
+ * Remove an ldap object from the available pool.
+ *
+ * @param t ldap object that exists in the available pool
+ */
+ protected void removeAvailable(final T t)
+ {
+ boolean destroy = false;
+ final PooledLdap<T> pl = new PooledLdap<T>(t);
+ this.poolLock.lock();
+ try {
+ if (this.available.remove(pl)) {
+ destroy = true;
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn(
+ "attempt to remove unknown available ldap object: " + t);
+ }
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ if (destroy) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("removing available ldap object: " + t);
+ }
+ this.ldapFactory.destroy(t);
+ }
+ }
+
+
+ /**
+ * Remove an ldap object from the active pool.
+ *
+ * @param t ldap object that exists in the active pool
+ */
+ protected void removeActive(final T t)
+ {
+ boolean destroy = false;
+ final PooledLdap<T> pl = new PooledLdap<T>(t);
+ this.poolLock.lock();
+ try {
+ if (this.active.remove(pl)) {
+ destroy = true;
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn(
+ "attempt to remove unknown active ldap object: " + t);
+ }
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ if (destroy) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("removing active ldap object: " + t);
+ }
+ this.ldapFactory.destroy(t);
+ }
+ }
+
+
+ /**
+ * Remove an ldap object from both the available and active pools.
+ *
+ * @param t ldap object that exists in the both the available and active
+ * pools
+ */
+ protected void removeAvailableAndActive(final T t)
+ {
+ boolean destroy = false;
+ final PooledLdap<T> pl = new PooledLdap<T>(t);
+ this.poolLock.lock();
+ try {
+ if (this.available.remove(pl)) {
+ destroy = true;
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "attempt to remove unknown available ldap object: " + t);
+ }
+ }
+ if (this.active.remove(pl)) {
+ destroy = true;
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "attempt to remove unknown active ldap object: " + t);
+ }
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ if (destroy) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("removing active ldap object: " + t);
+ }
+ this.ldapFactory.destroy(t);
+ }
+ }
+
+
+ /**
+ * Attempts to activate and validate an ldap object. Performed before an
+ * object is returned from {@link LdapPool#checkOut()}.
+ *
+ * @param t ldap object
+ *
+ * @throws LdapPoolException if this method fais
+ * @throws LdapActivationException if the ldap object cannot be activated
+ * @throws LdapValidateException if the ldap object cannot be validated
+ */
+ protected void activateAndValidate(final T t)
+ throws LdapPoolException
+ {
+ if (!this.ldapFactory.activate(t)) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("ldap object failed activation: " + t);
+ }
+ this.removeAvailableAndActive(t);
+ throw new LdapActivationException("Activation of ldap object failed");
+ }
+ if (
+ this.poolConfig.isValidateOnCheckOut() &&
+ !this.ldapFactory.validate(t)) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("ldap object failed check out validation: " + t);
+ }
+ this.removeAvailableAndActive(t);
+ throw new LdapValidationException("Validation of ldap object failed");
+ }
+ }
+
+
+ /**
+ * Attempts to validate and passivate an ldap object. Performed when an object
+ * is given to {@link LdapPool#checkIn}.
+ *
+ * @param t ldap object
+ *
+ * @return whether both validate and passivation succeeded
+ */
+ protected boolean validateAndPassivate(final T t)
+ {
+ boolean valid = false;
+ if (this.poolConfig.isValidateOnCheckIn()) {
+ if (!this.ldapFactory.validate(t)) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("ldap object failed check in validation: " + t);
+ }
+ } else {
+ valid = true;
+ }
+ } else {
+ valid = true;
+ }
+ if (valid && !this.ldapFactory.passivate(t)) {
+ valid = false;
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("ldap object failed activation: " + t);
+ }
+ }
+ return valid;
+ }
+
+
+ /** {@inheritDoc} */
+ public void prune()
+ {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "waiting for pool lock to prune " + this.poolLock.getQueueLength());
+ }
+ this.poolLock.lock();
+ try {
+ if (this.active.size() == 0) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("pruning pool of size " + this.available.size());
+ }
+ while (this.available.size() > this.poolConfig.getMinPoolSize()) {
+ PooledLdap<T> pl = this.available.peek();
+ final long time = System.currentTimeMillis() - pl.getCreatedTime();
+ if (time > this.poolConfig.getExpirationTime()) {
+ pl = this.available.remove();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "removing " + pl.getLdap() + " in the pool for " + time + "ms");
+ }
+ this.ldapFactory.destroy(pl.getLdap());
+ } else {
+ break;
+ }
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("pool size pruned to " + this.available.size());
+ }
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("pool is currently active, no objects pruned");
+ }
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void validate()
+ {
+ this.poolLock.lock();
+ try {
+ if (this.active.size() == 0) {
+ if (this.poolConfig.isValidatePeriodically()) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "validate for pool of size " + this.available.size());
+ }
+
+ final Queue<PooledLdap<T>> remove = new LinkedList<PooledLdap<T>>();
+ for (PooledLdap<T> pl : this.available) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("validating " + pl.getLdap());
+ }
+ if (this.ldapFactory.validate(pl.getLdap())) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "ldap object passed validation: " + pl.getLdap());
+ }
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn(
+ "ldap object failed validation: " + pl.getLdap());
+ }
+ remove.add(pl);
+ }
+ }
+ for (PooledLdap<T> pl : remove) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("removing " + pl.getLdap() + " from the pool");
+ }
+ this.available.remove(pl);
+ this.ldapFactory.destroy(pl.getLdap());
+ }
+ }
+ this.initializePool();
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "pool size after validation is " + this.available.size());
+ }
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "pool is currently active, " +
+ "no validation performed");
+ }
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public int availableCount()
+ {
+ return this.available.size();
+ }
+
+
+ /** {@inheritDoc} */
+ public int activeCount()
+ {
+ return this.active.size();
+ }
+
+
+ /**
+ * Called by the garbage collector on an object when garbage collection
+ * determines that there are no more references to the object.
+ *
+ * @throws Throwable if an exception is thrown by this method
+ */
+ protected void finalize()
+ throws Throwable
+ {
+ try {
+ this.close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+
+ /**
+ * <code>PooledLdap</code> contains an ldap object that is participating in a
+ * pool. Used to track how long an ldap object has been in either the
+ * available or active queues.
+ *
+ * @param <T> type of ldap object
+ */
+ static protected class PooledLdap<T extends BaseLdap>
+ {
+
+ /** hash code seed. */
+ protected static final int HASH_CODE_SEED = 89;
+
+ /** Underlying ldap object. */
+ private T ldap;
+
+ /** Time this object was created. */
+ private long createdTime;
+
+
+ /**
+ * Creates a new <code>PooledLdap</code> with the supplied ldap object.
+ *
+ * @param t ldap object
+ */
+ public PooledLdap(final T t)
+ {
+ this.ldap = t;
+ this.createdTime = System.currentTimeMillis();
+ }
+
+
+ /**
+ * Returns the ldap object.
+ *
+ * @return underlying ldap object
+ */
+ public T getLdap()
+ {
+ return this.ldap;
+ }
+
+
+ /**
+ * Returns the time this object was created.
+ *
+ * @return creation time
+ */
+ public long getCreatedTime()
+ {
+ return this.createdTime;
+ }
+
+
+ /**
+ * Returns whether the supplied <code>Object</code> contains the same data
+ * as this bean.
+ *
+ * @param o <code>Object</code>
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean equals(final Object o)
+ {
+ if (o == null) {
+ return false;
+ }
+ return
+ o == this ||
+ (this.getClass() == o.getClass() &&
+ o.hashCode() == this.hashCode());
+ }
+
+
+ /**
+ * This returns the hash code for this object.
+ *
+ * @return <code>int</code>
+ */
+ public int hashCode()
+ {
+ int hc = HASH_CODE_SEED;
+ if (this.ldap != null) {
+ hc += this.ldap.hashCode();
+ }
+ return hc;
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/BlockingLdapPool.java b/src/main/java/edu/vt/middleware/ldap/pool/BlockingLdapPool.java
new file mode 100644
index 0000000..b42638f
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/BlockingLdapPool.java
@@ -0,0 +1,323 @@
+/*
+ $Id: BlockingLdapPool.java 2241 2012-02-07 20:08:51Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2241 $
+ Updated: $Date: 2012-02-07 20:08:51 +0000 (Tue, 07 Feb 2012) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.TimeUnit;
+import edu.vt.middleware.ldap.Ldap;
+
+/**
+ * <code>BlockingLdapPool</code> implements a pool of ldap objects that has a
+ * set minimum and maximum size. The pool will not grow beyond the maximum size
+ * and when the pool is exhausted, requests for new objects will block. The
+ * length of time the pool will block is determined by {@link
+ * #getBlockWaitTime()}. By default the pool will block indefinitely and there
+ * is no guarantee that waiting threads will be serviced in the order in which
+ * they made their request. This implementation should be used when you need to
+ * control the <em>exact</em> number of ldap connections that can be created.
+ * See {@link AbstractLdapPool}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2241 $ $Date: 2012-02-07 20:08:51 +0000 (Tue, 07 Feb 2012) $
+ */
+public class BlockingLdapPool extends AbstractLdapPool<Ldap>
+{
+
+ /** Time in milliseconds to wait for an available ldap object. */
+ private long blockWaitTime;
+
+
+ /** Creates a new ldap pool using {@link DefaultLdapFactory}. */
+ public BlockingLdapPool()
+ {
+ super(new LdapPoolConfig(), new DefaultLdapFactory());
+ }
+
+
+ /**
+ * Creates a new ldap pool with the supplied ldap factory.
+ *
+ * @param lf ldap factory
+ */
+ public BlockingLdapPool(final LdapFactory<Ldap> lf)
+ {
+ super(new LdapPoolConfig(), lf);
+ }
+
+
+ /**
+ * Creates a new ldap pool with the supplied ldap config and factory.
+ *
+ * @param lpc ldap pool configuration
+ * @param lf ldap factory
+ */
+ public BlockingLdapPool(final LdapPoolConfig lpc, final LdapFactory<Ldap> lf)
+ {
+ super(lpc, lf);
+ }
+
+
+ /**
+ * Returns the block wait time. Default time is 0, which will wait
+ * indefinitely.
+ *
+ * @return time in milliseconds to wait for available ldap objects
+ */
+ public long getBlockWaitTime()
+ {
+ return this.blockWaitTime;
+ }
+
+
+ /**
+ * Sets the block wait time. Default time is 0, which will wait indefinitely.
+ *
+ * @param time in milliseconds to wait for available ldap objects
+ */
+ public void setBlockWaitTime(final long time)
+ {
+ if (time >= 0) {
+ this.blockWaitTime = time;
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public Ldap checkOut()
+ throws LdapPoolException
+ {
+ Ldap l = null;
+ boolean create = false;
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "waiting on pool lock for check out " + this.poolLock.getQueueLength());
+ }
+ this.poolLock.lock();
+ try {
+ // if an available object exists, use it
+ // if no available objects and the pool can grow, attempt to create
+ // otherwise the pool is full, block until an object is returned
+ if (this.available.size() > 0) {
+ try {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("retrieve available ldap object");
+ }
+ l = this.retrieveAvailable();
+ } catch (NoSuchElementException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("could not remove ldap object from list", e);
+ }
+ throw new IllegalStateException("Pool is empty", e);
+ }
+ } else if (this.active.size() < this.poolConfig.getMaxPoolSize()) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("pool can grow, attempt to create ldap object");
+ }
+ create = true;
+ } else {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "pool is full, block until ldap object " +
+ "is available");
+ }
+ l = this.blockAvailable();
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+
+ if (create) {
+ // previous block determined a creation should occur
+ // block here until create occurs without locking the whole pool
+ // if the pool is already maxed or creates are failing,
+ // block until an object is available
+ this.checkOutLock.lock();
+ try {
+ boolean b = true;
+ this.poolLock.lock();
+ try {
+ if (
+ this.available.size() + this.active.size() ==
+ this.poolConfig.getMaxPoolSize()) {
+ b = false;
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ if (b) {
+ l = this.createActive();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("created new active ldap object: " + l);
+ }
+ }
+ } finally {
+ this.checkOutLock.unlock();
+ }
+ if (l == null) {
+ if (this.available.size() == 0 && this.active.size() == 0) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Could not service check out request");
+ }
+ throw new LdapPoolExhaustedException(
+ "Pool is empty and object creation failed");
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "create failed, block until ldap object " +
+ "is available");
+ }
+ l = this.blockAvailable();
+ }
+ }
+
+ if (l != null) {
+ this.activateAndValidate(l);
+ } else {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Could not service check out request");
+ }
+ throw new LdapPoolExhaustedException(
+ "Pool is empty and object creation failed");
+ }
+
+ return l;
+ }
+
+
+ /**
+ * This attempts to retrieve an ldap object from the available queue.
+ *
+ * @return ldap object from the pool
+ *
+ * @throws NoSuchElementException if the available queue is empty
+ */
+ protected Ldap retrieveAvailable()
+ {
+ Ldap l = null;
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "waiting on pool lock for retrieve available " +
+ this.poolLock.getQueueLength());
+ }
+ this.poolLock.lock();
+ try {
+ final PooledLdap<Ldap> pl = this.available.remove();
+ this.active.add(new PooledLdap<Ldap>(pl.getLdap()));
+ l = pl.getLdap();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("retrieved available ldap object: " + l);
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ return l;
+ }
+
+
+ /**
+ * This blocks until an ldap object can be aquired.
+ *
+ * @return ldap object from the pool
+ *
+ * @throws LdapPoolException if this method fails
+ * @throws BlockingTimeoutException if this pool is configured with a block
+ * time and it occurs
+ * @throws PoolInterruptedException if the current thread is interrupted
+ */
+ protected Ldap blockAvailable()
+ throws LdapPoolException
+ {
+ Ldap l = null;
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "waiting on pool lock for block available " +
+ this.poolLock.getQueueLength());
+ }
+ this.poolLock.lock();
+ try {
+ while (l == null) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("available pool is empty, waiting...");
+ }
+ if (this.blockWaitTime > 0) {
+ if (
+ !this.poolNotEmpty.await(
+ this.blockWaitTime,
+ TimeUnit.MILLISECONDS)) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("block time exceeded, throwing exception");
+ }
+ throw new BlockingTimeoutException("Block time exceeded");
+ }
+ } else {
+ this.poolNotEmpty.await();
+ }
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("notified to continue...");
+ }
+ try {
+ l = this.retrieveAvailable();
+ } catch (NoSuchElementException e) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("notified to continue but pool was empty");
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("waiting for available object interrupted", e);
+ }
+ throw new PoolInterruptedException(
+ "Interrupted while waiting for an available object",
+ e);
+ } finally {
+ this.poolLock.unlock();
+ }
+ return l;
+ }
+
+
+ /** {@inheritDoc} */
+ public void checkIn(final Ldap l)
+ {
+ final boolean valid = this.validateAndPassivate(l);
+ final PooledLdap<Ldap> pl = new PooledLdap<Ldap>(l);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "waiting on pool lock for check in " + this.poolLock.getQueueLength());
+ }
+ this.poolLock.lock();
+ try {
+ if (this.active.remove(pl)) {
+ if (valid) {
+ this.available.add(pl);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("returned active ldap object: " + l);
+ }
+ this.poolNotEmpty.signal();
+ }
+ } else if (this.available.contains(pl)) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("returned available ldap object: " + l);
+ }
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("attempt to return unknown ldap object: " + l);
+ }
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/BlockingTimeoutException.java b/src/main/java/edu/vt/middleware/ldap/pool/BlockingTimeoutException.java
new file mode 100644
index 0000000..e8dd9b6
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/BlockingTimeoutException.java
@@ -0,0 +1,65 @@
+/*
+ $Id: BlockingTimeoutException.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+/**
+ * <code>BlockingTimeoutException</code> is thrown when a blocking operation
+ * times out. See {@link BlockingLdapPool#checkOut()}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class BlockingTimeoutException extends LdapPoolException
+{
+
+ /** serialVersionUID. */
+ private static final long serialVersionUID = -5152940431346111294L;
+
+
+ /**
+ * This creates a new <code>BlockingTimeoutException</code> with the supplied
+ * <code>String</code>.
+ *
+ * @param msg <code>String</code>
+ */
+ public BlockingTimeoutException(final String msg)
+ {
+ super(msg);
+ }
+
+
+ /**
+ * This creates a new <code>BlockingTimeoutException</code> with the supplied
+ * <code>Exception</code>.
+ *
+ * @param e <code>Exception</code>
+ */
+ public BlockingTimeoutException(final Exception e)
+ {
+ super(e);
+ }
+
+
+ /**
+ * This creates a new <code>BlockingTimeoutException</code> with the supplied
+ * <code>String</code> and <code>Exception</code>.
+ *
+ * @param msg <code>String</code>
+ * @param e <code>Exception</code>
+ */
+ public BlockingTimeoutException(final String msg, final Exception e)
+ {
+ super(msg, e);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/CloseLdapPassivator.java b/src/main/java/edu/vt/middleware/ldap/pool/CloseLdapPassivator.java
new file mode 100644
index 0000000..a1dcaa5
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/CloseLdapPassivator.java
@@ -0,0 +1,44 @@
+/*
+ $Id: CloseLdapPassivator.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import edu.vt.middleware.ldap.Ldap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>CloseLdapPassivator</code> passivates an ldap object by attempting to
+ * close it's connection to the ldap.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class CloseLdapPassivator implements LdapPassivator<Ldap>
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+
+ /** {@inheritDoc} */
+ public boolean passivate(final Ldap l)
+ {
+ boolean success = false;
+ if (l != null) {
+ l.close();
+ success = true;
+ }
+ return success;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/CompareLdapValidator.java b/src/main/java/edu/vt/middleware/ldap/pool/CompareLdapValidator.java
new file mode 100644
index 0000000..80394dd
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/CompareLdapValidator.java
@@ -0,0 +1,121 @@
+/*
+ $Id: CompareLdapValidator.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.SearchFilter;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>CompareLdapValidator</code> validates an ldap connection is healthy by
+ * performing a compare operation.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class CompareLdapValidator implements LdapValidator<Ldap>
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** DN for validating connections. Default value is {@value}. */
+ private String validateDn = "";
+
+ /** Filter for validating connections. Default value is {@value}. */
+ private SearchFilter validateFilter = new SearchFilter("(objectClass=*)");
+
+
+ /** Default constructor. */
+ public CompareLdapValidator() {}
+
+
+ /**
+ * Creates a new <code>CompareLdapValidator</code> with the supplied compare
+ * dn and filter.
+ *
+ * @param dn to use for compares
+ * @param filter to use for compares
+ */
+ public CompareLdapValidator(final String dn, final SearchFilter filter)
+ {
+ this.validateDn = dn;
+ this.validateFilter = filter;
+ }
+
+
+ /**
+ * Returns the validate DN.
+ *
+ * @return validate DN
+ */
+ public String getValidateDn()
+ {
+ return this.validateDn;
+ }
+
+
+ /**
+ * Returns the validate filter.
+ *
+ * @return validate filter
+ */
+ public SearchFilter getValidateFilter()
+ {
+ return this.validateFilter;
+ }
+
+
+ /**
+ * Sets the validate DN.
+ *
+ * @param s DN
+ */
+ public void setValidateDn(final String s)
+ {
+ this.validateDn = s;
+ }
+
+
+ /**
+ * Sets the validate filter.
+ *
+ * @param filter to compare with
+ */
+ public void setValidateFilter(final SearchFilter filter)
+ {
+ this.validateFilter = filter;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean validate(final Ldap l)
+ {
+ boolean success = false;
+ if (l != null) {
+ try {
+ success = l.compare(this.validateDn, this.validateFilter);
+ } catch (NamingException e) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "validation failed for compare " + this.validateFilter,
+ e);
+ }
+ }
+ }
+ return success;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/ConnectLdapActivator.java b/src/main/java/edu/vt/middleware/ldap/pool/ConnectLdapActivator.java
new file mode 100644
index 0000000..5913bba
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/ConnectLdapActivator.java
@@ -0,0 +1,51 @@
+/*
+ $Id: ConnectLdapActivator.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.Ldap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>ConnectLdapActivator</code> activates an ldap object by attempting to
+ * connect to the ldap.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class ConnectLdapActivator implements LdapActivator<Ldap>
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+
+ /** {@inheritDoc} */
+ public boolean activate(final Ldap l)
+ {
+ boolean success = false;
+ if (l != null) {
+ try {
+ l.connect();
+ success = true;
+ } catch (NamingException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("unabled to connect to the ldap", e);
+ }
+ }
+ }
+ return success;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/ConnectLdapValidator.java b/src/main/java/edu/vt/middleware/ldap/pool/ConnectLdapValidator.java
new file mode 100644
index 0000000..7795f40
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/ConnectLdapValidator.java
@@ -0,0 +1,50 @@
+/*
+ $Id: ConnectLdapValidator.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.Ldap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>ConnectLdapValidator</code> validates an ldap connection is healthy by
+ * testing it is connected.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class ConnectLdapValidator implements LdapValidator<Ldap>
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+
+ /** {@inheritDoc} */
+ public boolean validate(final Ldap l)
+ {
+ boolean success = false;
+ if (l != null) {
+ try {
+ success = l.connect();
+ } catch (NamingException e) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("validation failed for " + l, e);
+ }
+ }
+ }
+ return success;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/DefaultLdapFactory.java b/src/main/java/edu/vt/middleware/ldap/pool/DefaultLdapFactory.java
new file mode 100644
index 0000000..73316b6
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/DefaultLdapFactory.java
@@ -0,0 +1,126 @@
+/*
+ $Id: DefaultLdapFactory.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import java.io.InputStream;
+import javax.naming.NamingException;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.LdapConfig;
+
+/**
+ * <code>DefaultLdapFactory</code> provides a simple implementation of an ldap
+ * factory.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class DefaultLdapFactory extends AbstractLdapFactory<Ldap>
+{
+
+ /** Ldap config to create ldap objects with. */
+ private LdapConfig config;
+
+ /** Whether to connect to the ldap on object creation. */
+ private boolean connectOnCreate = true;
+
+
+ /**
+ * This creates a new <code>DefaultLdapFactory</code> with the default
+ * properties file, which must be located in your classpath.
+ */
+ public DefaultLdapFactory()
+ {
+ this.config = LdapConfig.createFromProperties(null);
+ this.config.makeImmutable();
+ }
+
+
+ /**
+ * This creates a new <code>DefaultLdapFactory</code> with the supplied input
+ * stream.
+ *
+ * @param is <code>InputStream</code>
+ */
+ public DefaultLdapFactory(final InputStream is)
+ {
+ this.config = LdapConfig.createFromProperties(is);
+ this.config.makeImmutable();
+ }
+
+
+ /**
+ * This creates a new <code>DefaultLdapFactory</code> with the supplied ldap
+ * configuration. The ldap configuration will be marked as immutable by this
+ * factory.
+ *
+ * @param lc ldap config
+ */
+ public DefaultLdapFactory(final LdapConfig lc)
+ {
+ this.config = lc;
+ this.config.makeImmutable();
+ }
+
+
+ /**
+ * Returns whether ldap objects will attempt to connect after creation.
+ * Default is true.
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean getConnectOnCreate()
+ {
+ return this.connectOnCreate;
+ }
+
+
+ /**
+ * This sets whether newly created ldap objects will attempt to connect.
+ * Default is true.
+ *
+ * @param b connect on create
+ */
+ public void setConnectOnCreate(final boolean b)
+ {
+ this.connectOnCreate = b;
+ }
+
+
+ /** {@inheritDoc} */
+ public Ldap create()
+ {
+ Ldap l = new Ldap(this.config);
+ if (this.connectOnCreate) {
+ try {
+ l.connect();
+ } catch (NamingException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("unabled to connect to the ldap", e);
+ }
+ l = null;
+ }
+ }
+ return l;
+ }
+
+
+ /** {@inheritDoc} */
+ public void destroy(final Ldap l)
+ {
+ l.close();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("destroyed ldap object: " + l);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/LdapActivationException.java b/src/main/java/edu/vt/middleware/ldap/pool/LdapActivationException.java
new file mode 100644
index 0000000..258cc2f
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/LdapActivationException.java
@@ -0,0 +1,65 @@
+/*
+ $Id: LdapActivationException.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+/**
+ * <code>LdapActivationException</code> is thrown when an attempt to activate a
+ * ldap object fails. See {@link LdapFactory#activate}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class LdapActivationException extends LdapPoolException
+{
+
+ /** serialVersionUID. */
+ private static final long serialVersionUID = -6185502955113178610L;
+
+
+ /**
+ * This creates a new <code>LdapActivationException</code> with the supplied
+ * <code>String</code>.
+ *
+ * @param msg <code>String</code>
+ */
+ public LdapActivationException(final String msg)
+ {
+ super(msg);
+ }
+
+
+ /**
+ * This creates a new <code>LdapActivationException</code> with the supplied
+ * <code>Exception</code>.
+ *
+ * @param e <code>Exception</code>
+ */
+ public LdapActivationException(final Exception e)
+ {
+ super(e);
+ }
+
+
+ /**
+ * This creates a new <code>LdapActivationException</code> with the supplied
+ * <code>String</code> and <code>Exception</code>.
+ *
+ * @param msg <code>String</code>
+ * @param e <code>Exception</code>
+ */
+ public LdapActivationException(final String msg, final Exception e)
+ {
+ super(msg, e);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/LdapActivator.java b/src/main/java/edu/vt/middleware/ldap/pool/LdapActivator.java
new file mode 100644
index 0000000..ff05aea
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/LdapActivator.java
@@ -0,0 +1,39 @@
+/*
+ $Id: LdapActivator.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import edu.vt.middleware.ldap.BaseLdap;
+
+/**
+ * <code>LdapActivator</code> provides an interface for activating ldap objects
+ * when they enter the pool.
+ *
+ * @param <T> type of ldap object
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface LdapActivator<T extends BaseLdap>
+{
+
+
+ /**
+ * Activate the supplied ldap object.
+ *
+ * @param t ldap object
+ *
+ * @return whether activation was successful
+ */
+ boolean activate(T t);
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/LdapFactory.java b/src/main/java/edu/vt/middleware/ldap/pool/LdapFactory.java
new file mode 100644
index 0000000..3090f10
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/LdapFactory.java
@@ -0,0 +1,75 @@
+/*
+ $Id: LdapFactory.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import edu.vt.middleware.ldap.BaseLdap;
+
+/**
+ * <code>LdapFactory</code> provides an interface for creating, activating,
+ * validating, and destroying ldap objects.
+ *
+ * @param <T> type of ldap object
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface LdapFactory<T extends BaseLdap>
+{
+
+
+ /**
+ * Create a new ldap object.
+ *
+ * @return ldap object
+ */
+ T create();
+
+
+ /**
+ * Destroy an ldap object.
+ *
+ * @param t ldap object
+ */
+ void destroy(T t);
+
+
+ /**
+ * Prepare the supplied object for placement in the pool.
+ *
+ * @param t ldap object
+ *
+ * @return whether the supplied object successfully activated
+ */
+ boolean activate(T t);
+
+
+ /**
+ * Prepare the supplied object for removal from the pool.
+ *
+ * @param t ldap object
+ *
+ * @return whether the supplied object successfully passivated
+ */
+ boolean passivate(T t);
+
+
+ /**
+ * Verify an ldap object is still viable for use in the pool.
+ *
+ * @param t ldap object
+ *
+ * @return whether the supplied object is ready for use
+ */
+ boolean validate(T t);
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/LdapPassivator.java b/src/main/java/edu/vt/middleware/ldap/pool/LdapPassivator.java
new file mode 100644
index 0000000..7282b52
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/LdapPassivator.java
@@ -0,0 +1,39 @@
+/*
+ $Id: LdapPassivator.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import edu.vt.middleware.ldap.BaseLdap;
+
+/**
+ * <code>LdapPasivator</code> provides an interface for passivating ldap objects
+ * when they are checked back into the pool.
+ *
+ * @param <T> type of ldap object
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface LdapPassivator<T extends BaseLdap>
+{
+
+
+ /**
+ * Passivate the supplied ldap object.
+ *
+ * @param t ldap object
+ *
+ * @return whether passivation was successful
+ */
+ boolean passivate(T t);
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/LdapPool.java b/src/main/java/edu/vt/middleware/ldap/pool/LdapPool.java
new file mode 100644
index 0000000..eb362b3
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/LdapPool.java
@@ -0,0 +1,107 @@
+/*
+ $Id: LdapPool.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import java.util.Timer;
+import edu.vt.middleware.ldap.BaseLdap;
+
+/**
+ * <code>LdapPool</code> provides an interface for pooling ldap objects.
+ *
+ * @param <T> type of ldap object
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface LdapPool<T extends BaseLdap>
+{
+
+
+ /**
+ * Returns the configuration for this pool.
+ *
+ * @return ldap pool config
+ */
+ LdapPoolConfig getLdapPoolConfig();
+
+
+ /**
+ * Sets the pool to use an existing timer. Pool will use an internal timer if
+ * none is provided. Must be called before {@link #initialize()}.
+ *
+ * @param t timer used to schedule pool tasks
+ */
+ void setPoolTimer(Timer t);
+
+
+ /** Initialize this pool for use. */
+ void initialize();
+
+
+ /** Empty this pool, closing all connections, and freeing any resources. */
+ void close();
+
+
+ /**
+ * Returns an ldap object from the pool.
+ *
+ * @return ldap object
+ *
+ * @throws LdapPoolException if this operation fails
+ * @throws BlockingTimeoutException if this pool is configured with a block
+ * time and it occurs
+ * @throws PoolInterruptedException if this pool is configured with a block
+ * time and the current thread is interrupted
+ */
+ T checkOut()
+ throws LdapPoolException;
+
+
+ /**
+ * Returns an ldap object to the pool.
+ *
+ * @param t ldap object
+ */
+ void checkIn(final T t);
+
+
+ /**
+ * Attempts to reduce the size of the pool back to it's configured minimum.
+ * {@link LdapPoolConfig#setMinPoolSize(int)}.
+ */
+ void prune();
+
+
+ /**
+ * Attempts to validate all objects in the pool. {@link
+ * LdapPoolConfig#setValidatePeriodically(boolean)}.
+ */
+ void validate();
+
+
+ /**
+ * Returns the number of ldap objects available for use.
+ *
+ * @return count
+ */
+ int availableCount();
+
+
+ /**
+ * Returns the number of ldap objects in use.
+ *
+ * @return count
+ */
+ int activeCount();
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/LdapPoolConfig.java b/src/main/java/edu/vt/middleware/ldap/pool/LdapPoolConfig.java
new file mode 100644
index 0000000..7786b58
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/LdapPoolConfig.java
@@ -0,0 +1,379 @@
+/*
+ $Id: LdapPoolConfig.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import java.io.InputStream;
+import edu.vt.middleware.ldap.props.AbstractPropertyConfig;
+import edu.vt.middleware.ldap.props.LdapConfigPropertyInvoker;
+import edu.vt.middleware.ldap.props.LdapProperties;
+
+/**
+ * <code>LdapPoolConfig</code> contains all the configuration data that the
+ * pooling implementations need to control the pool.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class LdapPoolConfig extends AbstractPropertyConfig
+{
+
+ /** Domain to look for ldap properties in, value is {@value}. */
+ public static final String PROPERTIES_DOMAIN = "edu.vt.middleware.ldap.pool.";
+
+ /** Default min pool size, value is {@value}. */
+ public static final int DEFAULT_MIN_POOL_SIZE = 3;
+
+ /** Default max pool size, value is {@value}. */
+ public static final int DEFAULT_MAX_POOL_SIZE = 10;
+
+ /** Default validate on check in, value is {@value}. */
+ public static final boolean DEFAULT_VALIDATE_ON_CHECKIN = false;
+
+ /** Default validate on check out, value is {@value}. */
+ public static final boolean DEFAULT_VALIDATE_ON_CHECKOUT = false;
+
+ /** Default validate periodically, value is {@value}. */
+ public static final boolean DEFAULT_VALIDATE_PERIODICALLY = false;
+
+ /** Default validate timer period, value is {@value}. */
+ public static final long DEFAULT_VALIDATE_TIMER_PERIOD = 1800000;
+
+ /** Default prune timer period, value is {@value}. */
+ public static final long DEFAULT_PRUNE_TIMER_PERIOD = 300000;
+
+ /** Default expiration time, value is {@value}. */
+ public static final long DEFAULT_EXPIRATION_TIME = 600000;
+
+ /** Invoker for ldap properties. */
+ private static final LdapConfigPropertyInvoker PROPERTIES =
+ new LdapConfigPropertyInvoker(LdapPoolConfig.class, PROPERTIES_DOMAIN);
+
+ /** Min pool size. */
+ private int minPoolSize = DEFAULT_MIN_POOL_SIZE;
+
+ /** Max pool size. */
+ private int maxPoolSize = DEFAULT_MAX_POOL_SIZE;
+
+ /** Whether the ldap object should be validated when returned to the pool. */
+ private boolean validateOnCheckIn = DEFAULT_VALIDATE_ON_CHECKIN;
+
+ /** Whether the ldap object should be validated when given from the pool. */
+ private boolean validateOnCheckOut = DEFAULT_VALIDATE_ON_CHECKOUT;
+
+ /** Whether the pool should be validated periodically. */
+ private boolean validatePeriodically = DEFAULT_VALIDATE_PERIODICALLY;
+
+ /** Time in milliseconds that the validate pool timer should repeat. */
+ private long validateTimerPeriod = DEFAULT_VALIDATE_TIMER_PERIOD;
+
+ /** Time in milliseconds that the prune pool timer should repeat. */
+ private long pruneTimerPeriod = DEFAULT_PRUNE_TIMER_PERIOD;
+
+ /** Time in milliseconds that ldap objects should be considered expired. */
+ private long expirationTime = DEFAULT_EXPIRATION_TIME;
+
+
+ /** Default constructor. */
+ public LdapPoolConfig() {}
+
+
+ /**
+ * This returns the min pool size for the <code>LdapPoolConfig</code>. Default
+ * value is {@link #DEFAULT_MIN_POOL_SIZE}. This value represents the size of
+ * the pool after the prune timer has run.
+ *
+ * @return <code>int</code> - min pool size
+ */
+ public int getMinPoolSize()
+ {
+ return this.minPoolSize;
+ }
+
+
+ /**
+ * This returns the max pool size for the <code>LdapPoolConfig</code>. Default
+ * value is {@link #DEFAULT_MAX_POOL_SIZE}. This value may or may not be
+ * strictly enforced depending on the pooling implementation.
+ *
+ * @return <code>int</code> - max pool size
+ */
+ public int getMaxPoolSize()
+ {
+ return this.maxPoolSize;
+ }
+
+
+ /**
+ * This returns the validate on check in flag for the <code>
+ * LdapPoolConfig</code>. Default value is {@link
+ * #DEFAULT_VALIDATE_ON_CHECKIN}.
+ *
+ * @return <code>boolean</code> - validate on check in
+ */
+ public boolean isValidateOnCheckIn()
+ {
+ return this.validateOnCheckIn;
+ }
+
+
+ /**
+ * This returns the validate on check out flag for the <code>
+ * LdapPoolConfig</code>. Default value is {@link
+ * #DEFAULT_VALIDATE_ON_CHECKOUT}.
+ *
+ * @return <code>boolean</code> - validate on check in
+ */
+ public boolean isValidateOnCheckOut()
+ {
+ return this.validateOnCheckOut;
+ }
+
+
+ /**
+ * This returns the validate periodically flag for the <code>
+ * LdapPoolConfig</code>. Default value is {@link
+ * #DEFAULT_VALIDATE_PERIODICALLY}.
+ *
+ * @return <code>boolean</code> - validate periodically
+ */
+ public boolean isValidatePeriodically()
+ {
+ return this.validatePeriodically;
+ }
+
+
+ /**
+ * This returns the prune timer period for the <code>LdapPoolConfig</code>.
+ * Default value is {@link #DEFAULT_PRUNE_TIMER_PERIOD}. The prune timer
+ * attempts to execute {@link LdapPool#prune()}.
+ *
+ * @return <code>long</code> - prune timer period in milliseconds
+ */
+ public long getPruneTimerPeriod()
+ {
+ return this.pruneTimerPeriod;
+ }
+
+
+ /**
+ * This returns the validate timer period for the <code>LdapPoolConfig</code>.
+ * Default value is {@link #DEFAULT_VALIDATE_TIMER_PERIOD}. The validate timer
+ * attempts to execute {@link LdapPool#validate()}.
+ *
+ * @return <code>long</code> - validate timer period in milliseconds
+ */
+ public long getValidateTimerPeriod()
+ {
+ return this.validateTimerPeriod;
+ }
+
+
+ /**
+ * This returns the expiration time for the <code>LdapPoolConfig</code>.
+ * Default value is {@link #DEFAULT_EXPIRATION_TIME}. The expiration time
+ * represents the max time an ldap object should be available before it is
+ * considered stale. This value does not apply to objects in the pool if the
+ * pool has only a minimum number of objects available.
+ *
+ * @return <code>long</code> - expiration time in milliseconds
+ */
+ public long getExpirationTime()
+ {
+ return this.expirationTime;
+ }
+
+
+ /**
+ * This sets the min pool size for the <code>LdapPoolConfig</code>.
+ *
+ * @param size <code>int</code>
+ */
+ public void setMinPoolSize(final int size)
+ {
+ checkImmutable();
+ if (size >= 0) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting minPoolSize: " + size);
+ }
+ this.minPoolSize = size;
+ }
+ }
+
+
+ /**
+ * This sets the max pool size for the <code>LdapPoolConfig</code>.
+ *
+ * @param size <code>int</code>
+ */
+ public void setMaxPoolSize(final int size)
+ {
+ checkImmutable();
+ if (size >= 0) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting maxPoolSize: " + size);
+ }
+ this.maxPoolSize = size;
+ }
+ }
+
+
+ /**
+ * This sets the validate on check in flag for the <code>
+ * LdapPoolConfig</code>.
+ *
+ * @param b <code>boolean</code>
+ */
+ public void setValidateOnCheckIn(final boolean b)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting validateOnCheckIn: " + b);
+ }
+ this.validateOnCheckIn = b;
+ }
+
+
+ /**
+ * This sets the validate on check out flag for the <code>
+ * LdapPoolConfig</code>.
+ *
+ * @param b <code>boolean</code>
+ */
+ public void setValidateOnCheckOut(final boolean b)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting validateOnCheckOut: " + b);
+ }
+ this.validateOnCheckOut = b;
+ }
+
+
+ /**
+ * This sets the validate periodically flag for the <code>
+ * LdapPoolConfig</code>.
+ *
+ * @param b <code>boolean</code>
+ */
+ public void setValidatePeriodically(final boolean b)
+ {
+ checkImmutable();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting validatePeriodically: " + b);
+ }
+ this.validatePeriodically = b;
+ }
+
+
+ /**
+ * Sets the period for which the prune pool timer will run.
+ *
+ * @param time in milliseconds
+ */
+ public void setPruneTimerPeriod(final long time)
+ {
+ checkImmutable();
+ if (time >= 0) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting pruneTimerPeriod: " + time);
+ }
+ this.pruneTimerPeriod = time;
+ }
+ }
+
+
+ /**
+ * Sets the period for which the validate pool timer will run.
+ *
+ * @param time in milliseconds
+ */
+ public void setValidateTimerPeriod(final long time)
+ {
+ checkImmutable();
+ if (time >= 0) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting validateTimerPeriod: " + time);
+ }
+ this.validateTimerPeriod = time;
+ }
+ }
+
+
+ /**
+ * Sets the time that an ldap object should be considered stale and ready for
+ * removal from the pool.
+ *
+ * @param time in milliseconds
+ */
+ public void setExpirationTime(final long time)
+ {
+ checkImmutable();
+ if (time >= 0) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("setting expirationTime: " + time);
+ }
+ this.expirationTime = time;
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public String getPropertiesDomain()
+ {
+ return PROPERTIES_DOMAIN;
+ }
+
+
+ /** {@inheritDoc} */
+ public void setEnvironmentProperties(final String name, final String value)
+ {
+ checkImmutable();
+ if (name != null && value != null) {
+ if (PROPERTIES.hasProperty(name)) {
+ PROPERTIES.setProperty(this, name, value);
+ }
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean hasEnvironmentProperty(final String name)
+ {
+ return PROPERTIES.hasProperty(name);
+ }
+
+
+ /**
+ * Create an instance of this class initialized with properties from the input
+ * stream. If the input stream is null, load properties from the default
+ * properties file.
+ *
+ * @param is to load properties from
+ *
+ * @return <code>LdapPoolConfig</code> initialized ldap pool config
+ */
+ public static LdapPoolConfig createFromProperties(final InputStream is)
+ {
+ final LdapPoolConfig poolConfig = new LdapPoolConfig();
+ LdapProperties properties = null;
+ if (is != null) {
+ properties = new LdapProperties(poolConfig, is);
+ } else {
+ properties = new LdapProperties(poolConfig);
+ properties.useDefaultPropertiesFile();
+ }
+ properties.configure();
+ return poolConfig;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/LdapPoolException.java b/src/main/java/edu/vt/middleware/ldap/pool/LdapPoolException.java
new file mode 100644
index 0000000..37c9009
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/LdapPoolException.java
@@ -0,0 +1,65 @@
+/*
+ $Id: LdapPoolException.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+/**
+ * <code>LdapPoolException</code> is the base exception thrown when a pool
+ * operation fails.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class LdapPoolException extends Exception
+{
+
+ /** serialVersionUID. */
+ private static final long serialVersionUID = 4077412841480524865L;
+
+
+ /**
+ * This creates a new <code>LdapPoolException</code> with the supplied <code>
+ * String</code>.
+ *
+ * @param msg <code>String</code>
+ */
+ public LdapPoolException(final String msg)
+ {
+ super(msg);
+ }
+
+
+ /**
+ * This creates a new <code>LdapPoolException</code> with the supplied <code>
+ * Exception</code>.
+ *
+ * @param e <code>Exception</code>
+ */
+ public LdapPoolException(final Exception e)
+ {
+ super(e);
+ }
+
+
+ /**
+ * This creates a new <code>LdapPoolException</code> with the supplied <code>
+ * String</code> and <code>Exception</code>.
+ *
+ * @param msg <code>String</code>
+ * @param e <code>Exception</code>
+ */
+ public LdapPoolException(final String msg, final Exception e)
+ {
+ super(msg, e);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/LdapPoolExhaustedException.java b/src/main/java/edu/vt/middleware/ldap/pool/LdapPoolExhaustedException.java
new file mode 100644
index 0000000..d77dd58
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/LdapPoolExhaustedException.java
@@ -0,0 +1,65 @@
+/*
+ $Id: LdapPoolExhaustedException.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+/**
+ * <code>LdapPoolExhaustedException</code> is thrown when the pool is empty and
+ * no need requests can be serviced.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class LdapPoolExhaustedException extends LdapPoolException
+{
+
+ /** serialVersionUID. */
+ private static final long serialVersionUID = 900885030182519501L;
+
+
+ /**
+ * This creates a new <code>LdapPoolExhaustedException</code> with the
+ * supplied <code>String</code>.
+ *
+ * @param msg <code>String</code>
+ */
+ public LdapPoolExhaustedException(final String msg)
+ {
+ super(msg);
+ }
+
+
+ /**
+ * This creates a new <code>LdapPoolExhaustedException</code> with the
+ * supplied <code>Exception</code>.
+ *
+ * @param e <code>Exception</code>
+ */
+ public LdapPoolExhaustedException(final Exception e)
+ {
+ super(e);
+ }
+
+
+ /**
+ * This creates a new <code>LdapPoolExhaustedException</code> with the
+ * supplied <code>String</code> and <code>Exception</code>.
+ *
+ * @param msg <code>String</code>
+ * @param e <code>Exception</code>
+ */
+ public LdapPoolExhaustedException(final String msg, final Exception e)
+ {
+ super(msg, e);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/LdapValidationException.java b/src/main/java/edu/vt/middleware/ldap/pool/LdapValidationException.java
new file mode 100644
index 0000000..63b81fe
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/LdapValidationException.java
@@ -0,0 +1,65 @@
+/*
+ $Id: LdapValidationException.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+/**
+ * <code>LdapValidationException</code> is thrown when an attempt to validate a
+ * ldap object fails. See {@link LdapFactory#validate}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class LdapValidationException extends LdapPoolException
+{
+
+ /** serialVersionUID. */
+ private static final long serialVersionUID = -3130116579807362686L;
+
+
+ /**
+ * This creates a new <code>LdapValidationException</code> with the supplied
+ * <code>String</code>.
+ *
+ * @param msg <code>String</code>
+ */
+ public LdapValidationException(final String msg)
+ {
+ super(msg);
+ }
+
+
+ /**
+ * This creates a new <code>LdapValidationException</code> with the supplied
+ * <code>Exception</code>.
+ *
+ * @param e <code>Exception</code>
+ */
+ public LdapValidationException(final Exception e)
+ {
+ super(e);
+ }
+
+
+ /**
+ * This creates a new <code>LdapValidationException</code> with the supplied
+ * <code>String</code> and <code>Exception</code>.
+ *
+ * @param msg <code>String</code>
+ * @param e <code>Exception</code>
+ */
+ public LdapValidationException(final String msg, final Exception e)
+ {
+ super(msg, e);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/LdapValidator.java b/src/main/java/edu/vt/middleware/ldap/pool/LdapValidator.java
new file mode 100644
index 0000000..43ac300
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/LdapValidator.java
@@ -0,0 +1,39 @@
+/*
+ $Id: LdapValidator.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import edu.vt.middleware.ldap.BaseLdap;
+
+/**
+ * <code>LdapValidator</code> provides an interface for validating ldap objects
+ * when they are in the pool.
+ *
+ * @param <T> type of ldap object
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface LdapValidator<T extends BaseLdap>
+{
+
+
+ /**
+ * Validate the supplied ldap object.
+ *
+ * @param t ldap object
+ *
+ * @return whether validation was successful
+ */
+ boolean validate(T t);
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/PoolInterruptedException.java b/src/main/java/edu/vt/middleware/ldap/pool/PoolInterruptedException.java
new file mode 100644
index 0000000..ff4c4e8
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/PoolInterruptedException.java
@@ -0,0 +1,65 @@
+/*
+ $Id: PoolInterruptedException.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+/**
+ * <code>PoolInterruptedException</code> is thrown when a pool thread is
+ * unexpectedly interrupted while blocking.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class PoolInterruptedException extends LdapPoolException
+{
+
+ /** serialVersionUID. */
+ private static final long serialVersionUID = 3788775913431470860L;
+
+
+ /**
+ * This creates a new <code>PoolInterruptedException</code> with the supplied
+ * <code>String</code>.
+ *
+ * @param msg <code>String</code>
+ */
+ public PoolInterruptedException(final String msg)
+ {
+ super(msg);
+ }
+
+
+ /**
+ * This creates a new <code>PoolInterruptedException</code> with the supplied
+ * <code>Exception</code>.
+ *
+ * @param e <code>Exception</code>
+ */
+ public PoolInterruptedException(final Exception e)
+ {
+ super(e);
+ }
+
+
+ /**
+ * This creates a new <code>PoolInterruptedException</code> with the supplied
+ * <code>String</code> and <code>Exception</code>.
+ *
+ * @param msg <code>String</code>
+ * @param e <code>Exception</code>
+ */
+ public PoolInterruptedException(final String msg, final Exception e)
+ {
+ super(msg, e);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/PrunePoolTask.java b/src/main/java/edu/vt/middleware/ldap/pool/PrunePoolTask.java
new file mode 100644
index 0000000..cd442b6
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/PrunePoolTask.java
@@ -0,0 +1,73 @@
+/*
+ $Id: PrunePoolTask.java 2790 2013-07-11 17:50:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2790 $
+ Updated: $Date: 2013-07-11 18:50:53 +0100 (Thu, 11 Jul 2013) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import java.util.TimerTask;
+import edu.vt.middleware.ldap.BaseLdap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>PrunePoolTask</code> is a periodic task that removes available ldap
+ * objects from the pool if the objects have been in the pool longer than a
+ * configured expiration time and the pool size is above it's configured
+ * minimum. Task will skip execution if the pool has any active objects.
+ *
+ * @param <T> type of ldap object
+ *
+ * @author Middleware Services
+ * @version $Revision: 2790 $ $Date: 2013-07-11 18:50:53 +0100 (Thu, 11 Jul 2013) $
+ */
+public class PrunePoolTask<T extends BaseLdap> extends TimerTask
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Pool to clean. */
+ private LdapPool<T> pool;
+
+
+ /**
+ * Creates a new task to periodically prune the supplied pool.
+ *
+ * @param lp ldap pool to periodically inspect
+ */
+ public PrunePoolTask(final LdapPool<T> lp)
+ {
+ this.pool = lp;
+ }
+
+
+ /**
+ * This attempts to remove idle objects from a pool. See {@link
+ * LdapPool#prune()}.
+ */
+ public void run()
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Begin prune task for " + this.pool);
+ }
+ try {
+ this.pool.prune();
+ } catch (Exception e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Prune task failed for " + this.pool, e);
+ }
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("End prune task for " + this.pool);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/SharedLdapPool.java b/src/main/java/edu/vt/middleware/ldap/pool/SharedLdapPool.java
new file mode 100644
index 0000000..cfe3076
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/SharedLdapPool.java
@@ -0,0 +1,224 @@
+/*
+ $Id: SharedLdapPool.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import java.util.NoSuchElementException;
+import edu.vt.middleware.ldap.Ldap;
+
+/**
+ * <code>SharedLdapPool</code> implements a pool of ldap objects that has a set
+ * minimum and maximum size. The pool will not grow beyond the maximum size and
+ * when the pool is exhausted, requests for new objects will be serviced by
+ * objects that are already in use. Since {@link edu.vt.middleware.ldap.Ldap} is
+ * a thread safe object this implementation leverages that by sharing ldap
+ * objects among requests. See {@link
+ * javax.naming.ldap.LdapContext#newInstance(Control[])}. This implementation
+ * should be used when you want some control over the maximum number of ldap
+ * connections, but can tolerate some new connections under high load. See
+ * {@link AbstractLdapPool}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class SharedLdapPool extends AbstractLdapPool<Ldap>
+{
+
+
+ /** Creates a new ldap pool using {@link DefaultLdapFactory}. */
+ public SharedLdapPool()
+ {
+ super(new LdapPoolConfig(), new DefaultLdapFactory());
+ }
+
+
+ /**
+ * Creates a new ldap pool with the supplied ldap factory.
+ *
+ * @param lf ldap factory
+ */
+ public SharedLdapPool(final LdapFactory<Ldap> lf)
+ {
+ super(new LdapPoolConfig(), lf);
+ }
+
+
+ /**
+ * Creates a new ldap pool with the supplied ldap config and factory.
+ *
+ * @param lpc ldap pool configuration
+ * @param lf ldap factory
+ */
+ public SharedLdapPool(final LdapPoolConfig lpc, final LdapFactory<Ldap> lf)
+ {
+ super(lpc, lf);
+ }
+
+
+ /** {@inheritDoc} */
+ public Ldap checkOut()
+ throws LdapPoolException
+ {
+ Ldap l = null;
+ boolean create = false;
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "waiting on pool lock for check out " + this.poolLock.getQueueLength());
+ }
+ this.poolLock.lock();
+ try {
+ // if an available object exists, use it
+ // if no available objects and the pool can grow, attempt to create
+ // otherwise the pool is full, return a shared object
+ if (this.active.size() < this.available.size()) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("retrieve available ldap object");
+ }
+ l = this.retrieveAvailable();
+ } else if (this.active.size() < this.poolConfig.getMaxPoolSize()) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("pool can grow, attempt to create ldap object");
+ }
+ create = true;
+ } else {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "pool is full, " +
+ "attempt to retrieve available ldap object");
+ }
+ l = this.retrieveAvailable();
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+
+ if (create) {
+ // previous block determined a creation should occur
+ // block here until create occurs without locking the whole pool
+ // if the pool is already maxed or creates are failing,
+ // return a shared object
+ this.checkOutLock.lock();
+ try {
+ boolean b = true;
+ this.poolLock.lock();
+ try {
+ if (this.available.size() == this.poolConfig.getMaxPoolSize()) {
+ b = false;
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ if (b) {
+ l = this.createAvailableAndActive();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "created new available and active ldap object: " + l);
+ }
+ }
+ } finally {
+ this.checkOutLock.unlock();
+ }
+ if (l == null) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("create failed, retrieve available ldap object");
+ }
+ l = this.retrieveAvailable();
+ }
+ }
+
+ if (l != null) {
+ this.activateAndValidate(l);
+ } else {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Could not service check out request");
+ }
+ throw new LdapPoolExhaustedException(
+ "Pool is empty and object creation failed");
+ }
+
+ return l;
+ }
+
+
+ /**
+ * This attempts to retrieve an ldap object from the available queue. This
+ * pooling implementation guarantees there is always an object available.
+ *
+ * @return ldap object from the pool
+ *
+ * @throws IllegalStateException if an object cannot be removed from the
+ * available queue
+ */
+ protected Ldap retrieveAvailable()
+ {
+ Ldap l = null;
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "waiting on pool lock for retrieve available " +
+ this.poolLock.getQueueLength());
+ }
+ this.poolLock.lock();
+ try {
+ try {
+ final PooledLdap<Ldap> pl = this.available.remove();
+ this.active.add(new PooledLdap<Ldap>(pl.getLdap()));
+ this.available.add(new PooledLdap<Ldap>(pl.getLdap()));
+ l = pl.getLdap();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("retrieved available ldap object: " + l);
+ }
+ } catch (NoSuchElementException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("could not remove ldap object from list", e);
+ }
+ throw new IllegalStateException("Pool is empty", e);
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ return l;
+ }
+
+
+ /** {@inheritDoc} */
+ public void checkIn(final Ldap l)
+ {
+ final boolean valid = this.validateAndPassivate(l);
+ final PooledLdap<Ldap> pl = new PooledLdap<Ldap>(l);
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "waiting on pool lock for check in " + this.poolLock.getQueueLength());
+ }
+ this.poolLock.lock();
+ try {
+ if (this.active.remove(pl)) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("returned active ldap object: " + l);
+ }
+ } else if (this.available.contains(pl)) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("returned available ldap object: " + l);
+ }
+ } else {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("attempt to return unknown ldap object: " + l);
+ }
+ }
+ if (!valid) {
+ this.available.remove(pl);
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/SoftLimitLdapPool.java b/src/main/java/edu/vt/middleware/ldap/pool/SoftLimitLdapPool.java
new file mode 100644
index 0000000..85ec676
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/SoftLimitLdapPool.java
@@ -0,0 +1,135 @@
+/*
+ $Id: SoftLimitLdapPool.java 2241 2012-02-07 20:08:51Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2241 $
+ Updated: $Date: 2012-02-07 20:08:51 +0000 (Tue, 07 Feb 2012) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import java.util.NoSuchElementException;
+import edu.vt.middleware.ldap.Ldap;
+
+/**
+ * <code>SoftLimitLdapPool</code> implements a pool of ldap objects that has a
+ * set minimum and maximum size. The pool will grow beyond it's maximum size as
+ * necessary based on it's current load. Pool size will return to it's minimum
+ * based on the configuration of the prune timer. See {@link
+ * LdapPoolConfig#setPruneTimerPeriod} and {@link
+ * LdapPoolConfig#setExpirationTime}. This implementation should be used when
+ * you have some flexibility in the number of ldap connections that can be
+ * created to handle spikes in load. See {@link AbstractLdapPool}. Note that
+ * this pool will begin blocking if it cannot create new ldap connections.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2241 $ $Date: 2012-02-07 20:08:51 +0000 (Tue, 07 Feb 2012) $
+ */
+public class SoftLimitLdapPool extends BlockingLdapPool
+{
+
+
+ /** Creates a new ldap pool using {@link DefaultLdapFactory}. */
+ public SoftLimitLdapPool()
+ {
+ super(new LdapPoolConfig(), new DefaultLdapFactory());
+ }
+
+
+ /**
+ * Creates a new ldap pool with the supplied ldap factory.
+ *
+ * @param lf ldap factory
+ */
+ public SoftLimitLdapPool(final LdapFactory<Ldap> lf)
+ {
+ super(new LdapPoolConfig(), lf);
+ }
+
+
+ /**
+ * Creates a new ldap pool with the supplied ldap config and factory.
+ *
+ * @param lpc ldap pool configuration
+ * @param lf ldap factory
+ */
+ public SoftLimitLdapPool(final LdapPoolConfig lpc, final LdapFactory<Ldap> lf)
+ {
+ super(lpc, lf);
+ }
+
+
+ /** {@inheritDoc} */
+ public Ldap checkOut()
+ throws LdapPoolException
+ {
+ Ldap l = null;
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace(
+ "waiting on pool lock for check out " + this.poolLock.getQueueLength());
+ }
+ this.poolLock.lock();
+ try {
+ // if an available object exists, use it
+ // if no available objects, attempt to create
+ if (this.available.size() > 0) {
+ try {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("retrieve available ldap object");
+ }
+ l = this.retrieveAvailable();
+ } catch (NoSuchElementException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("could not remove ldap object from list", e);
+ }
+ throw new IllegalStateException("Pool is empty", e);
+ }
+ }
+ } finally {
+ this.poolLock.unlock();
+ }
+
+ if (l == null) {
+ // no object was available, create a new one
+ l = this.createActive();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("created new active ldap object: " + l);
+ }
+ if (l == null) {
+ if (this.available.size() == 0 && this.active.size() == 0) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Could not service check out request");
+ }
+ throw new LdapPoolExhaustedException(
+ "Pool is empty and object creation failed");
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "create failed, block until an object is available");
+ }
+ l = this.blockAvailable();
+ } else {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("created new active ldap object: " + l);
+ }
+ }
+ }
+
+ if (l != null) {
+ this.activateAndValidate(l);
+ } else {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Could not service check out request");
+ }
+ throw new LdapPoolExhaustedException(
+ "Pool is empty and object creation failed");
+ }
+
+ return l;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/pool/ValidatePoolTask.java b/src/main/java/edu/vt/middleware/ldap/pool/ValidatePoolTask.java
new file mode 100644
index 0000000..7591067
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/pool/ValidatePoolTask.java
@@ -0,0 +1,71 @@
+/*
+ $Id: ValidatePoolTask.java 2790 2013-07-11 17:50:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2790 $
+ Updated: $Date: 2013-07-11 18:50:53 +0100 (Thu, 11 Jul 2013) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import java.util.TimerTask;
+import edu.vt.middleware.ldap.BaseLdap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>ValidatePoolTask</code> is a periodic task that checks that every ldap
+ * object in the pool is valid. Objects that don't pass validation are removed.
+ *
+ * @param <T> type of ldap object
+ *
+ * @author Middleware Services
+ * @version $Revision: 2790 $ $Date: 2013-07-11 18:50:53 +0100 (Thu, 11 Jul 2013) $
+ */
+public class ValidatePoolTask<T extends BaseLdap> extends TimerTask
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Pool to clean. */
+ private LdapPool<T> pool;
+
+
+ /**
+ * Creates a new task to periodically validate the supplied pool.
+ *
+ * @param lp ldap pool to periodically validate
+ */
+ public ValidatePoolTask(final LdapPool<T> lp)
+ {
+ this.pool = lp;
+ }
+
+
+ /**
+ * This attempts to validate idle objects in a pool. See {@link
+ * LdapPool#validate()}.
+ */
+ public void run()
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Begin validate task for " + this.pool);
+ }
+ try {
+ this.pool.validate();
+ } catch (Exception e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Validate task failed for " + this.pool, e);
+ }
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("End validate task for " + this.pool);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/props/AbstractPropertyConfig.java b/src/main/java/edu/vt/middleware/ldap/props/AbstractPropertyConfig.java
new file mode 100644
index 0000000..75e6c84
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/props/AbstractPropertyConfig.java
@@ -0,0 +1,143 @@
+/*
+ $Id: AbstractPropertyConfig.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.props;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AbstractPropertyConfig</code> provides a base implementation of <code>
+ * PropertyConfig</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public abstract class AbstractPropertyConfig implements PropertyConfig
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Whether this config has been marked immutable. */
+ private boolean immutable;
+
+
+ /** Make this property config immutable. */
+ public void makeImmutable()
+ {
+ this.immutable = true;
+ }
+
+
+ /**
+ * Verifies if this property config is immutable.
+ *
+ * @throws IllegalStateException if this property config is immutable
+ */
+ public void checkImmutable()
+ {
+ if (this.immutable) {
+ throw new IllegalStateException("Cannot modify immutable object");
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public abstract String getPropertiesDomain();
+
+
+ /** {@inheritDoc} */
+ public abstract void setEnvironmentProperties(
+ final String name,
+ final String value);
+
+
+ /** {@inheritDoc} */
+ public void setEnvironmentProperties(final Properties properties)
+ {
+ if (properties != null) {
+ final Map<String, String> props = new HashMap<String, String>();
+ final Enumeration<?> en = properties.keys();
+ if (en != null) {
+ while (en.hasMoreElements()) {
+ final String name = (String) en.nextElement();
+ final String value = (String) properties.get(name);
+ if (this.hasEnvironmentProperty(name)) {
+ props.put(name, value);
+ } else {
+ this.setEnvironmentProperties(name, value);
+ }
+ }
+ for (Map.Entry<String, String> e : props.entrySet()) {
+ this.setEnvironmentProperties(e.getKey(), e.getValue());
+ }
+ }
+ }
+ }
+
+
+ /**
+ * See {@link #setEnvironmentProperties(String,String)}.
+ *
+ * @param properties <code>Hashtable</code> of environment properties
+ */
+ public void setEnvironmentProperties(
+ final Hashtable<String, String> properties)
+ {
+ if (properties != null) {
+ final Map<String, String> props = new HashMap<String, String>();
+ for (Map.Entry<String, String> e : properties.entrySet()) {
+ if (this.hasEnvironmentProperty(e.getKey())) {
+ props.put(e.getKey(), e.getValue());
+ } else {
+ this.setEnvironmentProperties(e.getKey(), e.getValue());
+ }
+ }
+ for (Map.Entry<String, String> e : props.entrySet()) {
+ this.setEnvironmentProperties(e.getKey(), e.getValue());
+ }
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public abstract boolean hasEnvironmentProperty(final String name);
+
+
+ /**
+ * Verifies that a string is not null or empty.
+ *
+ * @param s to verify
+ * @param allowNull whether null strings are valid
+ *
+ * @throws IllegalArgumentException if the string is null or empty
+ */
+ protected void checkStringInput(final String s, final boolean allowNull)
+ {
+ if (allowNull) {
+ if (s != null && "".equals(s)) {
+ throw new IllegalArgumentException("Input cannot be empty");
+ }
+ } else {
+ if (s == null || "".equals(s)) {
+ throw new IllegalArgumentException("Input cannot be null or empty");
+ }
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/props/AbstractPropertyInvoker.java b/src/main/java/edu/vt/middleware/ldap/props/AbstractPropertyInvoker.java
new file mode 100644
index 0000000..4c38b83
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/props/AbstractPropertyInvoker.java
@@ -0,0 +1,264 @@
+/*
+ $Id: AbstractPropertyInvoker.java 1616 2010-09-21 17:22:27Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1616 $
+ Updated: $Date: 2010-09-21 18:22:27 +0100 (Tue, 21 Sep 2010) $
+*/
+package edu.vt.middleware.ldap.props;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AbstractPropertyInvoker</code> provides methods common to property
+ * invokers.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1616 $ $Date: 2010-09-21 18:22:27 +0100 (Tue, 21 Sep 2010) $
+ */
+public abstract class AbstractPropertyInvoker
+{
+
+ /** Cache of properties. */
+ protected static final Map<String, Map<String, Method[]>> PROPERTIES_CACHE =
+ new HashMap<String, Map<String, Method[]>>();
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Class to invoke methods on. */
+ protected Class<?> clazz;
+
+ /** Map of all properties to their getter and setter methods. */
+ protected Map<String, Method[]> properties;
+
+
+ /**
+ * Initializes the properties map with the supplied class.
+ *
+ * @param c to read methods from
+ * @param domain optional domain that properties are in
+ */
+ protected void initialize(final Class<?> c, final String domain)
+ {
+ final String cacheKey = new StringBuilder(c.getName()).append("@").append(
+ domain).toString();
+ if (PROPERTIES_CACHE.containsKey(cacheKey)) {
+ this.properties = PROPERTIES_CACHE.get(cacheKey);
+ } else {
+ this.properties = new HashMap<String, Method[]>();
+ PROPERTIES_CACHE.put(cacheKey, this.properties);
+ for (Method method : c.getMethods()) {
+ if (
+ method.getName().startsWith("set") &&
+ method.getParameterTypes().length == 1) {
+ final String mName = method.getName().substring(3);
+ final String pName = new StringBuilder(domain).append(
+ mName.substring(0, 1).toLowerCase()).append(
+ mName.substring(1, mName.length())).toString();
+ if (this.properties.containsKey(pName)) {
+ final Method[] m = this.properties.get(pName);
+ m[1] = method;
+ this.properties.put(pName, m);
+ } else {
+ this.properties.put(pName, new Method[] {null, method});
+ }
+ } else if (
+ method.getName().startsWith("get") &&
+ method.getParameterTypes().length == 0) {
+ final String mName = method.getName().substring(3);
+ final String pName = new StringBuilder(domain).append(
+ mName.substring(0, 1).toLowerCase()).append(
+ mName.substring(1, mName.length())).toString();
+ if (this.properties.containsKey(pName)) {
+ final Method[] m = this.properties.get(pName);
+ m[0] = method;
+ this.properties.put(pName, m);
+ } else {
+ this.properties.put(pName, new Method[] {method, null});
+ }
+ } else if (
+ "initialize".equals(method.getName()) &&
+ method.getParameterTypes().length == 0) {
+ final String pName = new StringBuilder(domain).append(
+ method.getName()).toString();
+ this.properties.put(pName, new Method[] {method, method});
+ }
+ }
+ }
+ this.clazz = c;
+ }
+
+
+ /**
+ * This invokes the setter method for the supplied property name with the
+ * supplied value. If name or value is null, then this method does nothing.
+ *
+ * @param object <code>Object</code> to invoke method on
+ * @param name <code>String</code> property name
+ * @param value <code>String</code> property value
+ *
+ * @throws IllegalArgumentException if an invocation exception occurs
+ */
+ public void setProperty(
+ final Object object,
+ final String name,
+ final String value)
+ {
+ if (!this.clazz.isInstance(object)) {
+ throw new IllegalArgumentException(
+ "Illegal attempt to set property for class " + this.clazz.getName() +
+ " on object of type " + object.getClass().getName());
+ }
+
+ final Method getter = this.properties.get(name) != null
+ ? this.properties.get(name)[0] : null;
+ if (getter == null) {
+ throw new IllegalArgumentException(
+ "No getter method found for " + name + " on object " +
+ this.clazz.getName());
+ }
+
+ final Method setter = this.properties.get(name) != null
+ ? this.properties.get(name)[1] : null;
+ if (setter == null) {
+ throw new IllegalArgumentException(
+ "No setter method found for " + name + " on object " +
+ this.clazz.getName());
+ }
+
+ invokeMethod(
+ setter,
+ object,
+ this.convertValue(getter.getReturnType(), value));
+ }
+
+
+ /**
+ * This converts the supplied string value into an Object of the appropriate
+ * supplied type. If value cannot be converted it is returned as is.
+ *
+ * @param type of object to convert value into
+ * @param value to parse
+ *
+ * @return object of the supplied type
+ */
+ protected abstract Object convertValue(
+ final Class<?> type,
+ final String value);
+
+
+ /**
+ * This returns whether the supplied property exists.
+ *
+ * @param name <code>String</code> to check
+ *
+ * @return <code>boolean</code> whether the supplied property exists
+ */
+ public boolean hasProperty(final String name)
+ {
+ return this.properties.containsKey(name);
+ }
+
+
+ /**
+ * This returns the property keys.
+ *
+ * @return <code>Set</code> of property names
+ */
+ public Set<String> getProperties()
+ {
+ return Collections.unmodifiableSet(this.properties.keySet());
+ }
+
+
+ /**
+ * Creates an instance of the supplied type.
+ *
+ * @param <T> type of class returned
+ * @param type of class to create
+ * @param className to create
+ *
+ * @return class of type T
+ *
+ * @throws IllegalArgumentException if the supplied class name cannot create
+ * a new instance of T
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T instantiateType(final T type, final String className)
+ {
+ try {
+ return (T) createClass(className).newInstance();
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException(e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+
+ /**
+ * Creates the class with the supplied name.
+ *
+ * @param className to create
+ *
+ * @return class
+ *
+ * @throws IllegalArgumentException if the supplied class name cannot be
+ * created
+ */
+ public static Class<?> createClass(final String className)
+ {
+ try {
+ return Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException(
+ "Could not find class '" + className + "'",
+ e);
+ }
+ }
+
+
+ /**
+ * Invokes the supplied method on the supplied object with the supplied
+ * argument.
+ *
+ * @param method <code>Method</code> to invoke
+ * @param object <code>Object</code> to invoke method on
+ * @param arg <code>Object</code> to invoke method with
+ *
+ * @return <code>Object</code> produced by the invocation
+ *
+ * @throws IllegalArgumentException if an error occurs invoking the method
+ */
+ public static Object invokeMethod(
+ final Method method,
+ final Object object,
+ final Object arg)
+ {
+ try {
+ Object[] params = new Object[] {arg};
+ if (arg == null && method.getParameterTypes().length == 0) {
+ params = (Object[]) null;
+ }
+ return method.invoke(object, params);
+ } catch (InvocationTargetException e) {
+ throw new IllegalArgumentException(e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/props/ConfigParser.java b/src/main/java/edu/vt/middleware/ldap/props/ConfigParser.java
new file mode 100644
index 0000000..cba74c2
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/props/ConfigParser.java
@@ -0,0 +1,143 @@
+/*
+ $Id: ConfigParser.java 1501 2010-08-18 18:48:01Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1501 $
+ Updated: $Date: 2010-08-18 19:48:01 +0100 (Wed, 18 Aug 2010) $
+*/
+package edu.vt.middleware.ldap.props;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses the configuration data associated with classes that contain setter
+ * properties. The format of the property string should be like:
+ *
+ * <pre>
+ MyClass{{propertyOne=foo}{propertyTwo=bar}}
+ * </pre>
+ *
+ * @author Middleware Services
+ * @version $Revision: 1501 $ $Date: 2010-08-18 19:48:01 +0100 (Wed, 18 Aug 2010) $
+ */
+public class ConfigParser
+{
+
+ /** Property string containing configuration. */
+ private static final Pattern CONFIG_PATTERN = Pattern.compile(
+ "([^\\{]+)\\s*\\{(.*)\\}\\s*");
+
+ /** Pattern for finding properties. */
+ private static final Pattern PROPERTY_PATTERN = Pattern.compile(
+ "([^\\}\\{])+");
+
+ /** Class found in the config. */
+ private String className;
+
+ /** Properties found in the config to set on the class. */
+ private Map<String, String> properties = new HashMap<String, String>();
+
+
+ /**
+ * Creates a new <code>ConfigParser</code> with the supplied configuration
+ * string.
+ *
+ * @param config <code>String</code>
+ */
+ public ConfigParser(final String config)
+ {
+ final Matcher matcher = CONFIG_PATTERN.matcher(config);
+ if (matcher.matches()) {
+ this.className = matcher.group(1).trim();
+
+ final String props = matcher.group(2).trim();
+ final Matcher m = PROPERTY_PATTERN.matcher(props);
+ while (m.find()) {
+ final String input = m.group().trim();
+ if (input != null && !"".equals(input)) {
+ final String[] s = input.split("=");
+ this.properties.put(s[0].trim(), s[1].trim());
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Returns the class name from the configuration.
+ *
+ * @return <code>String</code> class name
+ */
+ public String getClassName()
+ {
+ return this.className;
+ }
+
+
+ /**
+ * Returns the properties from the configuration.
+ *
+ * @return <code>Map</code> of property name to value
+ */
+ public Map<String, String> getProperties()
+ {
+ return this.properties;
+ }
+
+
+ /**
+ * Returns whether the supplied configuration data contains a config.
+ *
+ * @param config <code>String</code>
+ *
+ * @return <code>boolean</code>
+ */
+ public static boolean isConfig(final String config)
+ {
+ return CONFIG_PATTERN.matcher(config).matches();
+ }
+
+
+ /**
+ * Initialize an instance of the class type with the properties contained in
+ * this config.
+ *
+ * @return <code>Object</code> of the type the config parsed
+ */
+ public Object initializeType()
+ {
+ final Class<?> c = SimplePropertyInvoker.createClass(this.getClassName());
+ final Object o = SimplePropertyInvoker.instantiateType(
+ c,
+ this.getClassName());
+ this.setProperties(c, o);
+ return o;
+ }
+
+
+ /**
+ * Sets the properties on the supplied object.
+ *
+ * @param c <code>Class</code> type of the supplied object
+ * @param o <code>Object</code> to invoke properties on
+ */
+ protected void setProperties(final Class<?> c, final Object o)
+ {
+ final SimplePropertyInvoker invoker = new SimplePropertyInvoker(c);
+ for (Map.Entry<String, String> entry : this.getProperties().entrySet()) {
+ invoker.setProperty(o, entry.getKey(), entry.getValue());
+ }
+ if (invoker.getProperties().contains("initialize")) {
+ invoker.setProperty(o, "initialize", null);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/props/LdapConfigPropertyInvoker.java b/src/main/java/edu/vt/middleware/ldap/props/LdapConfigPropertyInvoker.java
new file mode 100644
index 0000000..a7284ee
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/props/LdapConfigPropertyInvoker.java
@@ -0,0 +1,239 @@
+/*
+ $Id: LdapConfigPropertyInvoker.java 1498 2010-08-18 14:21:37Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1498 $
+ Updated: $Date: 2010-08-18 15:21:37 +0100 (Wed, 18 Aug 2010) $
+*/
+package edu.vt.middleware.ldap.props;
+
+import java.lang.reflect.Array;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+import edu.vt.middleware.ldap.auth.DnResolver;
+import edu.vt.middleware.ldap.auth.handler.AuthenticationHandler;
+import edu.vt.middleware.ldap.auth.handler.AuthenticationResultHandler;
+import edu.vt.middleware.ldap.auth.handler.AuthorizationHandler;
+import edu.vt.middleware.ldap.handler.ConnectionHandler;
+import edu.vt.middleware.ldap.handler.SearchResultHandler;
+import edu.vt.middleware.ldap.ssl.CredentialConfigParser;
+import edu.vt.middleware.ldap.ssl.SSLContextInitializer;
+
+/**
+ * <code>PropertyInvoker</code> stores setter methods for a class to make method
+ * invocation by property easier.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1498 $ $Date: 2010-08-18 15:21:37 +0100 (Wed, 18 Aug 2010) $
+ */
+public class LdapConfigPropertyInvoker extends AbstractPropertyInvoker
+{
+
+
+ /**
+ * Creates a new <code>PropertyInvoker</code> for the supplied class.
+ *
+ * @param c <code>Class</code> that has setter methods
+ * @param propertiesDomain <code>String</code> to prepend to each setter
+ * name
+ */
+ public LdapConfigPropertyInvoker(
+ final Class<?> c,
+ final String propertiesDomain)
+ {
+ this.initialize(c, propertiesDomain);
+ }
+
+
+ /** {@inheritDoc} */
+ protected Object convertValue(final Class<?> type, final String value)
+ {
+ Object newValue = value;
+ if (type != String.class) {
+ if (SSLSocketFactory.class.isAssignableFrom(type)) {
+ if ("null".equals(value)) {
+ newValue = null;
+ } else {
+ // use a credential reader to configure key/trust material
+ if (CredentialConfigParser.isCredentialConfig(value)) {
+ final CredentialConfigParser configParser =
+ new CredentialConfigParser(value);
+ newValue = instantiateType(
+ SSLSocketFactory.class,
+ configParser.getSslSocketFactoryClassName());
+
+ final Object credentialConfig = configParser.initializeType();
+ try {
+ // set the SSL context initializer using the credential config
+ invokeMethod(
+ newValue.getClass().getMethod(
+ "setSSLContextInitializer",
+ SSLContextInitializer.class),
+ newValue,
+ invokeMethod(
+ credentialConfig.getClass().getMethod(
+ "createSSLContextInitializer",
+ new Class<?>[0]),
+ credentialConfig,
+ null));
+ // initialize the TLS socket factory.
+ invokeMethod(
+ newValue.getClass().getMethod("initialize", new Class<?>[0]),
+ newValue,
+ null);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException(e);
+ }
+ // use a standard config to initialize the socket factory
+ } else if (ConfigParser.isConfig(value)) {
+ final ConfigParser configParser = new ConfigParser(value);
+ newValue = configParser.initializeType();
+ } else {
+ newValue = instantiateType(SSLSocketFactory.class, value);
+ }
+ }
+ } else if (HostnameVerifier.class.isAssignableFrom(type)) {
+ newValue = this.createTypeFromPropertyValue(
+ HostnameVerifier.class,
+ value);
+ } else if (ConnectionHandler.class.isAssignableFrom(type)) {
+ newValue = this.createTypeFromPropertyValue(
+ ConnectionHandler.class,
+ value);
+ } else if (AuthenticationHandler.class.isAssignableFrom(type)) {
+ newValue = this.createTypeFromPropertyValue(
+ AuthenticationHandler.class,
+ value);
+ } else if (DnResolver.class.isAssignableFrom(type)) {
+ newValue = this.createTypeFromPropertyValue(DnResolver.class, value);
+ } else if (SearchResultHandler[].class.isAssignableFrom(type)) {
+ newValue = this.createArrayTypeFromPropertyValue(
+ SearchResultHandler.class,
+ value);
+ } else if (AuthenticationResultHandler[].class.isAssignableFrom(type)) {
+ newValue = this.createArrayTypeFromPropertyValue(
+ AuthenticationResultHandler.class,
+ value);
+ } else if (AuthorizationHandler[].class.isAssignableFrom(type)) {
+ newValue = this.createArrayTypeFromPropertyValue(
+ AuthorizationHandler.class,
+ value);
+ } else if (Class.class.isAssignableFrom(type)) {
+ newValue = this.createTypeFromPropertyValue(Class.class, value);
+ } else if (Class[].class.isAssignableFrom(type)) {
+ newValue = this.createArrayTypeFromPropertyValue(Class.class, value);
+ } else if (type.isEnum()) {
+ for (Object o : type.getEnumConstants()) {
+ final Enum<?> e = (Enum<?>) o;
+ if (e.name().equals(value)) {
+ newValue = o;
+ }
+ }
+ } else if (String[].class == type) {
+ newValue = value.split(",");
+ } else if (Object[].class == type) {
+ newValue = value.split(",");
+ } else if (float.class == type) {
+ newValue = Float.parseFloat(value);
+ } else if (int.class == type) {
+ newValue = Integer.parseInt(value);
+ } else if (long.class == type) {
+ newValue = Long.parseLong(value);
+ } else if (short.class == type) {
+ newValue = Short.parseShort(value);
+ } else if (double.class == type) {
+ newValue = Double.parseDouble(value);
+ } else if (boolean.class == type) {
+ newValue = Boolean.valueOf(value);
+ }
+ }
+ return newValue;
+ }
+
+
+ /**
+ * Returns the object which represents the supplied class given the supplied
+ * string representation.
+ *
+ * @param c <code>Class</code> type to instantiate
+ * @param s <code>String</code> to parse
+ *
+ * @return <code>Object</code> of the supplied type or null
+ */
+ protected Object createTypeFromPropertyValue(final Class<?> c, final String s)
+ {
+ Object newObject = null;
+ if ("null".equals(s)) {
+ newObject = null;
+ } else {
+ if (ConfigParser.isConfig(s)) {
+ final ConfigParser configParser = new ConfigParser(s);
+ newObject = configParser.initializeType();
+ } else {
+ if (Class.class == c) {
+ newObject = createClass(s);
+ } else {
+ newObject = instantiateType(c, s);
+ }
+ }
+ }
+ return newObject;
+ }
+
+
+ /**
+ * Returns the object which represents an array of the supplied class given
+ * the supplied string representation.
+ *
+ * @param c <code>Class</code> type to instantiate
+ * @param s <code>String</code> to parse
+ *
+ * @return <code>Object</code> that is an array or null
+ */
+ protected Object createArrayTypeFromPropertyValue(
+ final Class<?> c,
+ final String s)
+ {
+ Object newObject = null;
+ if ("null".equals(s)) {
+ newObject = null;
+ } else {
+ if (s.indexOf("},") != -1) {
+ final String[] classes = s.split("\\},");
+ newObject = Array.newInstance(c, classes.length);
+ for (int i = 0; i < classes.length; i++) {
+ classes[i] = classes[i] + "}";
+ if (ConfigParser.isConfig(classes[i])) {
+ final ConfigParser configParser = new ConfigParser(classes[i]);
+ Array.set(newObject, i, configParser.initializeType());
+ } else {
+ throw new IllegalArgumentException(
+ "Could not parse property string: " + classes[i]);
+ }
+ }
+ } else {
+ final String[] classes = s.split(",");
+ newObject = Array.newInstance(c, classes.length);
+ for (int i = 0; i < classes.length; i++) {
+ if (ConfigParser.isConfig(classes[i])) {
+ final ConfigParser configParser = new ConfigParser(classes[i]);
+ Array.set(newObject, i, configParser.initializeType());
+ } else {
+ if (Class.class == c) {
+ Array.set(newObject, i, createClass(classes[i]));
+ } else {
+ Array.set(newObject, i, instantiateType(c, classes[i]));
+ }
+ }
+ }
+ }
+ }
+ return newObject;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/props/LdapProperties.java b/src/main/java/edu/vt/middleware/ldap/props/LdapProperties.java
new file mode 100644
index 0000000..08019d1
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/props/LdapProperties.java
@@ -0,0 +1,188 @@
+/*
+ $Id: LdapProperties.java 1743 2010-11-19 17:00:18Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1743 $
+ Updated: $Date: 2010-11-19 17:00:18 +0000 (Fri, 19 Nov 2010) $
+*/
+package edu.vt.middleware.ldap.props;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>LdapProperties</code> attempts to load the configuration properties
+ * from a properties file in the classpath for a <code>PropertyConfig</code>
+ * object. The default properties file is '/ldap.properties'.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1743 $ $Date: 2010-11-19 17:00:18 +0000 (Fri, 19 Nov 2010) $
+ */
+public final class LdapProperties
+{
+
+ /** Default file to read properties from, value is {@value}. */
+ public static final String PROPERTIES_FILE = "/ldap.properties";
+
+ /** Log for this class. */
+ private final Log logger = LogFactory.getLog(LdapProperties.class);
+
+ /** Class with properties. */
+ private PropertyConfig propertyConfig;
+
+ /** Underlying properties. */
+ private Properties config;
+
+
+ /**
+ * This will create a new <code>LdapProperties</code> for the supplied
+ * properties config.
+ *
+ * @param pc object to set properties for
+ */
+ public LdapProperties(final PropertyConfig pc)
+ {
+ this.propertyConfig = pc;
+ this.config = new Properties();
+ }
+
+
+ /**
+ * This will create a new <code>LdapProperties</code> with the supplied
+ * properties properties config and input stream.
+ *
+ * @param pc object to set properties for
+ * @param is <code>InputStream</code> containing properties
+ */
+ public LdapProperties(final PropertyConfig pc, final InputStream is)
+ {
+ this.propertyConfig = pc;
+ this.useProperties(is);
+ }
+
+
+ /** This will load properties from the default properties file. */
+ public void useDefaultPropertiesFile()
+ {
+ this.useProperties(
+ LdapProperties.class.getResourceAsStream(PROPERTIES_FILE));
+ }
+
+
+ /**
+ * This will load properties from the supplied input stream.
+ *
+ * @param is <code>InputStream</code> containing properties
+ */
+ public void useProperties(final InputStream is)
+ {
+ if (this.config == null) {
+ this.config = loadProperties(is);
+ } else {
+ this.config.putAll(loadProperties(is));
+ }
+ }
+
+
+ /**
+ * This creates a <code>Properties</code> from the supplied input stream.
+ *
+ * @param is <code>InputStream</code>
+ *
+ * @return <code>Properties</code>
+ */
+ private Properties loadProperties(final InputStream is)
+ {
+ final Properties properties = new Properties();
+ if (is != null) {
+ try {
+ properties.load(is);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Loaded ldap properties from input stream");
+ }
+ is.close();
+ } catch (IOException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error using input stream", e);
+ }
+ }
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Input stream was null, no properties loaded");
+ }
+ }
+ return properties;
+ }
+
+
+ /**
+ * This returns the name of the properties being used by this <code>
+ * LdapProperties</code>.
+ *
+ * @return <code>Properties</code>
+ */
+ public Properties getProperties()
+ {
+ return this.config;
+ }
+
+
+ /**
+ * This sets the supplied key and value in the ldap properties. The key will
+ * be prepended with the appropriate namespace.
+ *
+ * @param key <code>String</code>
+ * @param value <code>String</code>
+ */
+ public void setProperty(final String key, final String value)
+ {
+ if (
+ this.propertyConfig.hasEnvironmentProperty(
+ this.propertyConfig.getPropertiesDomain() + key)) {
+ this.config.setProperty(
+ this.propertyConfig.getPropertiesDomain() + key,
+ value);
+ } else {
+ this.config.setProperty(key, value);
+ }
+ }
+
+
+ /**
+ * This returns whether the supplied key has already been set. The key will be
+ * prepended with the appropriate namespace.
+ *
+ * @param key <code>String</code>
+ *
+ * @return <code>boolean</code>
+ */
+ public boolean isPropertySet(final String key)
+ {
+ boolean exists = false;
+ if (
+ this.propertyConfig.hasEnvironmentProperty(
+ this.propertyConfig.getPropertiesDomain() + key)) {
+ exists = this.config.containsKey(
+ this.propertyConfig.getPropertiesDomain() + key);
+ } else {
+ exists = this.config.containsKey(key);
+ }
+ return exists;
+ }
+
+
+ /** Calls {@link PropertyConfig#setEnvironmentProperties(Properties)}. */
+ public void configure()
+ {
+ this.propertyConfig.setEnvironmentProperties(this.config);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/props/PropertyConfig.java b/src/main/java/edu/vt/middleware/ldap/props/PropertyConfig.java
new file mode 100644
index 0000000..c0c7e0e
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/props/PropertyConfig.java
@@ -0,0 +1,72 @@
+/*
+ $Id: PropertyConfig.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.props;
+
+import java.util.Hashtable;
+import java.util.Properties;
+
+/**
+ * <code>PropertyConfig</code> provides an interface for objects that can be
+ * configured with a <code>PropertyInvoker.</code>
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public interface PropertyConfig
+{
+
+
+ /**
+ * This returns the properties domain for this property config.
+ *
+ * @return <code>String</code> properties domain
+ */
+ String getPropertiesDomain();
+
+
+ /**
+ * This returns whether the supplied property exists.
+ *
+ * @param name <code>String</code> to check
+ *
+ * @return <code>boolean</code> whether the supplied property exists
+ */
+ boolean hasEnvironmentProperty(String name);
+
+
+ /**
+ * This adds environment properties to this object. If name or value is null,
+ * then this method does nothing.
+ *
+ * @param name <code>String</code> property name
+ * @param value <code>String</code> property value
+ */
+ void setEnvironmentProperties(String name, String value);
+
+
+ /**
+ * See {@link #setEnvironmentProperties(String,String)}.
+ *
+ * @param properties <code>Properties</code>
+ */
+ void setEnvironmentProperties(Properties properties);
+
+
+ /**
+ * See {@link #setEnvironmentProperties(String,String)}.
+ *
+ * @param properties <code>Hashtable</code>
+ */
+ void setEnvironmentProperties(Hashtable<String, String> properties);
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/props/SimplePropertyInvoker.java b/src/main/java/edu/vt/middleware/ldap/props/SimplePropertyInvoker.java
new file mode 100644
index 0000000..5400398
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/props/SimplePropertyInvoker.java
@@ -0,0 +1,88 @@
+/*
+ $Id: SimplePropertyInvoker.java 1441 2010-07-01 16:55:43Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1441 $
+ Updated: $Date: 2010-07-01 17:55:43 +0100 (Thu, 01 Jul 2010) $
+*/
+package edu.vt.middleware.ldap.props;
+
+import java.lang.reflect.Array;
+
+/**
+ * <code>SimplePropertyInvoker</code> stores setter methods for a class to make
+ * method invocation of simple properties easier.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1441 $ $Date: 2010-07-01 17:55:43 +0100 (Thu, 01 Jul 2010) $
+ */
+public class SimplePropertyInvoker extends AbstractPropertyInvoker
+{
+
+
+ /**
+ * Creates a new <code>SimplePropertyInvoker</code> for the supplied class.
+ *
+ * @param c <code>Class</code> that has setter methods
+ */
+ public SimplePropertyInvoker(final Class<?> c)
+ {
+ this.initialize(c, "");
+ }
+
+
+ /** {@inheritDoc} */
+ protected Object convertValue(final Class<?> type, final String value)
+ {
+ Object newValue = value;
+ if (type != String.class) {
+ if (Class.class.isAssignableFrom(type)) {
+ if ("null".equals(value)) {
+ newValue = null;
+ } else {
+ newValue = createClass(value);
+ }
+ } else if (Class[].class.isAssignableFrom(type)) {
+ if ("null".equals(value)) {
+ newValue = null;
+ } else {
+ final String[] classes = value.split(",");
+ newValue = Array.newInstance(Class.class, classes.length);
+ for (int i = 0; i < classes.length; i++) {
+ Array.set(newValue, i, createClass(classes[i]));
+ }
+ }
+ } else if (type.isEnum()) {
+ for (Object o : type.getEnumConstants()) {
+ final Enum<?> e = (Enum<?>) o;
+ if (e.name().equals(value)) {
+ newValue = o;
+ }
+ }
+ } else if (String[].class == type) {
+ newValue = value.split(",");
+ } else if (Object[].class == type) {
+ newValue = value.split(",");
+ } else if (float.class == type) {
+ newValue = Float.parseFloat(value);
+ } else if (int.class == type) {
+ newValue = Integer.parseInt(value);
+ } else if (long.class == type) {
+ newValue = Long.parseLong(value);
+ } else if (short.class == type) {
+ newValue = Short.parseShort(value);
+ } else if (double.class == type) {
+ newValue = Double.parseDouble(value);
+ } else if (boolean.class == type) {
+ newValue = Boolean.valueOf(value);
+ }
+ }
+ return newValue;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/servlets/AttributeServlet.java b/src/main/java/edu/vt/middleware/ldap/servlets/AttributeServlet.java
new file mode 100644
index 0000000..a49ec45
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/servlets/AttributeServlet.java
@@ -0,0 +1,240 @@
+/*
+ $Id: AttributeServlet.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Iterator;
+import javax.naming.directory.SearchResult;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.LdapConfig;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.bean.LdapAttribute;
+import edu.vt.middleware.ldap.bean.LdapBeanFactory;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import edu.vt.middleware.ldap.pool.BlockingLdapPool;
+import edu.vt.middleware.ldap.pool.DefaultLdapFactory;
+import edu.vt.middleware.ldap.pool.LdapPool;
+import edu.vt.middleware.ldap.pool.LdapPoolConfig;
+import edu.vt.middleware.ldap.pool.SharedLdapPool;
+import edu.vt.middleware.ldap.pool.SoftLimitLdapPool;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>AttributeServlet</code> is a servlet which queries an LDAP and returns
+ * the value of a single attribute. Example:
+ * http://www.server.com/Attribute?query=uid=dfisher&attr=givenName If you need
+ * to pass complex queries, such as (&(cn=daniel*)(surname=fisher)), then the
+ * query must be form encoded. The content returned by the servlet is of type
+ * text/plain, if you want to receive the content as application/octet-stream
+ * that can be specified by passing the content-type=octet param. The following
+ * init params can be set for this servlet:
+ * edu.vt.middleware.ldap.servlets.propertiesFile - to load ldap properties from
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class AttributeServlet extends HttpServlet
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -5918353780927139315L;
+
+ /** Types of available pools. */
+ private enum PoolType {
+
+ /** blocking. */
+ BLOCKING,
+
+ /** soft limit. */
+ SOFTLIMIT,
+
+ /** shared. */
+ SHARED
+ }
+
+ /** Log for this class. */
+ private final Log logger = LogFactory.getLog(AttributeServlet.class);
+
+ /** Ldap bean factory. */
+ private LdapBeanFactory beanFactory = LdapBeanProvider.getLdapBeanFactory();
+
+ /** Pool to use for searching. */
+ private LdapPool<Ldap> pool;
+
+
+ /**
+ * Initialize this servlet.
+ *
+ * @param config <code>ServletConfig</code>
+ *
+ * @throws ServletException if an error occurs
+ */
+ public void init(final ServletConfig config)
+ throws ServletException
+ {
+ super.init(config);
+
+ final String propertiesFile = getInitParameter(
+ ServletConstants.PROPERTIES_FILE);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ ServletConstants.PROPERTIES_FILE + " = " + propertiesFile);
+ }
+
+ final LdapConfig ldapConfig = LdapConfig.createFromProperties(
+ AttributeServlet.class.getResourceAsStream(propertiesFile));
+
+ final String poolPropertiesFile = getInitParameter(
+ ServletConstants.POOL_PROPERTIES_FILE);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ ServletConstants.POOL_PROPERTIES_FILE + " = " + poolPropertiesFile);
+ }
+
+ final LdapPoolConfig ldapPoolConfig = LdapPoolConfig.createFromProperties(
+ AttributeServlet.class.getResourceAsStream(poolPropertiesFile));
+
+ final String poolType = getInitParameter(ServletConstants.POOL_TYPE);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(ServletConstants.POOL_TYPE + " = " + poolType);
+ }
+ if (PoolType.BLOCKING == PoolType.valueOf(poolType)) {
+ this.pool = new BlockingLdapPool(
+ ldapPoolConfig,
+ new DefaultLdapFactory(ldapConfig));
+ } else if (PoolType.SOFTLIMIT == PoolType.valueOf(poolType)) {
+ this.pool = new SoftLimitLdapPool(
+ ldapPoolConfig,
+ new DefaultLdapFactory(ldapConfig));
+ } else if (PoolType.SHARED == PoolType.valueOf(poolType)) {
+ this.pool = new SharedLdapPool(
+ ldapPoolConfig,
+ new DefaultLdapFactory(ldapConfig));
+ } else {
+ throw new ServletException("Unknown pool type: " + poolType);
+ }
+ this.pool.initialize();
+
+ final String beanFactoryClass = getInitParameter(
+ ServletConstants.BEAN_FACTORY);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(ServletConstants.BEAN_FACTORY + " = " + beanFactory);
+ }
+ if (beanFactoryClass != null) {
+ try {
+ this.beanFactory = (LdapBeanFactory) Class.forName(beanFactoryClass)
+ .newInstance();
+ } catch (ClassNotFoundException e) {
+ throw new ServletException(e);
+ } catch (InstantiationException e) {
+ throw new ServletException(e);
+ } catch (IllegalAccessException e) {
+ throw new ServletException(e);
+ }
+ }
+ }
+
+
+ /**
+ * Handle all requests sent to this servlet.
+ *
+ * @param request <code>HttpServletRequest</code>
+ * @param response <code>HttpServletResponse</code>
+ *
+ * @throws ServletException if an error occurs
+ * @throws IOException if an error occurs
+ */
+ public void service(
+ final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ final String attribute = request.getParameter("attr");
+ byte[] value = null;
+ final String content = request.getParameter("content-type");
+
+ if (content != null && content.equalsIgnoreCase("octet")) {
+ response.setContentType("application/octet-stream");
+ response.setHeader(
+ "Content-Disposition",
+ "attachment; filename=\"" + attribute + ".bin\"");
+ } else {
+ response.setContentType("text/plain");
+ }
+
+ try {
+ Ldap ldap = null;
+ try {
+ ldap = this.pool.checkOut();
+
+ final Iterator<SearchResult> i = ldap.search(
+ new SearchFilter(request.getParameter("query")),
+ request.getParameterValues("attr"));
+
+ final LdapResult r = this.beanFactory.newLdapResult();
+ r.addEntries(i);
+ for (LdapEntry e : r.getEntries()) {
+ final LdapAttribute a = e.getLdapAttributes().getAttribute(attribute);
+ if (a != null && a.getValues().size() > 0) {
+ final Object rawValue = a.getValues().iterator().next();
+ if (rawValue instanceof String) {
+ final String stringValue = (String) rawValue;
+ value = stringValue.getBytes();
+ } else {
+ value = (byte[]) rawValue;
+ }
+ }
+ }
+ } finally {
+ this.pool.checkIn(ldap);
+ }
+
+ if (value != null) {
+ final OutputStream out = response.getOutputStream();
+ out.write(value);
+ out.flush();
+ out.close();
+ }
+
+ } catch (Exception e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error performing search", e);
+ }
+ throw new ServletException(e.getMessage());
+ }
+ }
+
+
+ /**
+ * Called by the servlet container to indicate to a servlet that the servlet
+ * is being taken out of service.
+ */
+ public void destroy()
+ {
+ try {
+ this.pool.close();
+ } finally {
+ super.destroy();
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/servlets/CommonServlet.java b/src/main/java/edu/vt/middleware/ldap/servlets/CommonServlet.java
new file mode 100644
index 0000000..c87c850
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/servlets/CommonServlet.java
@@ -0,0 +1,108 @@
+/*
+ $Id: CommonServlet.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import edu.vt.middleware.ldap.servlets.session.SessionManager;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>CommonServlet</code> contains common code that each servlet uses to
+ * initialize itself.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class CommonServlet extends HttpServlet
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -2580419817969949661L;
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Used to manage a session after login and logout. */
+ protected SessionManager sessionManager;
+
+
+ /**
+ * Initialize this servlet.
+ *
+ * @param config <code>ServletConfig</code>
+ *
+ * @throws ServletException if an error occurs
+ */
+ public void init(final ServletConfig config)
+ throws ServletException
+ {
+ super.init(config);
+
+ String sessionManagerClass = getInitParameter(
+ ServletConstants.SESSION_MANAGER);
+ if (sessionManagerClass == null) {
+ sessionManagerClass = ServletConstants.DEFAULT_SESSION_MANAGER;
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ ServletConstants.SESSION_MANAGER + " = " + sessionManagerClass);
+ }
+ try {
+ this.sessionManager = (SessionManager) Class.forName(sessionManagerClass)
+ .newInstance();
+ } catch (ClassNotFoundException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Could not find class " + sessionManagerClass, e);
+ }
+ throw new ServletException(e);
+ } catch (InstantiationException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error(
+ "Could not instantiate class " + sessionManagerClass,
+ e);
+ }
+ throw new ServletException(e);
+ } catch (IllegalAccessException e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Could not access class " + sessionManagerClass, e);
+ }
+ throw new ServletException(e);
+ }
+
+ String sessionId = getInitParameter(ServletConstants.SESSION_ID);
+ if (sessionId == null) {
+ sessionId = ServletConstants.DEFAULT_SESSION_ID;
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(ServletConstants.SESSION_ID + " = " + sessionId);
+ }
+ this.sessionManager.setSessionId(sessionId);
+
+
+ String invalidateSession = getInitParameter(
+ ServletConstants.INVALIDATE_SESSION);
+ if (invalidateSession == null) {
+ invalidateSession = ServletConstants.DEFAULT_INVALIDATE_SESSION;
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ ServletConstants.INVALIDATE_SESSION + " = " + invalidateSession);
+ }
+ this.sessionManager.setInvalidateSession(
+ Boolean.valueOf(invalidateSession).booleanValue());
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/servlets/LoginServlet.java b/src/main/java/edu/vt/middleware/ldap/servlets/LoginServlet.java
new file mode 100644
index 0000000..50e67cc
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/servlets/LoginServlet.java
@@ -0,0 +1,215 @@
+/*
+ $Id: LoginServlet.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import edu.vt.middleware.ldap.auth.Authenticator;
+import edu.vt.middleware.ldap.props.LdapProperties;
+
+/**
+ * <code>LoginServet</code> attempts to authenticate a user against an LDAP. The
+ * following init params can be set for this servlet:
+ * edu.vt.middleware.ldap.servlets.propertiesFile - to load authenticator
+ * properties from edu.vt.middleware.ldap.servlets.sessionId - to set the user
+ * identifier in the session edu.vt.middleware.ldap.servlets.loginUrl - to set
+ * the URL of your login page edu.vt.middleware.ldap.servlets.errorMsg - to
+ * display if authentication fails
+ * edu.vt.middleware.ldap.servlets.sessionManager - optional class to perform
+ * session management after login and logout (must extend
+ * edu.vt.middleware.ldap.servlets.session.SessionManager)
+ *
+ * <p>The following http params can be sent to this servlet: user - user
+ * identifier to authenticate credential - user credential to authenticate with
+ * url - to redirect client to after successful authentication</p>
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class LoginServlet extends CommonServlet
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -3482852409544351134L;
+
+ /** URL of the page that does collects user credentials. */
+ private String loginUrl;
+
+ /** Message to display if authentication fails. */
+ private String errorMsg;
+
+ /** Used to authenticate against an LDAP. */
+ private Authenticator auth;
+
+
+ /**
+ * Initialize this servlet.
+ *
+ * @param config <code>ServletConfig</code>
+ *
+ * @throws ServletException if an error occurs
+ */
+ public void init(final ServletConfig config)
+ throws ServletException
+ {
+ super.init(config);
+ this.loginUrl = getInitParameter(ServletConstants.LOGIN_URL);
+ if (this.loginUrl == null) {
+ this.loginUrl = ServletConstants.DEFAULT_LOGIN_URL;
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(ServletConstants.LOGIN_URL + " = " + this.loginUrl);
+ }
+ this.errorMsg = getInitParameter(ServletConstants.ERROR_MSG);
+ if (this.errorMsg == null) {
+ this.errorMsg = ServletConstants.DEFAULT_ERROR_MSG;
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(ServletConstants.ERROR_MSG + " = " + this.errorMsg);
+ }
+
+ String propertiesFile = getInitParameter(ServletConstants.PROPERTIES_FILE);
+ if (propertiesFile == null) {
+ propertiesFile = LdapProperties.PROPERTIES_FILE;
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ ServletConstants.PROPERTIES_FILE + " = " + propertiesFile);
+ }
+ this.auth = new Authenticator();
+ this.auth.loadFromProperties(
+ LoginServlet.class.getResourceAsStream(propertiesFile));
+ }
+
+
+ /**
+ * Handle all requests sent to this servlet.
+ *
+ * @param request <code>HttpServletRequest</code>
+ * @param response <code>HttpServletResponse</code>
+ *
+ * @throws ServletException if this request cannot be serviced
+ * @throws IOException if a response cannot be sent
+ */
+ public void service(
+ final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ boolean validCredentials = false;
+ String user = request.getParameter(ServletConstants.USER_PARAM);
+ if (user != null) {
+ user = user.trim().toLowerCase();
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Received user param = " + user);
+ }
+
+ final String credential = request.getParameter(
+ ServletConstants.CREDENTIAL_PARAM);
+ String url = request.getParameter(ServletConstants.URL_PARAM);
+ if (url == null) {
+ url = "";
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Received url param = " + url);
+ }
+
+ final StringBuffer error = new StringBuffer(this.errorMsg);
+
+ try {
+ if (this.auth.authenticate(user, credential)) {
+ validCredentials = true;
+ }
+ } catch (Exception e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error authenticating user " + user, e);
+ }
+ if (
+ e.getCause() != null &&
+ e.getCause().getMessage() != null &&
+ !"null".equals(e.getCause().getMessage())) {
+ error.append(": ").append(e.getCause().getMessage());
+ } else if (e.getMessage() != null && !"null".equals(e.getMessage())) {
+ error.append(": ").append(e.getMessage());
+ }
+ }
+
+ if (validCredentials) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Authentication succeeded for user " + user);
+ }
+ try {
+ // invalidate existing session
+ HttpSession session = request.getSession(false);
+ if (session != null) {
+ session.invalidate();
+ }
+ session = request.getSession(true);
+ this.sessionManager.login(session, user);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Initialized session for user " + user);
+ }
+ response.sendRedirect(url);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Redirected user to " + url);
+ }
+ return;
+ } catch (Exception e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error authorizing user " + user, e);
+ }
+ if (
+ e.getCause() != null &&
+ e.getCause().getMessage() != null &&
+ !"null".equals(e.getCause().getMessage())) {
+ error.append(": ").append(e.getCause().getMessage());
+ } else if (e.getMessage() != null && !"null".equals(e.getMessage())) {
+ error.append(": ").append(e.getMessage());
+ }
+ }
+ }
+
+ final StringBuffer errorUrl = new StringBuffer(this.loginUrl);
+ if (error != null) {
+ errorUrl.append("?error=").append(
+ URLEncoder.encode(error.toString(), "UTF-8"));
+ }
+ if (user != null) {
+ errorUrl.append("&user=").append(URLEncoder.encode(user, "UTF-8"));
+ }
+ if (url != null) {
+ errorUrl.append("&url=").append(URLEncoder.encode(url, "UTF-8"));
+ }
+ response.sendRedirect(errorUrl.toString());
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Redirected user to " + errorUrl.toString());
+ }
+ }
+
+
+ /**
+ * Called by the servlet container to indicate to a servlet that the servlet
+ * is being taken out of service.
+ */
+ public void destroy()
+ {
+ super.destroy();
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/servlets/LogoutServlet.java b/src/main/java/edu/vt/middleware/ldap/servlets/LogoutServlet.java
new file mode 100644
index 0000000..b9de933
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/servlets/LogoutServlet.java
@@ -0,0 +1,92 @@
+/*
+ $Id: LogoutServlet.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets;
+
+import java.io.IOException;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * <code>LogoutServet</code> removes the session id attribute set by the <code>
+ * LoginServlet</code>. The following init params can be set for this servlet:
+ * edu.vt.middleware.ldap.servlets.sessionId - to remove from the session
+ *
+ * <p>The following http params can be sent to this servlet: url - to redirect
+ * client to after logout</p>
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class LogoutServlet extends CommonServlet
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = -6521700995773675507L;
+
+
+ /**
+ * Initialize this servlet.
+ *
+ * @param config <code>ServletConfig</code>
+ *
+ * @throws ServletException if an error occurs
+ */
+ public void init(final ServletConfig config)
+ throws ServletException
+ {
+ super.init(config);
+ }
+
+
+ /**
+ * Handle all requests sent to this servlet.
+ *
+ * @param request <code>HttpServletRequest</code>
+ * @param response <code>HttpServletResponse</code>
+ *
+ * @throws ServletException if this request cannot be serviced
+ * @throws IOException if a response cannot be sent
+ */
+ public void service(
+ final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ String url = request.getParameter(ServletConstants.URL_PARAM);
+ if (url == null) {
+ url = "";
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Received url param = " + url);
+ }
+
+ this.sessionManager.logout(request.getSession(true));
+ response.sendRedirect(url);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Redirected user to " + url);
+ }
+ }
+
+
+ /**
+ * Called by the servlet container to indicate to a servlet that the servlet
+ * is being taken out of service.
+ */
+ public void destroy()
+ {
+ super.destroy();
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/servlets/SearchServlet.java b/src/main/java/edu/vt/middleware/ldap/servlets/SearchServlet.java
new file mode 100644
index 0000000..7c98d47
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/servlets/SearchServlet.java
@@ -0,0 +1,254 @@
+/*
+ $Id: SearchServlet.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.LdapConfig;
+import edu.vt.middleware.ldap.dsml.DsmlSearch;
+import edu.vt.middleware.ldap.ldif.LdifSearch;
+import edu.vt.middleware.ldap.pool.BlockingLdapPool;
+import edu.vt.middleware.ldap.pool.DefaultLdapFactory;
+import edu.vt.middleware.ldap.pool.LdapPool;
+import edu.vt.middleware.ldap.pool.LdapPoolConfig;
+import edu.vt.middleware.ldap.pool.SharedLdapPool;
+import edu.vt.middleware.ldap.pool.SoftLimitLdapPool;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>SearchServlet</code> is a servlet which queries an LDAP and returns the
+ * result as LDIF or DSML. The following init params can be set for this
+ * servlet: edu.vt.middleware.ldap.servlets.propertiesFile - to load ldap
+ * properties from edu.vt.middleware.ldap.servlets.outputFormat - type of output
+ * to produce, 'ldif' or 'dsml' Example:
+ * http://www.server.com/Search?query=uid=dfisher If you need to pass complex
+ * queries, such as (&(cn=daniel*)(surname=fisher)), then the query must be form
+ * encoded. If you only want to receive a subset of attributes those can be
+ * specified. Example:
+ * http://www.server.com/Search?query=uid=dfisher&attrs=givenname&attrs=surname
+ *
+ * <h3>LDIF</h3>
+ *
+ * <p>The content returned by the servlet is of type text/plain.</p>
+ * <hr/>
+ * <h3>DSML</h3>
+ *
+ * <p>The content returned by the servlet is of type text/xml, if you want to
+ * receive the content as text/plain that can be specified as well. Example:
+ * http://www.server.com/Search?query=uid=dfisher&content-type=text By default
+ * DSML version 1 is returned, if you want to receive DSML version 2 then you
+ * must pass in the dsml-version parameter. Example:
+ * http://www.server.com/Search?query=uid=dfisher&dsml-version=2</p>
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class SearchServlet extends HttpServlet
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = 1731614499970954068L;
+
+ /** Types of available pools. */
+ private enum PoolType {
+
+ /** blocking. */
+ BLOCKING,
+
+ /** soft limit. */
+ SOFTLIMIT,
+
+ /** shared. */
+ SHARED
+ }
+
+ /** Types of available output. */
+ private enum OutputType {
+
+ /** LDIF output type. */
+ LDIF,
+
+ /** DSML output type. */
+ DSML
+ }
+
+ /** Log for this class. */
+ private final Log logger = LogFactory.getLog(SearchServlet.class);
+
+ /** Type of output to produce. */
+ private OutputType output;
+
+ /** Object to use for searching. */
+ private LdifSearch ldifSearch;
+
+ /** Object to use for searching. */
+ private DsmlSearch dsmlv1Search;
+
+ /** Object to use for searching. */
+ private DsmlSearch dsmlv2Search;
+
+
+ /**
+ * Initialize this servlet.
+ *
+ * @param config <code>ServletConfig</code>
+ *
+ * @throws ServletException if an error occurs
+ */
+ public void init(final ServletConfig config)
+ throws ServletException
+ {
+ super.init(config);
+
+ final String propertiesFile = getInitParameter(
+ ServletConstants.PROPERTIES_FILE);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ ServletConstants.PROPERTIES_FILE + " = " + propertiesFile);
+ }
+
+ final LdapConfig ldapConfig = LdapConfig.createFromProperties(
+ SearchServlet.class.getResourceAsStream(propertiesFile));
+
+ final String poolPropertiesFile = getInitParameter(
+ ServletConstants.POOL_PROPERTIES_FILE);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ ServletConstants.POOL_PROPERTIES_FILE + " = " + poolPropertiesFile);
+ }
+
+ final LdapPoolConfig ldapPoolConfig = LdapPoolConfig.createFromProperties(
+ SearchServlet.class.getResourceAsStream(poolPropertiesFile));
+
+ LdapPool<Ldap> ldapPool = null;
+ final String poolType = getInitParameter(ServletConstants.POOL_TYPE);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(ServletConstants.POOL_TYPE + " = " + poolType);
+ }
+ if (PoolType.BLOCKING == PoolType.valueOf(poolType)) {
+ ldapPool = new BlockingLdapPool(
+ ldapPoolConfig,
+ new DefaultLdapFactory(ldapConfig));
+ } else if (PoolType.SOFTLIMIT == PoolType.valueOf(poolType)) {
+ ldapPool = new SoftLimitLdapPool(
+ ldapPoolConfig,
+ new DefaultLdapFactory(ldapConfig));
+ } else if (PoolType.SHARED == PoolType.valueOf(poolType)) {
+ ldapPool = new SharedLdapPool(
+ ldapPoolConfig,
+ new DefaultLdapFactory(ldapConfig));
+ } else {
+ throw new ServletException("Unknown pool type: " + poolType);
+ }
+ ldapPool.initialize();
+
+ this.ldifSearch = new LdifSearch(ldapPool);
+ this.dsmlv1Search = new DsmlSearch(ldapPool);
+ this.dsmlv1Search.setVersion(DsmlSearch.Version.ONE);
+ this.dsmlv2Search = new DsmlSearch(ldapPool);
+ this.dsmlv2Search.setVersion(DsmlSearch.Version.TWO);
+
+ String outputType = getInitParameter(ServletConstants.OUTPUT_FORMAT);
+ if (outputType == null) {
+ outputType = ServletConstants.DEFAULT_OUTPUT_FORMAT;
+ }
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(ServletConstants.OUTPUT_FORMAT + " = " + outputType);
+ }
+ this.output = OutputType.valueOf(outputType);
+ }
+
+
+ /**
+ * Handle all requests sent to this servlet.
+ *
+ * @param request <code>HttpServletRequest</code>
+ * @param response <code>HttpServletResponse</code>
+ *
+ * @throws ServletException if an error occurs
+ * @throws IOException if an error occurs
+ */
+ public void service(
+ final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info(
+ "Performing search: " + request.getParameter("query") +
+ " for attributes: " + request.getParameter("attrs"));
+ }
+ try {
+ if (this.output == OutputType.LDIF) {
+ response.setContentType("text/plain");
+ this.ldifSearch.search(
+ request.getParameter("query"),
+ request.getParameterValues("attrs"),
+ new BufferedWriter(
+ new OutputStreamWriter(response.getOutputStream())));
+ } else {
+ final String content = request.getParameter("content-type");
+ if (content != null && content.equalsIgnoreCase("text")) {
+ response.setContentType("text/plain");
+ } else {
+ response.setContentType("text/xml");
+ }
+
+ final String dsmlVersion = request.getParameter("dsml-version");
+ if ("2".equals(dsmlVersion)) {
+ this.dsmlv2Search.search(
+ request.getParameter("query"),
+ request.getParameterValues("attrs"),
+ new BufferedWriter(
+ new OutputStreamWriter(response.getOutputStream())));
+ } else {
+ this.dsmlv1Search.search(
+ request.getParameter("query"),
+ request.getParameterValues("attrs"),
+ new BufferedWriter(
+ new OutputStreamWriter(response.getOutputStream())));
+ }
+ }
+ } catch (Exception e) {
+ if (this.logger.isErrorEnabled()) {
+ this.logger.error("Error performing search", e);
+ }
+ throw new ServletException(e);
+ }
+ }
+
+
+ /**
+ * Called by the servlet container to indicate to a servlet that the servlet
+ * is being taken out of service.
+ */
+ public void destroy()
+ {
+ try {
+ // all search instances share the same pool
+ // only need to close one of them
+ this.ldifSearch.close();
+ } finally {
+ super.destroy();
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/servlets/ServletConstants.java b/src/main/java/edu/vt/middleware/ldap/servlets/ServletConstants.java
new file mode 100644
index 0000000..fcc64b9
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/servlets/ServletConstants.java
@@ -0,0 +1,108 @@
+/*
+ $Id: ServletConstants.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets;
+
+/**
+ * <code>ServletConstants</code> contains all the constants needed by the ldap
+ * servlet package.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public final class ServletConstants
+{
+
+ /** Domain to look for properties in, value is {@value}. */
+ public static final String PROPERTIES_DOMAIN =
+ "edu.vt.middleware.ldap.servlets.";
+
+ /** LDAP initialization properties file, value is {@value}. */
+ public static final String PROPERTIES_FILE = PROPERTIES_DOMAIN +
+ "propertiesFile";
+
+ /** LDAP pool initialization properties file, value is {@value}. */
+ public static final String POOL_PROPERTIES_FILE = PROPERTIES_DOMAIN +
+ "poolPropertiesFile";
+
+ /** Format of search output, value is {@value}. */
+ public static final String OUTPUT_FORMAT = PROPERTIES_DOMAIN + "outputFormat";
+
+ /** Default format of search output, value is {@value}. */
+ public static final String DEFAULT_OUTPUT_FORMAT = "DSML";
+
+ /** Type of pool used, value is {@value}. */
+ public static final String POOL_TYPE = PROPERTIES_DOMAIN + "poolType";
+
+ /** Type of ldap bean factory, value is {@value}. */
+ public static final String BEAN_FACTORY = PROPERTIES_DOMAIN + "beanFactory";
+
+ /**
+ * Identifier to set in the session after valid authentication, value is
+ * {@value}.
+ */
+ public static final String SESSION_ID = PROPERTIES_DOMAIN + "sessionId";
+
+ /**
+ * Default identifier to set in the session after valid authentication, value
+ * is {@value}.
+ */
+ public static final String DEFAULT_SESSION_ID = "user";
+
+ /** Whether to invalidate the user session at logout, value is {@value}. */
+ public static final String INVALIDATE_SESSION = PROPERTIES_DOMAIN +
+ "invalidateSession";
+
+ /**
+ * Default behavior for invalidating the user session at logout, value is
+ * {@value}.
+ */
+ public static final String DEFAULT_INVALIDATE_SESSION = "true";
+
+ /** URL of the page that collects user credentials, value is {@value}. */
+ public static final String LOGIN_URL = PROPERTIES_DOMAIN + "loginUrl";
+
+ /**
+ * Default URL of the page that does collects user credentials, value is
+ * {@value}.
+ */
+ public static final String DEFAULT_LOGIN_URL = "/";
+
+ /** Error message to display if authentication fails, value is {@value}. */
+ public static final String ERROR_MSG = PROPERTIES_DOMAIN + "errorMsg";
+
+ /** Class used to initialize http sessions. */
+ public static final String SESSION_MANAGER = PROPERTIES_DOMAIN +
+ "sessionManager";
+
+ /** Default session initializer, value is {@value}. */
+ public static final String DEFAULT_SESSION_MANAGER =
+ "edu.vt.middleware.ldap.servlets.session.DefaultSessionManager";
+
+ /** Default error message, value is {@value}. */
+ public static final String DEFAULT_ERROR_MSG =
+ "Could not authenticate or authorize user";
+
+ /** HTTP parameter used to transmit the user identifier, value is {@value}. */
+ public static final String USER_PARAM = "user";
+
+ /** HTTP parameter used to transmit the user credential, value is {@value}. */
+ public static final String CREDENTIAL_PARAM = "credential";
+
+ /** HTTP parameter used to transmit the redirect url, value is {@value}. */
+ public static final String URL_PARAM = "url";
+
+
+ /** Default constructor. */
+ private ServletConstants() {}
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/servlets/session/DefaultSessionManager.java b/src/main/java/edu/vt/middleware/ldap/servlets/session/DefaultSessionManager.java
new file mode 100644
index 0000000..51ed4d1
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/servlets/session/DefaultSessionManager.java
@@ -0,0 +1,97 @@
+/*
+ $Id: DefaultSessionManager.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets.session;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+
+/**
+ * <code>DefaultSessionManager</code> provides a base class for session
+ * management. After a successful authentication, this class sets the session id
+ * that is set for the login servlet to the user name. After logout the session
+ * id attribute is removed from the session. This class is used by default if no
+ * custom session manager has been set.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class DefaultSessionManager extends SessionManager
+{
+
+
+ /**
+ * This performs any actions necessary to login the suppled user.
+ *
+ * @param session <code>HttpSession</code>
+ * @param user <code>String</code>
+ *
+ * @throws ServletException if an error occurs initializing the session
+ */
+ public void login(final HttpSession session, final String user)
+ throws ServletException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Begin login method");
+ }
+ if (this.sessionId != null) {
+ session.setAttribute(this.sessionId, user);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "Set session attribute " + this.sessionId + " to " + user);
+ }
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Could not set session attribute, value is null");
+ }
+ }
+ }
+
+
+ /**
+ * This performs any actions necessary to logout the suppled session.
+ *
+ * @param session <code>HttpSession</code>
+ *
+ * @throws ServletException if an error occurs cleaning up the session
+ */
+ public void logout(final HttpSession session)
+ throws ServletException
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Begin logout method");
+ }
+ if (this.sessionId != null) {
+ final String user = (String) session.getAttribute(this.sessionId);
+ session.removeAttribute(this.sessionId);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "Removed session attribute " + this.sessionId + " for " + user);
+ }
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Could not remove session attribute, value is null");
+ }
+ }
+ if (this.invalidateSession) {
+ session.invalidate();
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Session invalidated");
+ }
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Session was not invalidated");
+ }
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/servlets/session/SessionManager.java b/src/main/java/edu/vt/middleware/ldap/servlets/session/SessionManager.java
new file mode 100644
index 0000000..0d99b4f
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/servlets/session/SessionManager.java
@@ -0,0 +1,92 @@
+/*
+ $Id: SessionManager.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets.session;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>SessionManager</code> provides a parent class for initializing a <code>
+ * HttpSession</code> after a successful authentication and destroying a <code>
+ * HttpSession</code> after logout.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public abstract class SessionManager
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Identifier to set in the session after valid authentication. */
+ protected String sessionId;
+
+ /** Whether to invalidate session on logout. */
+ protected boolean invalidateSession = true;
+
+
+ /**
+ * This sets a session id that can be used in {@link #login} or {@link
+ * #logout}.
+ *
+ * @param id <code>String</code>
+ */
+ public void setSessionId(final String id)
+ {
+ this.sessionId = id;
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Set session attribute to " + this.sessionId);
+ }
+ }
+
+
+ /**
+ * This sets whether to invalidate a session on logout. Default value is true.
+ *
+ * @param invalidate <code>boolean</code>
+ */
+ public void setInvalidateSession(final boolean invalidate)
+ {
+ this.invalidateSession = invalidate;
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Set invalidateSession to " + this.invalidateSession);
+ }
+ }
+
+
+ /**
+ * This performs any actions necessary to login the suppled user.
+ *
+ * @param session <code>HttpSession</code>
+ * @param user <code>String</code>
+ *
+ * @throws ServletException if an error occurs initializing the session
+ */
+ public abstract void login(HttpSession session, String user)
+ throws ServletException;
+
+
+ /**
+ * This performs any actions necessary to logout the suppled session.
+ *
+ * @param session <code>HttpSession</code>
+ *
+ * @throws ServletException if an error occurs cleaning up the session
+ */
+ public abstract void logout(HttpSession session)
+ throws ServletException;
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/AbstractCredentialReader.java b/src/main/java/edu/vt/middleware/ldap/ssl/AbstractCredentialReader.java
new file mode 100644
index 0000000..c4e37f9
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/AbstractCredentialReader.java
@@ -0,0 +1,111 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Base class for all credential readers. It provides support for loading files
+ * from resources on the classpath or a filepath. If a path is prefixed with the
+ * string "classpath:" it is interpreted as a classpath specification. If a path
+ * is prefixed with the string "file:" it is interpreted as a file path. Any
+ * other input throws IllegalArgumentException.
+ *
+ * @param <T> Type of credential read by this instance.
+ *
+ * @author Middleware Services
+ * @version $Revision$
+ */
+public abstract class AbstractCredentialReader<T> implements CredentialReader<T>
+{
+
+ /** Prefix used to indicate a classpath resource. */
+ public static final String CLASSPATH_PREFIX = "classpath:";
+
+ /** Prefix used to indicate a file resource. */
+ public static final String FILE_PREFIX = "file:";
+
+ /** Start index of path specification when given a classpath resource. */
+ private static final int CLASSPATH_START_INDEX = CLASSPATH_PREFIX.length();
+
+ /** Start index of path specification when given a file resource. */
+ private static final int FILE_START_INDEX = FILE_PREFIX.length();
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+
+ /** {@inheritDoc} */
+ public T read(final String path, final String... params)
+ throws IOException, GeneralSecurityException
+ {
+ InputStream is = null;
+ if (path.startsWith(CLASSPATH_PREFIX)) {
+ is = getClass().getResourceAsStream(
+ path.substring(CLASSPATH_START_INDEX));
+ } else if (path.startsWith(FILE_PREFIX)) {
+ is = new FileInputStream(new File(path.substring(FILE_START_INDEX)));
+ } else {
+ throw new IllegalArgumentException(
+ "path must start with either " + CLASSPATH_PREFIX + " or " +
+ FILE_PREFIX);
+ }
+ if (is != null) {
+ try {
+ return read(is, params);
+ } finally {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Successfully loaded " + path);
+ }
+ is.close();
+ }
+ } else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Failed to load " + path);
+ }
+ return null;
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public abstract T read(InputStream is, String... params)
+ throws IOException, GeneralSecurityException;
+
+
+ /**
+ * Gets a buffered input stream from the given input stream. If the given
+ * instance is already buffered, it is simply returned.
+ *
+ * @param is Input stream from which to create buffered instance.
+ *
+ * @return Buffered input stream. If the given instance is already buffered,
+ * it is simply returned.
+ */
+ protected InputStream getBufferedInputStream(final InputStream is)
+ {
+ if (is.markSupported()) {
+ return is;
+ } else {
+ return new BufferedInputStream(is);
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/AbstractSSLContextInitializer.java b/src/main/java/edu/vt/middleware/ldap/ssl/AbstractSSLContextInitializer.java
new file mode 100644
index 0000000..8531b0e
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/AbstractSSLContextInitializer.java
@@ -0,0 +1,55 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.GeneralSecurityException;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Provides common implementation for <code>SSLContextInitializer</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1106 $ $Date: 2010-01-29 23:34:13 -0500 (Fri, 29 Jan 2010) $
+ */
+public abstract class AbstractSSLContextInitializer
+ implements SSLContextInitializer
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+
+ /** {@inheritDoc} */
+ public SSLContext initSSLContext(final String protocol)
+ throws GeneralSecurityException
+ {
+ final SSLContext ctx = SSLContext.getInstance(protocol);
+ ctx.init(this.getKeyManagers(), this.getTrustManagers(), null);
+ return ctx;
+ }
+
+
+ /** {@inheritDoc} */
+ public abstract TrustManager[] getTrustManagers()
+ throws GeneralSecurityException;
+
+
+ /** {@inheritDoc} */
+ public abstract KeyManager[] getKeyManagers()
+ throws GeneralSecurityException;
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/AbstractTLSSocketFactory.java b/src/main/java/edu/vt/middleware/ldap/ssl/AbstractTLSSocketFactory.java
new file mode 100644
index 0000000..d60a97f
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/AbstractTLSSocketFactory.java
@@ -0,0 +1,371 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.GeneralSecurityException;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Provides common implementation for <code>TLSSocketFactory</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1106 $ $Date: 2010-01-29 23:34:13 -0500 (Fri, 29 Jan 2010) $
+ */
+public abstract class AbstractTLSSocketFactory extends SSLSocketFactory
+{
+
+ /** Default SSL protocol, value is {@value}. */
+ public static final String DEFAULT_PROTOCOL = "TLS";
+
+ /** SSLSocketFactory used for creating SSL sockets. */
+ protected SSLSocketFactory factory;
+
+ /** Hostname verifier for this socket factory. */
+ protected HostnameVerifier hostnameVerifier;
+
+ /** Enabled cipher suites. */
+ protected String[] cipherSuites;
+
+ /** Enabled protocol versions. */
+ protected String[] protocols;
+
+
+ /**
+ * Prepares this socket factory for use. Must be called before factory can be
+ * used.
+ *
+ * @throws GeneralSecurityException if the factory cannot be initialized
+ */
+ public abstract void initialize()
+ throws GeneralSecurityException;
+
+
+ /**
+ * This returns the underlying <code>SSLSocketFactory</code> that this class
+ * uses for creating SSL Sockets.
+ *
+ * @return <code>SSLSocketFactory</code>
+ */
+ public SSLSocketFactory getFactory()
+ {
+ return this.factory;
+ }
+
+
+ /**
+ * Returns the hostname verifier to invoke when sockets are created.
+ *
+ * @return hostname verifier
+ */
+ public HostnameVerifier getHostnameVerifier()
+ {
+ return hostnameVerifier;
+ }
+
+
+ /**
+ * Sets the hostname verifier to invoke when sockets are created.
+ *
+ * @param verifier for SSL hostnames
+ */
+ public void setHostnameVerifier(final HostnameVerifier verifier)
+ {
+ hostnameVerifier = verifier;
+ }
+
+
+ /**
+ * This returns the names of the SSL cipher suites which are currently enabled
+ * for use on sockets created by this factory. A null value indicates that no
+ * specific cipher suites have been enabled and that the default suites are in
+ * use.
+ *
+ * @return <code>String[]</code> of cipher suites
+ */
+ public String[] getEnabledCipherSuites()
+ {
+ return this.cipherSuites;
+ }
+
+
+ /**
+ * Sets the cipher suites enabled for use on sockets created by this factory.
+ * See {@link javax.net.ssl.SSLSocket#setEnabledCipherSuites(String[])}.
+ *
+ * @param s <code>String[]</code> of cipher suites
+ */
+ public void setEnabledCipherSuites(final String[] s)
+ {
+ this.cipherSuites = s;
+ }
+
+
+ /**
+ * This returns the names of the protocol versions which are currently enabled
+ * for use on sockets created by this factory. A null value indicates that no
+ * specific protocols have been enabled and that the default protocols are in
+ * use.
+ *
+ * @return <code>String[]</code> of protocols
+ */
+ public String[] getEnabledProtocols()
+ {
+ return this.protocols;
+ }
+
+
+ /**
+ * Sets the protocol versions enabled for use on sockets created by this
+ * factory. See {@link javax.net.ssl.SSLSocket#setEnabledProtocols(String[])}.
+ *
+ * @param s <code>String[]</code> of cipher suites
+ */
+ public void setEnabledProtocols(final String[] s)
+ {
+ this.protocols = s;
+ }
+
+
+ /**
+ * Initializes the supplied socket for use.
+ *
+ * @param s <code>SSLSocket</code> to initialize
+ *
+ * @return <code>SSLSocket</code>
+ *
+ * @throws IOException if an I/O error occurs when initializing the socket
+ */
+ protected SSLSocket initSSLSocket(final SSLSocket s)
+ throws IOException
+ {
+ if (this.cipherSuites != null) {
+ s.setEnabledCipherSuites(this.cipherSuites);
+ }
+ if (this.protocols != null) {
+ s.setEnabledProtocols(this.protocols);
+ }
+ if (hostnameVerifier != null) {
+ // calling getSession() will initiate the handshake if necessary
+ final String hostname = s.getSession().getPeerHost();
+ if (!hostnameVerifier.verify(hostname, s.getSession())) {
+ s.close();
+ s.getSession().invalidate();
+ throw new SSLPeerUnverifiedException(
+ String.format(
+ "Hostname '%s' does not match the hostname in the server's " +
+ "certificate", hostname));
+ }
+ }
+ return s;
+ }
+
+
+ /**
+ * This returns a socket layered over an existing socket connected to the
+ * named host, at the given port.
+ *
+ * @param s <code>Socket</code> existing socket
+ * @param host <code>String</code> server hostname
+ * @param port <code>int</code> server port
+ * @param autoClose <code>boolean</code> close the underlying socket when
+ * this socket is closed
+ *
+ * @return <code>Socket</code> - connected to the specified host and port
+ *
+ * @throws IOException if an I/O error occurs when creating the socket
+ */
+ public Socket createSocket(
+ final Socket s,
+ final String host,
+ final int port,
+ final boolean autoClose)
+ throws IOException
+ {
+ SSLSocket socket = null;
+ if (this.factory != null) {
+ socket = this.initSSLSocket(
+ (SSLSocket) this.factory.createSocket(s, host, port, autoClose));
+ }
+ return socket;
+ }
+
+
+ /**
+ * This creates an unconnected socket.
+ *
+ * @return <code>Socket</code> - unconnected socket
+ *
+ * @throws IOException if an I/O error occurs when creating the socket
+ */
+ public Socket createSocket()
+ throws IOException
+ {
+ SSLSocket socket = null;
+ if (this.factory != null) {
+ socket = this.initSSLSocket((SSLSocket) this.factory.createSocket());
+ }
+ return socket;
+ }
+
+
+ /**
+ * This creates a socket and connects it to the specified port number at the
+ * specified address.
+ *
+ * @param host <code>InetAddress</code> server hostname
+ * @param port <code>int</code> server port
+ *
+ * @return <code>Socket</code> - connected to the specified host and port
+ *
+ * @throws IOException if an I/O error occurs when creating the socket
+ */
+ public Socket createSocket(final InetAddress host, final int port)
+ throws IOException
+ {
+ SSLSocket socket = null;
+ if (this.factory != null) {
+ socket = this.initSSLSocket(
+ (SSLSocket) this.factory.createSocket(host, port));
+ }
+ return socket;
+ }
+
+
+ /**
+ * This creates a socket and connect it to the specified port number at the
+ * specified address. The socket will also be bound to the supplied local
+ * address and port.
+ *
+ * @param address <code>InetAddress</code> server hostname
+ * @param port <code>int</code> server port
+ * @param localAddress <code>InetAddress</code> client hostname
+ * @param localPort <code>int</code> client port
+ *
+ * @return <code>Socket</code> - connected to the specified host and port
+ *
+ * @throws IOException if an I/O error occurs when creating the socket
+ */
+ public Socket createSocket(
+ final InetAddress address,
+ final int port,
+ final InetAddress localAddress,
+ final int localPort)
+ throws IOException
+ {
+ SSLSocket socket = null;
+ if (this.factory != null) {
+ socket = this.initSSLSocket(
+ (SSLSocket) this.factory.createSocket(
+ address,
+ port,
+ localAddress,
+ localPort));
+ }
+ return socket;
+ }
+
+
+ /**
+ * This creates a socket and connects it to the specified port number at the
+ * specified address.
+ *
+ * @param host <code>String</code> server hostname
+ * @param port <code>int</code> server port
+ *
+ * @return <code>Socket</code> - connected to the specified host and port
+ *
+ * @throws IOException if an I/O error occurs when creating the socket
+ */
+ public Socket createSocket(final String host, final int port)
+ throws IOException
+ {
+ SSLSocket socket = null;
+ if (this.factory != null) {
+ socket = this.initSSLSocket(
+ (SSLSocket) this.factory.createSocket(host, port));
+ }
+ return socket;
+ }
+
+
+ /**
+ * This creates a socket and connect it to the specified port number at the
+ * specified address. The socket will also be bound to the supplied local
+ * address and port.
+ *
+ * @param host <code>String</code> server hostname
+ * @param port <code>int</code> server port
+ * @param localHost <code>InetAddress</code> client hostname
+ * @param localPort <code>int</code> client port
+ *
+ * @return <code>Socket</code> - connected to the specified host and port
+ *
+ * @throws IOException if an I/O error occurs when creating the socket
+ */
+ public Socket createSocket(
+ final String host,
+ final int port,
+ final InetAddress localHost,
+ final int localPort)
+ throws IOException
+ {
+ SSLSocket socket = null;
+ if (this.factory != null) {
+ socket = this.initSSLSocket(
+ (SSLSocket) this.factory.createSocket(
+ host,
+ port,
+ localHost,
+ localPort));
+ }
+ return socket;
+ }
+
+
+ /**
+ * This returns the list of cipher suites which are enabled by default.
+ *
+ * @return <code>String[]</code> - array of the cipher suites
+ */
+ public String[] getDefaultCipherSuites()
+ {
+ String[] ciphers = null;
+ if (this.factory != null) {
+ ciphers = this.factory.getDefaultCipherSuites();
+ }
+ return ciphers;
+ }
+
+
+ /**
+ * This returns the names of the cipher suites which could be enabled for use
+ * on an SSL connection.
+ *
+ * @return <code>String[]</code> - array of the cipher suites
+ */
+ public String[] getSupportedCipherSuites()
+ {
+ String[] ciphers = null;
+ if (this.factory != null) {
+ ciphers = this.factory.getSupportedCipherSuites();
+ }
+ return ciphers;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/AggregateTrustManager.java b/src/main/java/edu/vt/middleware/ldap/ssl/AggregateTrustManager.java
new file mode 100644
index 0000000..39a9d20
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/AggregateTrustManager.java
@@ -0,0 +1,99 @@
+/*
+ $Id: AggregateTrustManager.java 2231 2012-02-02 15:46:27Z dfisher $
+
+ Copyright (C) 2003-2012 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2231 $
+ Updated: $Date: 2012-02-02 15:46:27 +0000 (Thu, 02 Feb 2012) $
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import javax.net.ssl.X509TrustManager;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Trust manager that delegates to multiple trust managers.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2231 $
+ */
+public class AggregateTrustManager implements X509TrustManager
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Trust managers to invoke. */
+ private X509TrustManager[] trustManagers;
+
+
+ /**
+ * Creates a new aggregate trust manager.
+ *
+ * @param managers to aggregate
+ */
+ public AggregateTrustManager(final X509TrustManager... managers)
+ {
+ trustManagers = managers;
+ }
+
+
+ /** {@inheritDoc} */
+ public void checkClientTrusted(
+ final X509Certificate[] chain, final String authType)
+ throws CertificateException
+ {
+ if (trustManagers != null) {
+ for (X509TrustManager tm : trustManagers) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("invoking checkClientTrusted for " + tm);
+ }
+ tm.checkClientTrusted(chain, authType);
+ }
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public void checkServerTrusted(
+ final X509Certificate[] chain, final String authType)
+ throws CertificateException
+ {
+ if (trustManagers != null) {
+ for (X509TrustManager tm : trustManagers) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("invoking checkServerTrusted for " + tm);
+ }
+ tm.checkServerTrusted(chain, authType);
+ }
+ }
+ }
+
+
+ /** {@inheritDoc} */
+ public X509Certificate[] getAcceptedIssuers()
+ {
+ final List<X509Certificate> issuers = new ArrayList<X509Certificate>();
+ if (trustManagers != null) {
+ for (X509TrustManager tm : trustManagers) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("invoking getAcceptedIssuers invoked for " + tm);
+ }
+ for (X509Certificate cert : tm.getAcceptedIssuers()) {
+ issuers.add(cert);
+ }
+ }
+ }
+ return issuers.toArray(new X509Certificate[issuers.size()]);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/CertificateHostnameVerifier.java b/src/main/java/edu/vt/middleware/ldap/ssl/CertificateHostnameVerifier.java
new file mode 100644
index 0000000..5fbfaa3
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/CertificateHostnameVerifier.java
@@ -0,0 +1,37 @@
+/*
+ $Id: CertificateHostnameVerifier.java 2231 2012-02-02 15:46:27Z dfisher $
+
+ Copyright (C) 2003-2012 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2231 $
+ Updated: $Date: 2012-02-02 15:46:27 +0000 (Thu, 02 Feb 2012) $
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * Interface for verifying a hostname matching a certificate.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2231 $
+ */
+public interface CertificateHostnameVerifier
+{
+
+
+ /**
+ * Verify the supplied hostname matches the supplied certificate.
+ *
+ * @param hostname to verify
+ * @param cert to verify hostname against
+ *
+ * @return whether hostname is valid for the supplied certificate
+ */
+ boolean verify(final String hostname, final X509Certificate cert);
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/CredentialConfig.java b/src/main/java/edu/vt/middleware/ldap/ssl/CredentialConfig.java
new file mode 100644
index 0000000..00c5a54
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/CredentialConfig.java
@@ -0,0 +1,43 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * <code>CredentialConfig</code> provides a base interface for all credential
+ * configurations. Since credential configs are invoked via reflection by the
+ * PropertyInvoker their method signatures are not important. They only need to
+ * be able to create an SSL context initializer once their properties have been
+ * set.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1106 $ $Date: 2010-01-29 23:34:13 -0500 (Fri, 29 Jan 2010) $
+ */
+public interface CredentialConfig
+{
+
+
+ /**
+ * Creates an <code>SSLContextInitializer</code> using the configured trust
+ * and authentication material in this config.
+ *
+ * @return <code>SSLContextInitializer</code>
+ *
+ * @throws GeneralSecurityException if the ssl context initializer cannot be
+ * created
+ */
+ SSLContextInitializer createSSLContextInitializer()
+ throws GeneralSecurityException;
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/CredentialConfigParser.java b/src/main/java/edu/vt/middleware/ldap/ssl/CredentialConfigParser.java
new file mode 100644
index 0000000..b0dfc59
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/CredentialConfigParser.java
@@ -0,0 +1,205 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import edu.vt.middleware.ldap.props.SimplePropertyInvoker;
+
+/**
+ * Parses the configuration data associated with credential configs and ssl
+ * socket factories. The format of the property string should be like:
+ *
+ * <pre>
+ MySSLSocketFactory
+ {KeyStoreCredentialConfig
+ {{trustStore=/tmp/my.truststore}{trustStoreType=JKS}}}
+ * </pre>
+ *
+ * <p>or</p>
+ *
+ * <pre>
+ {KeyStoreCredentialConfig
+ {{trustStore=/tmp/my.truststore}{trustStoreType=JKS}}}
+ * </pre>
+ *
+ * <p>or</p>
+ *
+ * <pre>
+ {{trustCertificates=/tmp/my.crt}}
+ * </pre>
+ *
+ * @author Middleware Services
+ * @version $Revision: 930 $ $Date: 2009-10-26 16:44:26 -0400 (Mon, 26 Oct 2009) $
+ */
+public class CredentialConfigParser
+{
+
+ /** Property string for configuring a credential config. */
+ private static final Pattern FULL_CONFIG_PATTERN = Pattern.compile(
+ "([^\\{]+)\\s*\\{\\s*([^\\{]+)\\s*\\{\\s*(.*)\\}\\s*\\}\\s*");
+
+ /** Property string for configuring a credential config. */
+ private static final Pattern CREDENTIAL_ONLY_CONFIG_PATTERN = Pattern.compile(
+ "\\s*\\{\\s*([^\\{]+)\\s*\\{\\s*(.*)\\}\\s*\\}\\s*");
+
+ /** Property string for configuring a credential config. */
+ private static final Pattern PARAMS_ONLY_CONFIG_PATTERN = Pattern.compile(
+ "\\s*\\{\\s*(.*)\\s*\\}\\s*");
+
+ /** Pattern for finding properties. */
+ private static final Pattern PROPERTY_PATTERN = Pattern.compile(
+ "([^\\}\\{])+");
+
+ /** SSL socket factory class found in the config. */
+ private String sslSocketFactoryClassName =
+ "edu.vt.middleware.ldap.ssl.TLSSocketFactory";
+
+ /** Credential config class found in the config. */
+ private String credentialConfigClassName =
+ "edu.vt.middleware.ldap.ssl.X509CredentialConfig";
+
+ /** Properties found in the config to set on the credential config. */
+ private Map<String, String> properties = new HashMap<String, String>();
+
+
+ /**
+ * Creates a new <code>CredentialConfigParser</code> with the supplied
+ * configuration string.
+ *
+ * @param config <code>String</code>
+ */
+ public CredentialConfigParser(final String config)
+ {
+ final Matcher fullMatcher = FULL_CONFIG_PATTERN.matcher(config);
+ final Matcher credentialOnlyMatcher = CREDENTIAL_ONLY_CONFIG_PATTERN
+ .matcher(config);
+ final Matcher paramsOnlyMatcher = PARAMS_ONLY_CONFIG_PATTERN.matcher(
+ config);
+ Matcher m = null;
+ if (fullMatcher.matches()) {
+ int i = 1;
+ this.sslSocketFactoryClassName = fullMatcher.group(i++).trim();
+ this.credentialConfigClassName = fullMatcher.group(i++).trim();
+ if (!"".equals(fullMatcher.group(i).trim())) {
+ m = PROPERTY_PATTERN.matcher(fullMatcher.group(i).trim());
+ }
+ } else if (credentialOnlyMatcher.matches()) {
+ int i = 1;
+ this.credentialConfigClassName = credentialOnlyMatcher.group(i++).trim();
+ if (!"".equals(credentialOnlyMatcher.group(i).trim())) {
+ m = PROPERTY_PATTERN.matcher(credentialOnlyMatcher.group(i).trim());
+ }
+ } else if (paramsOnlyMatcher.matches()) {
+ final int i = 1;
+ if (!"".equals(paramsOnlyMatcher.group(i).trim())) {
+ m = PROPERTY_PATTERN.matcher(paramsOnlyMatcher.group(i).trim());
+ }
+ }
+ if (m != null) {
+ while (m.find()) {
+ final String input = m.group().trim();
+ if (input != null && !"".equals(input)) {
+ final String[] s = input.split("=");
+ this.properties.put(s[0].trim(), s[1].trim());
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Returns the SSL socket factory class name from the configuration.
+ *
+ * @return <code>String</code> class name
+ */
+ public String getSslSocketFactoryClassName()
+ {
+ return this.sslSocketFactoryClassName;
+ }
+
+
+ /**
+ * Returns the credential config class name from the configuration.
+ *
+ * @return <code>String</code> class name
+ */
+ public String getCredentialConfigClassName()
+ {
+ return this.credentialConfigClassName;
+ }
+
+
+ /**
+ * Returns the properties from the configuration.
+ *
+ * @return <code>Map</code> of property name to value
+ */
+ public Map<String, String> getProperties()
+ {
+ return this.properties;
+ }
+
+
+ /**
+ * Returns whether the supplied configuration data contains a credential
+ * config.
+ *
+ * @param config <code>String</code>
+ *
+ * @return <code>boolean</code>
+ */
+ public static boolean isCredentialConfig(final String config)
+ {
+ return
+ FULL_CONFIG_PATTERN.matcher(config).matches() ||
+ CREDENTIAL_ONLY_CONFIG_PATTERN.matcher(config).matches() ||
+ PARAMS_ONLY_CONFIG_PATTERN.matcher(config).matches();
+ }
+
+
+ /**
+ * Initialize an instance of credential config with the properties contained
+ * in this config.
+ *
+ * @return <code>Object</code> of the type <code>CredentialConfig</code>
+ */
+ public Object initializeType()
+ {
+ final Class<?> c = SimplePropertyInvoker.createClass(
+ this.getCredentialConfigClassName());
+ final Object o = SimplePropertyInvoker.instantiateType(
+ c,
+ this.getCredentialConfigClassName());
+ this.setProperties(c, o);
+ return o;
+ }
+
+
+ /**
+ * Sets the properties on the supplied object.
+ *
+ * @param c <code>Class</code> type of the supplied object
+ * @param o <code>Object</code> to invoke properties on
+ */
+ protected void setProperties(final Class<?> c, final Object o)
+ {
+ final SimplePropertyInvoker invoker = new SimplePropertyInvoker(c);
+ for (Map.Entry<String, String> entry : this.getProperties().entrySet()) {
+ invoker.setProperty(o, entry.getKey(), entry.getValue());
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/CredentialReader.java b/src/main/java/edu/vt/middleware/ldap/ssl/CredentialReader.java
new file mode 100644
index 0000000..6f1429d
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/CredentialReader.java
@@ -0,0 +1,62 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+
+/**
+ * Reads a credential from an IO source.
+ *
+ * @param <T> Type of credential read by this instance.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1106 $ $Date: 2010-01-29 23:34:13 -0500 (Fri, 29 Jan 2010) $
+ */
+public interface CredentialReader<T>
+{
+
+
+ /**
+ * Reads a credential object from a path.
+ *
+ * @param path Path from which to read credential.
+ * @param params Arbitrary string parameters, e.g. password, needed to read
+ * the credential.
+ *
+ * @return Credential read from data at path.
+ *
+ * @throws IOException On IO errors.
+ * @throws GeneralSecurityException On errors with the credential data.
+ */
+ T read(String path, String... params)
+ throws IOException, GeneralSecurityException;
+
+
+ /**
+ * Reads a credential object from an input stream.
+ *
+ * @param is Input stream from which to read credential.
+ * @param params Arbitrary string parameters, e.g. password, needed to read
+ * the credential.
+ *
+ * @return Credential read from data in stream.
+ *
+ * @throws IOException On IO errors.
+ * @throws GeneralSecurityException On errors with the credential data.
+ */
+ T read(InputStream is, String... params)
+ throws IOException, GeneralSecurityException;
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/DefaultHostnameVerifier.java b/src/main/java/edu/vt/middleware/ldap/ssl/DefaultHostnameVerifier.java
new file mode 100644
index 0000000..01a0912
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/DefaultHostnameVerifier.java
@@ -0,0 +1,355 @@
+/*
+ $Id: DefaultHostnameVerifier.java 2231 2012-02-02 15:46:27Z dfisher $
+
+ Copyright (C) 2003-2012 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2231 $
+ Updated: $Date: 2012-02-02 15:46:27 +0000 (Thu, 02 Feb 2012) $
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.StringTokenizer;
+import javax.net.SocketFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import edu.vt.middleware.ldap.LdapUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Hostname verifier that provides an implementation similar to what occurs with
+ * JNDI startTLS. Verification occurs in the following order:
+ * <ul>
+ * <li>if hostname is IP, then cert must have exact match IP subjAltName</li>
+ * <li>hostname must match any DNS subjAltName if any exist</li>
+ * <li>hostname must match the first CN</li>
+ * <li>if cert begins with a wildcard, domains are used for matching</li>
+ * </ul>
+ *
+ * @author Middleware Services
+ * @version $Revision: 2231 $ $Date: 2012-02-02 15:46:27 +0000 (Thu, 02 Feb 2012) $
+ */
+public class DefaultHostnameVerifier
+ implements HostnameVerifier, CertificateHostnameVerifier
+{
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** Enum for subject alt name types. */
+ private enum SubjectAltNameType
+ {
+ /** other name (0). */
+ OTHER_NAME,
+
+ /** ref822 name (1). */
+ RFC822_NAME,
+
+ /** dns name (2). */
+ DNS_NAME,
+
+ /** x400 address (3). */
+ X400_ADDRESS,
+
+ /** directory name (4). */
+ DIRECTORY_NAME,
+
+ /** edi party name (5). */
+ EDI_PARTY_NAME,
+
+ /** uniform resource identifier (6). */
+ UNIFORM_RESOURCE_IDENTIFIER,
+
+ /** ip address (7). */
+ IP_ADDRESS,
+
+ /** registered id (8). */
+ REGISTERED_ID;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean verify(final String hostname, final SSLSession session)
+ {
+ boolean b = false;
+ try {
+ String name = null;
+ if (hostname != null) {
+ // if IPv6 strip off the "[]"
+ if (hostname.startsWith("[") && hostname.endsWith("]")) {
+ name = hostname.substring(1, hostname.length() - 1).trim();
+ } else {
+ name = hostname.trim();
+ }
+ }
+ b = verify(name, (X509Certificate) session.getPeerCertificates()[0]);
+ } catch (SSLPeerUnverifiedException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("Could not get certificate from the SSL session", e);
+ }
+ }
+ return b;
+ }
+
+
+ /**
+ * Verify if the hostname is an IP address using
+ * {@link LdapUtil#isIPAddress(String)}. Delegates to
+ * {@link #verifyIP(String, X509Certificate)} and
+ * {@link #verifyDNS(String, X509Certificate)} accordingly.
+ *
+ * @param hostname to verify
+ * @param cert to verify hostname against
+ *
+ * @return whether hostname is valid for the supplied certificate
+ */
+ public boolean verify(final String hostname, final X509Certificate cert)
+ {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Verify with the following parameters:");
+ this.logger.debug(" hostname = " + hostname);
+ this.logger.debug(
+ " cert = " + cert.getSubjectX500Principal().toString());
+ }
+ boolean b = false;
+ if (LdapUtil.isIPAddress(hostname)) {
+ b = verifyIP(hostname, cert);
+ } else {
+ b = verifyDNS(hostname, cert);
+ }
+ return b;
+ }
+
+
+ /**
+ * Verify the certificate allows use of the supplied IP address.
+ *
+ * From RFC2818:
+ * In some cases, the URI is specified as an IP address rather than a
+ * hostname. In this case, the iPAddress subjectAltName must be present
+ * in the certificate and must exactly match the IP in the URI.
+ *
+ * @param ip address to match in the certificate
+ * @param cert to inspect for the IP address
+ *
+ * @return whether the ip matched a subject alt name
+ */
+ protected boolean verifyIP(final String ip, final X509Certificate cert)
+ {
+ final String[] subjAltNames = getSubjectAltNames(
+ cert, SubjectAltNameType.IP_ADDRESS);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "verifyIP using subjectAltNames = " + Arrays.toString(subjAltNames));
+ }
+ for (String name : subjAltNames) {
+ if (ip.equalsIgnoreCase(name)) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("verifyIP found hostname match: " + name);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Verify the certificate allows use of the supplied DNS name. Note that only
+ * the first CN is used.
+ *
+ * From RFC2818:
+ * If a subjectAltName extension of type dNSName is present, that MUST
+ * be used as the identity. Otherwise, the (most specific) Common Name
+ * field in the Subject field of the certificate MUST be used. Although
+ * the use of the Common Name is existing practice, it is deprecated and
+ * Certification Authorities are encouraged to use the dNSName instead.
+ *
+ * Matching is performed using the matching rules specified by
+ * [RFC2459]. If more than one identity of a given type is present in
+ * the certificate (e.g., more than one dNSName name, a match in any one
+ * of the set is considered acceptable.)
+ *
+ * @param hostname to match in the certificate
+ * @param cert to inspect for the hostname
+ *
+ * @return whether the hostname matched a subject alt name or CN
+ */
+ protected boolean verifyDNS(final String hostname, final X509Certificate cert)
+ {
+ boolean verified = false;
+ final String[] subjAltNames = getSubjectAltNames(
+ cert, SubjectAltNameType.DNS_NAME);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(
+ "verifyDNS using subjectAltNames = " + Arrays.toString(subjAltNames));
+ }
+ if (subjAltNames.length > 0) {
+ // if subject alt names exist, one must match
+ for (String name : subjAltNames) {
+ if (isMatch(hostname, name)) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("verifyDNS found hostname match: " + name);
+ }
+ verified = true;
+ break;
+ }
+ }
+ } else {
+ final String[] cns = getCNs(cert);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("verifyDNS using CN = " + Arrays.toString(cns));
+ }
+ if (cns.length > 0) {
+ if (isMatch(hostname, cns[0])) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("verifyDNS found hostname match: " + cns[0]);
+ }
+ verified = true;
+ }
+ }
+ }
+ return verified;
+ }
+
+
+ /**
+ * Returns the subject alternative names matching the supplied name type from
+ * the supplied certificate.
+ *
+ * @param cert to get subject alt names from
+ * @param type subject alt name type
+ *
+ * @return subject alt names
+ */
+ private String[] getSubjectAltNames(
+ final X509Certificate cert, final SubjectAltNameType type)
+ {
+ final List<String> names = new ArrayList<String>();
+ try {
+ final Collection<List<?>> subjAltNames =
+ cert.getSubjectAlternativeNames();
+ if (subjAltNames != null) {
+ for (List<?> generalName : subjAltNames) {
+ final Integer nameType = (Integer) generalName.get(0);
+ if (nameType.intValue() == type.ordinal()) {
+ names.add((String) generalName.get(1));
+ }
+ }
+ }
+ } catch (CertificateParsingException e) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("Error reading subject alt names from certificate", e);
+ }
+ }
+ return names.toArray(new String[names.size()]);
+ }
+
+
+ /**
+ * Returns the CNs from the supplied certificate.
+ *
+ * @param cert to get CNs from
+ *
+ * @return CNs
+ */
+ private String[] getCNs(final X509Certificate cert)
+ {
+ final List<String> names = new ArrayList<String>();
+ // not a perfect implementation but appears to work for >99% of certificates
+ // and has the virtue of not requiring any dependencies
+ final String subjectPrincipal = cert.getSubjectX500Principal().toString();
+ final StringTokenizer st = new StringTokenizer(subjectPrincipal, ",");
+ while (st.hasMoreTokens()) {
+ final String tok = st.nextToken();
+ final int x = tok.indexOf("CN=");
+ if (x >= 0) {
+ names.add(tok.substring(x + "CN=".length()));
+ }
+ }
+ return names.toArray(new String[names.size()]);
+ }
+
+
+ /**
+ * Determines if the supplied hostname matches a name derived from the
+ * certificate. If the certificate name starts with '*', the domain components
+ * after the first '.' in each name are compared.
+ *
+ * @param hostname to match
+ * @param certName to match
+ *
+ * @return whether the hostname matched the cert name
+ */
+ private boolean isMatch(final String hostname, final String certName)
+ {
+ // must start with '*' and contain two domain components
+ final boolean isWildcard =
+ certName.startsWith("*.") &&
+ certName.indexOf('.') < certName.lastIndexOf('.');
+
+ boolean match = false;
+ if (isWildcard) {
+ final String certNameDomain = certName.substring(certName.indexOf("."));
+
+ final int hostnameIdx = hostname.indexOf(".") != -1 ?
+ hostname.indexOf(".") : hostname.length();
+ final String hostnameDomain = hostname.substring(hostnameIdx);
+
+ match = certNameDomain.equalsIgnoreCase(hostnameDomain);
+ } else {
+ match = certName.equalsIgnoreCase(hostname);
+ }
+ return match;
+ }
+
+
+ /**
+ * Socket factory that uses {@link DefaultHostnameVerifier}.
+ */
+ public static class SSLSocketFactory extends TLSSocketFactory
+ {
+
+
+ /**
+ * Creates a new socket factory that uses this hostname verifier.
+ */
+ public SSLSocketFactory()
+ {
+ setHostnameVerifier(new DefaultHostnameVerifier());
+ }
+
+
+ /**
+ * Returns the default SSL socket factory.
+ *
+ * @return socket factory
+ */
+ public static SocketFactory getDefault()
+ {
+ final SSLSocketFactory sf = new SSLSocketFactory();
+ try {
+ sf.initialize();
+ } catch (GeneralSecurityException e) {
+ final Log logger = LogFactory.getLog(TLSSocketFactory.class);
+ if (logger.isErrorEnabled()) {
+ logger.error("Error initializing socket factory", e);
+ }
+ }
+ return sf;
+ }
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/DefaultSSLContextInitializer.java b/src/main/java/edu/vt/middleware/ldap/ssl/DefaultSSLContextInitializer.java
new file mode 100644
index 0000000..1c70d87
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/DefaultSSLContextInitializer.java
@@ -0,0 +1,74 @@
+/*
+ $Id: DefaultSSLContextInitializer.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.GeneralSecurityException;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.TrustManager;
+
+/**
+ * Provides a default implementation of <code>SSLContextInitializer</code> which
+ * allows the setting of trust and key managers in order to create an SSL
+ * context.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class DefaultSSLContextInitializer extends AbstractSSLContextInitializer
+{
+
+ /** Trust managers. */
+ private TrustManager[] trustManagers;
+
+ /** Key managers. */
+ private KeyManager[] keyManagers;
+
+
+ /** {@inheritDoc} */
+ public TrustManager[] getTrustManagers()
+ throws GeneralSecurityException
+ {
+ return this.trustManagers;
+ }
+
+
+ /**
+ * Sets the trust managers.
+ *
+ * @param tm <code>TrustManager[]</code>
+ */
+ public void setTrustManagers(final TrustManager[] tm)
+ {
+ this.trustManagers = tm;
+ }
+
+
+ /** {@inheritDoc} */
+ public KeyManager[] getKeyManagers()
+ throws GeneralSecurityException
+ {
+ return this.keyManagers;
+ }
+
+
+ /**
+ * Sets the key managers.
+ *
+ * @param km <code>KeyManager[]</code>
+ */
+ public void setKeyManagers(final KeyManager[] km)
+ {
+ this.keyManagers = km;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/HostnameVerifyingTrustManager.java b/src/main/java/edu/vt/middleware/ldap/ssl/HostnameVerifyingTrustManager.java
new file mode 100644
index 0000000..775bbfd
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/HostnameVerifyingTrustManager.java
@@ -0,0 +1,99 @@
+/*
+ $Id: HostnameVerifyingTrustManager.java 2231 2012-02-02 15:46:27Z dfisher $
+
+ Copyright (C) 2003-2012 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2231 $
+ Updated: $Date: 2012-02-02 15:46:27 +0000 (Thu, 02 Feb 2012) $
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * Trust manager that delegates to {@link CertificateHostnameVerifier}. Any name
+ * that verifies passes this trust manager check.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2231 $
+ */
+public class HostnameVerifyingTrustManager implements X509TrustManager
+{
+
+ /** Hostnames to allow. */
+ private String[] hostnames;
+
+ /** Hostname verifier to use for trust. */
+ private CertificateHostnameVerifier hostnameVerifier;
+
+
+ /**
+ * Creates a new hostname verifying trust manager.
+ *
+ * @param verifier that establishes trust
+ * @param names to match against a certificate
+ */
+ public HostnameVerifyingTrustManager(
+ final CertificateHostnameVerifier verifier,
+ final String... names)
+ {
+ hostnameVerifier = verifier;
+ hostnames = names;
+ }
+
+
+ /** {@inheritDoc} */
+ public void checkClientTrusted(
+ final X509Certificate[] chain, final String authType)
+ throws CertificateException
+ {
+ checkCertificateTrusted(chain[0]);
+ }
+
+
+ /** {@inheritDoc} */
+ public void checkServerTrusted(
+ final X509Certificate[] chain, final String authType)
+ throws CertificateException
+ {
+ checkCertificateTrusted(chain[0]);
+ }
+
+
+ /**
+ * Verifies the supplied certificate using the hostname verifier with each
+ * hostname.
+ *
+ * @param cert to verify
+ *
+ * @throws CertificateException if none of the hostnames verify
+ */
+ private void checkCertificateTrusted(final X509Certificate cert)
+ throws CertificateException
+ {
+ for (String name : hostnames) {
+ if (hostnameVerifier.verify(name, cert)) {
+ return;
+ }
+ }
+ throw new CertificateException(
+ String.format(
+ "Hostname '%s' does not match the hostname in the server's " +
+ "certificate", Arrays.toString(hostnames)));
+ }
+
+
+ /** {@inheritDoc} */
+ public X509Certificate[] getAcceptedIssuers()
+ {
+ return new X509Certificate[0];
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/KeyStoreCredentialConfig.java b/src/main/java/edu/vt/middleware/ldap/ssl/KeyStoreCredentialConfig.java
new file mode 100644
index 0000000..a1d88aa
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/KeyStoreCredentialConfig.java
@@ -0,0 +1,213 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/**
+ * Provides the properties necessary for creating an SSL context initializer
+ * with a <code>KeyStoreCredentialReader</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1106 $ $Date: 2010-01-29 23:34:13 -0500 (Fri, 29 Jan 2010) $
+ */
+public class KeyStoreCredentialConfig implements CredentialConfig
+{
+
+ /** Handles loading keystores. */
+ protected KeyStoreCredentialReader keyStoreReader =
+ new KeyStoreCredentialReader();
+
+ /** Name of the truststore to use for the SSL connection. */
+ private String trustStore;
+
+ /** Password needed to open the truststore. */
+ private String trustStorePassword;
+
+ /** Truststore type. */
+ private String trustStoreType;
+
+ /** Name of the keystore to use for the SSL connection. */
+ private String keyStore;
+
+ /** Password needed to open the keystore. */
+ private String keyStorePassword;
+
+ /** Keystore type. */
+ private String keyStoreType;
+
+
+ /**
+ * This returns the name of the truststore to use.
+ *
+ * @return <code>String</code> truststore name
+ */
+ public String getTrustStore()
+ {
+ return this.trustStore;
+ }
+
+
+ /**
+ * This sets the name of the truststore to use.
+ *
+ * @param s <code>String</code> truststore name
+ */
+ public void setTrustStore(final String s)
+ {
+ this.trustStore = s;
+ }
+
+
+ /**
+ * This returns the password for the truststore.
+ *
+ * @return <code>String</code> truststore password
+ */
+ public String getTrustStorePassword()
+ {
+ return this.trustStorePassword;
+ }
+
+
+ /**
+ * This sets the password for the truststore.
+ *
+ * @param s <code>String</code> truststore password
+ */
+ public void setTrustStorePassword(final String s)
+ {
+ this.trustStorePassword = s;
+ }
+
+
+ /**
+ * This returns the type of the truststore.
+ *
+ * @return <code>String</code> truststore type
+ */
+ public String getTrustStoreType()
+ {
+ return this.trustStoreType;
+ }
+
+
+ /**
+ * This sets the type of the truststore.
+ *
+ * @param s <code>String</code> truststore type
+ */
+ public void setTrustStoreType(final String s)
+ {
+ this.trustStoreType = s;
+ }
+
+
+ /**
+ * This returns the name of the keystore to use.
+ *
+ * @return <code>String</code> keystore name
+ */
+ public String getKeyStore()
+ {
+ return this.keyStore;
+ }
+
+
+ /**
+ * This sets the name of the keystore to use.
+ *
+ * @param s <code>String</code> keystore name
+ */
+ public void setKeyStore(final String s)
+ {
+ this.keyStore = s;
+ }
+
+
+ /**
+ * This returns the password for the keystore.
+ *
+ * @return <code>String</code> keystore password
+ */
+ public String getKeyStorePassword()
+ {
+ return this.keyStorePassword;
+ }
+
+
+ /**
+ * This sets the password for the keystore.
+ *
+ * @param s <code>String</code> keystore password
+ */
+ public void setKeyStorePassword(final String s)
+ {
+ this.keyStorePassword = s;
+ }
+
+
+ /**
+ * This returns the type of the keystore.
+ *
+ * @return <code>String</code> keystore type
+ */
+ public String getKeyStoreType()
+ {
+ return this.keyStoreType;
+ }
+
+
+ /**
+ * This sets the type of the keystore.
+ *
+ * @param s <code>String</code> keystore type
+ */
+ public void setKeyStoreType(final String s)
+ {
+ this.keyStoreType = s;
+ }
+
+
+ /** {@inheritDoc} */
+ public SSLContextInitializer createSSLContextInitializer()
+ throws GeneralSecurityException
+ {
+ final KeyStoreSSLContextInitializer sslInit =
+ new KeyStoreSSLContextInitializer();
+ try {
+ if (this.trustStore != null) {
+ sslInit.setTrustKeystore(
+ this.keyStoreReader.read(
+ this.trustStore,
+ this.trustStorePassword,
+ this.trustStoreType));
+ }
+ if (this.keyStore != null) {
+ sslInit.setAuthenticationKeystore(
+ this.keyStoreReader.read(
+ this.keyStore,
+ this.keyStorePassword,
+ this.keyStoreType));
+ sslInit.setAuthenticationPassword(
+ this.keyStorePassword != null ? this.keyStorePassword.toCharArray()
+ : null);
+ }
+ } catch (IOException e) {
+ throw new GeneralSecurityException(e);
+ }
+ return sslInit;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/KeyStoreCredentialReader.java b/src/main/java/edu/vt/middleware/ldap/ssl/KeyStoreCredentialReader.java
new file mode 100644
index 0000000..a073d01
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/KeyStoreCredentialReader.java
@@ -0,0 +1,74 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Arrays;
+
+/**
+ * Reads keystore credentials from a classpath, filepath, or stream resource.
+ *
+ * @author Middleware Services
+ * @version $Revision$
+ */
+public class KeyStoreCredentialReader extends AbstractCredentialReader<KeyStore>
+{
+
+
+ /**
+ * Reads a keystore from an input stream.
+ *
+ * @param is Input stream from which to read keystore.
+ * @param params Two optional parameters are supported:
+ *
+ * <ul>
+ * <li>KeyStore password</li>
+ * <li>KeyStore type; defaults to JVM default keystore format if
+ * omitted</li>
+ * </ul>
+ *
+ * <p>If only a single parameter is supplied, it is assumed to be
+ * the password.</p>
+ *
+ * @return KeyStore read from data in stream.
+ *
+ * @throws IOException On IO errors.
+ * @throws GeneralSecurityException On errors with the credential data.
+ */
+ public KeyStore read(final InputStream is, final String... params)
+ throws IOException, GeneralSecurityException
+ {
+ char[] password = null;
+ if (params.length > 0 && params[0] != null) {
+ password = params[0].toCharArray();
+ }
+
+ String type = KeyStore.getDefaultType();
+ if (params.length > 1 && params[1] != null) {
+ type = params[1];
+ }
+
+ final KeyStore keystore = KeyStore.getInstance(type);
+ if (is != null) {
+ keystore.load(this.getBufferedInputStream(is), password);
+ if (password != null) {
+ Arrays.fill(password, '0');
+ }
+ }
+ return keystore;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/KeyStoreSSLContextInitializer.java b/src/main/java/edu/vt/middleware/ldap/ssl/KeyStoreSSLContextInitializer.java
new file mode 100644
index 0000000..31e8f49
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/KeyStoreSSLContextInitializer.java
@@ -0,0 +1,106 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+/**
+ * Provides a <code>SSLContextInitializer</code> which can use java KeyStores to
+ * create key and trust managers.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1106 $ $Date: 2010-01-29 23:34:13 -0500 (Fri, 29 Jan 2010) $
+ */
+public class KeyStoreSSLContextInitializer extends AbstractSSLContextInitializer
+{
+
+ /** KeyStore used to create trust managers. */
+ private KeyStore trustKeystore;
+
+ /** KeyStore used to create key managers. */
+ private KeyStore authenticationKeystore;
+
+ /** Password used to access the authentication keystore. */
+ private char[] authenticationPassword;
+
+
+ /**
+ * Sets the keystore to use for creating the trust managers.
+ *
+ * @param ks <code>KeyStore</code>
+ */
+ public void setTrustKeystore(final KeyStore ks)
+ {
+ this.trustKeystore = ks;
+ }
+
+
+ /**
+ * Sets the keystore to use for creating the key managers.
+ *
+ * @param ks <code>KeyStore</code>
+ */
+ public void setAuthenticationKeystore(final KeyStore ks)
+ {
+ this.authenticationKeystore = ks;
+ }
+
+
+ /**
+ * Sets the password used for accessing the authentication keystore.
+ *
+ * @param password <code>char[]</code>
+ */
+ public void setAuthenticationPassword(final char[] password)
+ {
+ this.authenticationPassword = password;
+ }
+
+
+ /** {@inheritDoc} */
+ public TrustManager[] getTrustManagers()
+ throws GeneralSecurityException
+ {
+ TrustManager[] tm = null;
+ if (this.trustKeystore != null) {
+ final TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(this.trustKeystore);
+ tm = tmf.getTrustManagers();
+ }
+ return tm;
+ }
+
+
+ /** {@inheritDoc} */
+ public KeyManager[] getKeyManagers()
+ throws GeneralSecurityException
+ {
+ KeyManager[] km = null;
+ if (
+ this.authenticationKeystore != null &&
+ this.authenticationPassword != null) {
+ final KeyManagerFactory kmf = KeyManagerFactory.getInstance(
+ KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(this.authenticationKeystore, this.authenticationPassword);
+ km = kmf.getKeyManagers();
+ }
+ return km;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/PrivateKeyCredentialReader.java b/src/main/java/edu/vt/middleware/ldap/ssl/PrivateKeyCredentialReader.java
new file mode 100644
index 0000000..a018b3b
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/PrivateKeyCredentialReader.java
@@ -0,0 +1,61 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import edu.vt.middleware.ldap.LdapUtil;
+
+/**
+ * Reads private key credentials from classpath, filepath, or stream resource.
+ * Supported private key formats include: PKCS8.
+ *
+ * @author Middleware Services
+ * @version $Revision$
+ */
+public class PrivateKeyCredentialReader
+ extends AbstractCredentialReader<PrivateKey>
+{
+
+
+ /**
+ * Reads a private key from an input stream.
+ *
+ * @param is Input stream from which to read private key.
+ * @param params A single optional parameter, algorithm, may be specified.
+ * The default is RSA.
+ *
+ * @return Private key read from data in stream.
+ *
+ * @throws IOException On IO errors.
+ * @throws GeneralSecurityException On errors with the credential data.
+ */
+ public PrivateKey read(final InputStream is, final String... params)
+ throws IOException, GeneralSecurityException
+ {
+ String algorithm = "RSA";
+ if (params.length > 0 && params[0] != null) {
+ algorithm = params[0];
+ }
+
+ final KeyFactory kf = KeyFactory.getInstance(algorithm);
+ final PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(
+ LdapUtil.readInputStream(this.getBufferedInputStream(is)));
+ return kf.generatePrivate(spec);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/SSLContextInitializer.java b/src/main/java/edu/vt/middleware/ldap/ssl/SSLContextInitializer.java
new file mode 100644
index 0000000..a54a613
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/SSLContextInitializer.java
@@ -0,0 +1,66 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.GeneralSecurityException;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+
+/**
+ * Provides an interface for the initialization of new SSL contexts.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1106 $ $Date: 2010-01-29 23:34:13 -0500 (Fri, 29 Jan 2010) $
+ */
+public interface SSLContextInitializer
+{
+
+
+ /**
+ * Creates an initialized SSLContext for the supplied protocol.
+ *
+ * @param protocol type to use for SSL
+ *
+ * @return <code>SSLContext</code>
+ *
+ * @throws GeneralSecurityException if the SSLContext cannot be created
+ */
+ SSLContext initSSLContext(String protocol)
+ throws GeneralSecurityException;
+
+
+ /**
+ * Returns the trust managers used when creating SSL contexts.
+ *
+ * @return <code>TrustManager[]</code>
+ *
+ * @throws GeneralSecurityException if an errors occurs while loading the
+ * TrustManagers
+ */
+ TrustManager[] getTrustManagers()
+ throws GeneralSecurityException;
+
+
+ /**
+ * Returns the key managers used when creating SSL contexts.
+ *
+ * @return <code>KeyManagers[]</code>
+ *
+ * @throws GeneralSecurityException if an errors occurs while loading the
+ * KeyManagers
+ */
+ KeyManager[] getKeyManagers()
+ throws GeneralSecurityException;
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/SingletonTLSSocketFactory.java b/src/main/java/edu/vt/middleware/ldap/ssl/SingletonTLSSocketFactory.java
new file mode 100644
index 0000000..b72ebba
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/SingletonTLSSocketFactory.java
@@ -0,0 +1,75 @@
+/*
+ $Id: SingletonTLSSocketFactory.java 1742 2010-11-19 15:18:06Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1742 $
+ Updated: $Date: 2010-11-19 15:18:06 +0000 (Fri, 19 Nov 2010) $
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.GeneralSecurityException;
+import javax.net.SocketFactory;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * TLSSocketFactory implementation that uses a static SSLContextInitializer.
+ * Useful for SSL configurations that can only retrieve the SSLSocketFactory
+ * from getDefault().
+ *
+ * @author Middleware Services
+ * @version $Revision: 1742 $ $Date: 2010-11-19 15:18:06 +0000 (Fri, 19 Nov 2010) $
+ */
+public class SingletonTLSSocketFactory extends TLSSocketFactory
+{
+ /** SSLContextInitializer used for initializing SSL contexts. */
+ protected static SSLContextInitializer staticContextInitializer;
+
+
+ /** {@inheritDoc} */
+ public void setSSLContextInitializer(final SSLContextInitializer initializer)
+ {
+ if (staticContextInitializer != null) {
+ final Log logger = LogFactory.getLog(SingletonTLSSocketFactory.class);
+ if (logger.isWarnEnabled()) {
+ logger.warn("SSLContextInitializer is being overridden");
+ }
+ }
+ staticContextInitializer = initializer;
+ }
+
+
+ /** {@inheritDoc} */
+ public void initialize()
+ throws GeneralSecurityException
+ {
+ super.setSSLContextInitializer(staticContextInitializer);
+ super.initialize();
+ }
+
+
+ /**
+ * This returns the default SSL socket factory.
+ *
+ * @return <code>SocketFactory</code>
+ */
+ public static SocketFactory getDefault()
+ {
+ final SingletonTLSSocketFactory sf = new SingletonTLSSocketFactory();
+ try {
+ sf.initialize();
+ } catch (GeneralSecurityException e) {
+ final Log logger = LogFactory.getLog(SingletonTLSSocketFactory.class);
+ if (logger.isErrorEnabled()) {
+ logger.error("Error initializing socket factory", e);
+ }
+ }
+ return sf;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/TLSSocketFactory.java b/src/main/java/edu/vt/middleware/ldap/ssl/TLSSocketFactory.java
new file mode 100644
index 0000000..37abf2b
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/TLSSocketFactory.java
@@ -0,0 +1,116 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.GeneralSecurityException;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <code>TLSSocketFactory</code> is an extension of SSLSocketFactory. Note that
+ * {@link #initialize()} must be called prior to using this socket factory. This
+ * means that this class cannot be passed to implementations that expect the
+ * socket factory to function immediately after construction.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1106 $ $Date: 2010-01-29 23:34:13 -0500 (Fri, 29 Jan 2010) $
+ */
+public class TLSSocketFactory extends AbstractTLSSocketFactory
+{
+
+ /** SSLContextInitializer used for initializing SSL contexts. */
+ protected SSLContextInitializer contextInitializer =
+ new DefaultSSLContextInitializer();
+
+
+ /**
+ * Returns the SSL context initializer.
+ *
+ * @return <code>SSLContextInitializer</code>
+ */
+ public SSLContextInitializer getSSLContextInitializer()
+ {
+ return this.contextInitializer;
+ }
+
+
+ /**
+ * Sets the SSL context initializer.
+ *
+ * @param initializer to create SSL contexts with
+ */
+ public void setSSLContextInitializer(final SSLContextInitializer initializer)
+ {
+ this.contextInitializer = initializer;
+ }
+
+
+ /**
+ * Creates the underlying SSLContext using truststore and keystore attributes
+ * and makes this factory ready for use. Must be called before factory can be
+ * used.
+ *
+ * @throws GeneralSecurityException if the SSLContext cannot be created
+ */
+ public void initialize()
+ throws GeneralSecurityException
+ {
+ final SSLContext ctx = this.getSSLContextInitializer().initSSLContext(
+ DEFAULT_PROTOCOL);
+ this.factory = ctx.getSocketFactory();
+ }
+
+
+ /**
+ * This returns the default SSL socket factory.
+ *
+ * @return <code>SocketFactory</code>
+ */
+ public static SocketFactory getDefault()
+ {
+ final TLSSocketFactory sf = new TLSSocketFactory();
+ try {
+ sf.initialize();
+ } catch (GeneralSecurityException e) {
+ final Log logger = LogFactory.getLog(TLSSocketFactory.class);
+ if (logger.isErrorEnabled()) {
+ logger.error("Error initializing socket factory", e);
+ }
+ }
+ return sf;
+ }
+
+
+ /**
+ * Provides a descriptive string representation of this instance.
+ *
+ * @return String of the form $Classname::factory=$factory.
+ */
+ @Override
+ public String toString()
+ {
+ return
+ String.format(
+ "%s@%d::sslContextInitializer=%s,factory=%s," +
+ "enabledCipherSuites=%s,enabledProtocols=%s",
+ this.getClass().getName(),
+ this.hashCode(),
+ this.getSSLContextInitializer(),
+ this.getFactory(),
+ this.getEnabledCipherSuites(),
+ this.getEnabledProtocols());
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/ThreadLocalTLSSocketFactory.java b/src/main/java/edu/vt/middleware/ldap/ssl/ThreadLocalTLSSocketFactory.java
new file mode 100644
index 0000000..ad7b7e5
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/ThreadLocalTLSSocketFactory.java
@@ -0,0 +1,143 @@
+/*
+ $Id: ThreadLocalTLSSocketFactory.java 2231 2012-02-02 15:46:27Z dfisher $
+
+ Copyright (C) 2003-2012 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2231 $
+ Updated: $Date: 2012-02-02 15:46:27 +0000 (Thu, 02 Feb 2012) $
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * TLSSocketFactory implementation that uses a thread local variable to store
+ * configuration. Useful for SSL configurations that can only retrieve the
+ * SSLSocketFactory from getDefault().
+ *
+ * @author Middleware Services
+ * @version $Revision: 2231 $ $Date: 2012-02-02 15:46:27 +0000 (Thu, 02 Feb 2012) $
+ */
+public class ThreadLocalTLSSocketFactory extends TLSSocketFactory
+{
+
+ /** Thread local instance of the ssl config. */
+ private static final ThreadLocalSslConfig THREAD_LOCAL_SSL_CONFIG =
+ new ThreadLocalSslConfig();
+
+
+ /** {@inheritDoc} */
+ @Override
+ public SSLContextInitializer getSSLContextInitializer()
+ {
+ return THREAD_LOCAL_SSL_CONFIG.get();
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ public void setSSLContextInitializer(final SSLContextInitializer initializer)
+ {
+ THREAD_LOCAL_SSL_CONFIG.set(initializer);
+ }
+
+
+ /**
+ * This returns the default SSL socket factory.
+ *
+ * @return socket factory
+ */
+ public static SocketFactory getDefault()
+ {
+ final ThreadLocalTLSSocketFactory sf = new ThreadLocalTLSSocketFactory();
+ if (sf.getSSLContextInitializer() == null) {
+ throw new NullPointerException(
+ "Thread local sslContextInitializer has not been set");
+ }
+ try {
+ sf.initialize();
+ } catch (GeneralSecurityException e) {
+ throw new IllegalArgumentException(
+ "Error initializing socket factory", e);
+ }
+ return sf;
+ }
+
+
+ /**
+ * Returns an instance of this socket factory configured with a hostname
+ * verifying trust manager.
+ *
+ * @param names to use for hostname verification
+ *
+ * @return socket factory
+ */
+ public static SSLSocketFactory getHostnameVerifierFactory(
+ final String[] names)
+ {
+ final ThreadLocalTLSSocketFactory sf = new ThreadLocalTLSSocketFactory();
+ final DefaultSSLContextInitializer ctxInit =
+ new DefaultSSLContextInitializer();
+ try {
+ final TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init((KeyStore) null);
+ final TrustManager[] tm = tmf.getTrustManagers();
+ final X509TrustManager[] aggregate =
+ new X509TrustManager[tm != null ? tm.length + 1 : 1];
+ if (tm != null) {
+ for (int i = 0; i < tm.length; i++) {
+ aggregate[i] = (X509TrustManager) tm[i];
+ }
+ }
+ aggregate[aggregate.length - 1] = new HostnameVerifyingTrustManager(
+ new DefaultHostnameVerifier(), names);
+ ctxInit.setTrustManagers(
+ new TrustManager[] {new AggregateTrustManager(aggregate)});
+ sf.setSSLContextInitializer(ctxInit);
+ sf.initialize();
+ } catch (GeneralSecurityException e) {
+ throw new IllegalArgumentException(e);
+ }
+ return sf;
+ }
+
+
+ /**
+ * Provides a descriptive string representation of this instance.
+ *
+ * @return String of the form $Classname::factory=$factory.
+ */
+ @Override
+ public String toString()
+ {
+ return
+ String.format(
+ "%s@%d::sslContextInitializer=%s,factory=%s," +
+ "enabledCipherSuites=%s,enabledProtocols=%s",
+ this.getClass().getName(),
+ this.hashCode(),
+ this.getSSLContextInitializer(),
+ this.getFactory(),
+ this.getEnabledCipherSuites(),
+ this.getEnabledProtocols());
+ }
+
+
+ /**
+ * Thread local class for {@link SslConfig}.
+ */
+ private static class ThreadLocalSslConfig
+ extends ThreadLocal<SSLContextInitializer> {}
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/X509CertificateCredentialReader.java b/src/main/java/edu/vt/middleware/ldap/ssl/X509CertificateCredentialReader.java
new file mode 100644
index 0000000..45dbd22
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/X509CertificateCredentialReader.java
@@ -0,0 +1,41 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+/**
+ * Loads an X.509 certificate credential from a classpath, filepath, or stream
+ * resource. Supported certificate formats include: PEM, DER, and PKCS7.
+ *
+ * @author Middleware Services
+ * @version $Revision$
+ */
+public class X509CertificateCredentialReader
+ extends AbstractCredentialReader<X509Certificate>
+{
+
+ /** {@inheritDoc} */
+ public X509Certificate read(final InputStream is, final String... params)
+ throws IOException, GeneralSecurityException
+ {
+ final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return
+ (X509Certificate) cf.generateCertificate(this.getBufferedInputStream(is));
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/X509CertificatesCredentialReader.java b/src/main/java/edu/vt/middleware/ldap/ssl/X509CertificatesCredentialReader.java
new file mode 100644
index 0000000..a9be266
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/X509CertificatesCredentialReader.java
@@ -0,0 +1,51 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loads X.509 certificate credentials from a classpath, filepath, or stream
+ * resource. Supported certificate formats include: PEM, DER, and PKCS7.
+ *
+ * @author Middleware Services
+ * @version $Revision$
+ */
+public class X509CertificatesCredentialReader
+ extends AbstractCredentialReader<X509Certificate[]>
+{
+
+ /** {@inheritDoc} */
+ public X509Certificate[] read(final InputStream is, final String... params)
+ throws IOException, GeneralSecurityException
+ {
+ final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ final List<X509Certificate> certList = new ArrayList<X509Certificate>();
+ final InputStream bufIs = this.getBufferedInputStream(is);
+ while (bufIs.available() > 0) {
+ final X509Certificate cert = (X509Certificate) cf.generateCertificate(
+ bufIs);
+ if (cert != null) {
+ certList.add(cert);
+ }
+ }
+ return certList.toArray(new X509Certificate[certList.size()]);
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/X509CredentialConfig.java b/src/main/java/edu/vt/middleware/ldap/ssl/X509CredentialConfig.java
new file mode 100644
index 0000000..95275b4
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/X509CredentialConfig.java
@@ -0,0 +1,140 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/**
+ * Provides the properties necessary for creating an SSL context initializer
+ * with a <code>X509CredentialReader</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1106 $ $Date: 2010-01-29 23:34:13 -0500 (Fri, 29 Jan 2010) $
+ */
+public class X509CredentialConfig implements CredentialConfig
+{
+
+ /** Reads X.509 certificates credential. */
+ protected X509CertificatesCredentialReader certsReader =
+ new X509CertificatesCredentialReader();
+
+ /** Reads X.509 certificate credential. */
+ protected X509CertificateCredentialReader certReader =
+ new X509CertificateCredentialReader();
+
+ /** Reads private key credential. */
+ protected PrivateKeyCredentialReader keyReader =
+ new PrivateKeyCredentialReader();
+
+ /** Name of the trust certificates to use for the SSL connection. */
+ private String trustCertificates;
+
+ /** Name of the authentication certificate to use for the SSL connection. */
+ private String authenticationCertificate;
+
+ /** Name of the key to use for the SSL connection. */
+ private String authenticationKey;
+
+
+ /**
+ * This returns the name of the trust certificates to use.
+ *
+ * @return <code>String</code> trust certificates name
+ */
+ public String getTrustCertificates()
+ {
+ return this.trustCertificates;
+ }
+
+
+ /**
+ * This sets the name of the trust certificates to use.
+ *
+ * @param s <code>String</code> trust certificates name
+ */
+ public void setTrustCertificates(final String s)
+ {
+ this.trustCertificates = s;
+ }
+
+
+ /**
+ * This returns the name of the authentication certificate to use.
+ *
+ * @return <code>String</code> authentication certificate name
+ */
+ public String getAuthenticationCertificate()
+ {
+ return this.authenticationCertificate;
+ }
+
+
+ /**
+ * This sets the name of the authentication certificate to use.
+ *
+ * @param s <code>String</code> authentication certificate name
+ */
+ public void setAuthenticationCertificate(final String s)
+ {
+ this.authenticationCertificate = s;
+ }
+
+
+ /**
+ * This returns the name of the authentication key to use.
+ *
+ * @return <code>String</code> authentication key name
+ */
+ public String getAuthenticationKey()
+ {
+ return this.authenticationKey;
+ }
+
+
+ /**
+ * This sets the name of the authentication key to use.
+ *
+ * @param s <code>String</code> authentication key name
+ */
+ public void setAuthenticationKey(final String s)
+ {
+ this.authenticationKey = s;
+ }
+
+
+ /** {@inheritDoc} */
+ public SSLContextInitializer createSSLContextInitializer()
+ throws GeneralSecurityException
+ {
+ final X509SSLContextInitializer sslInit = new X509SSLContextInitializer();
+ try {
+ if (this.trustCertificates != null) {
+ sslInit.setTrustCertificates(
+ this.certsReader.read(this.trustCertificates));
+ }
+ if (this.authenticationCertificate != null) {
+ sslInit.setAuthenticationCertificate(
+ this.certReader.read(this.authenticationCertificate));
+ }
+ if (this.authenticationKey != null) {
+ sslInit.setAuthenticationKey(
+ this.keyReader.read(this.authenticationKey));
+ }
+ } catch (IOException e) {
+ throw new GeneralSecurityException(e);
+ }
+ return sslInit;
+ }
+}
diff --git a/src/main/java/edu/vt/middleware/ldap/ssl/X509SSLContextInitializer.java b/src/main/java/edu/vt/middleware/ldap/ssl/X509SSLContextInitializer.java
new file mode 100644
index 0000000..d2c8c58
--- /dev/null
+++ b/src/main/java/edu/vt/middleware/ldap/ssl/X509SSLContextInitializer.java
@@ -0,0 +1,162 @@
+/*
+ $Id$
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision$
+ Updated: $Date$
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+/**
+ * Provides a <code>SSLContextInitializer</code> which can use X509 certificates
+ * to create key and trust managers.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1106 $ $Date: 2010-01-29 23:34:13 -0500 (Fri, 29 Jan 2010) $
+ */
+public class X509SSLContextInitializer extends AbstractSSLContextInitializer
+{
+
+ /** Certificates used to create trust managers. */
+ private X509Certificate[] trustCerts;
+
+ /** Certificate used to create key managers. */
+ private X509Certificate authenticationCert;
+
+ /** Private key used to create key managers. */
+ private PrivateKey authenticationKey;
+
+
+ /**
+ * Returns the certificates to use for creating the trust managers.
+ *
+ * @return <code>X509Certificates[]</code>
+ */
+ public X509Certificate[] getTrustCertificates()
+ {
+ return this.trustCerts;
+ }
+
+
+ /**
+ * Sets the certificates to use for creating the trust managers.
+ *
+ * @param certs <code>X509Certificates[]</code>
+ */
+ public void setTrustCertificates(final X509Certificate[] certs)
+ {
+ this.trustCerts = certs;
+ }
+
+
+ /**
+ * Returns the certificate to use for creating the key managers.
+ *
+ * @return <code>X509Certificate</code>
+ */
+ public X509Certificate getAuthenticationCertificate()
+ {
+ return this.authenticationCert;
+ }
+
+
+ /**
+ * Sets the certificate to use for creating the key managers.
+ *
+ * @param cert <code>X509Certificate</code>
+ */
+ public void setAuthenticationCertificate(final X509Certificate cert)
+ {
+ this.authenticationCert = cert;
+ }
+
+
+ /**
+ * Returns the private key associated with the authentication certificate.
+ *
+ * @return <code>PrivateKey</code>
+ */
+ public PrivateKey getAuthenticationKey()
+ {
+ return this.authenticationKey;
+ }
+
+
+ /**
+ * Sets the private key associated with the authentication certificate.
+ *
+ * @param key <code>PrivateKey</code>
+ */
+ public void setAuthenticationKey(final PrivateKey key)
+ {
+ this.authenticationKey = key;
+ }
+
+
+ /** {@inheritDoc} */
+ public TrustManager[] getTrustManagers()
+ throws GeneralSecurityException
+ {
+ TrustManager[] tm = null;
+ if (this.trustCerts != null && this.trustCerts.length > 0) {
+ final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ try {
+ ks.load(null, null);
+ } catch (IOException e) {
+ throw new GeneralSecurityException(e);
+ }
+ for (int i = 0; i < this.trustCerts.length; i++) {
+ ks.setCertificateEntry("ldap_trust_" + i, this.trustCerts[i]);
+ }
+
+ final TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(ks);
+ tm = tmf.getTrustManagers();
+ }
+ return tm;
+ }
+
+
+ /** {@inheritDoc} */
+ public KeyManager[] getKeyManagers()
+ throws GeneralSecurityException
+ {
+ KeyManager[] km = null;
+ if (this.authenticationCert != null && this.authenticationKey != null) {
+ final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ try {
+ ks.load(null, null);
+ } catch (IOException e) {
+ throw new GeneralSecurityException(e);
+ }
+ ks.setKeyEntry(
+ "ldap_client_auth",
+ this.authenticationKey,
+ "changeit".toCharArray(),
+ new X509Certificate[] {this.authenticationCert});
+
+ final KeyManagerFactory kmf = KeyManagerFactory.getInstance(
+ KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, "changeit".toCharArray());
+ km = kmf.getKeyManagers();
+ }
+ return km;
+ }
+}
diff --git a/src/main/resources/edu/vt/middleware/ldap/LdapCli.args b/src/main/resources/edu/vt/middleware/ldap/LdapCli.args
new file mode 100644
index 0000000..55dbe7e
--- /dev/null
+++ b/src/main/resources/edu/vt/middleware/ldap/LdapCli.args
@@ -0,0 +1,24 @@
+authoritative:whether to require an authoritative source (true|false)
+authtype:type of authentication (none|simple|strong)
+baseDn:base dn
+batchSize:result batch size
+binaryAttributes:space delimited list of binary attributes
+bindCredential:password for the bindDn
+bindDn:fully qualified dn to bind as
+countLimit:maximum number of entries to return
+derefAliases:how to handle aliases (always|never|finding|searching)
+derefLinkFlag:whether to force dereferencing (true|false)
+handlerIgnoreExceptions:comma delimited list of exceptions that the handler should ignore
+hostnameVerifier:fully qualified class name which implements HostnameVerifier
+ignoreCase:whether to ignore case in attribute names (true|false)
+language:preferred language
+ldapUrl:URL that identifies the LDAP server to search
+referral:how to handle referrals (throw|ignore|follow)
+searchResultHandlers:comma delimited list of search result handlers to use
+searchScope:scope of the search (0:object|1:one level|2: subtree)
+ssl:whether to use ssl (true|false)
+sslSocketFactory:fully qualified class name which implements SSLSocketFactory
+timeout: amount of time in milliseconds that connect operations will block
+timeLimit:amount of time in milliseconds that search operations will block
+tls:whether to use tls (true|false)
+typesOnly:whether to return only attribute types (true|false)
diff --git a/src/main/resources/edu/vt/middleware/ldap/LdapCli.examples b/src/main/resources/edu/vt/middleware/ldap/LdapCli.examples
new file mode 100644
index 0000000..9bda9ce
--- /dev/null
+++ b/src/main/resources/edu/vt/middleware/ldap/LdapCli.examples
@@ -0,0 +1,19 @@
+- Search a ldap directory returning the mail attribute in ldif format
+
+ ldapsearch -ldapUrl ldap://directory.vt.edu -baseDn ou=People,dc=vt,dc=edu \
+ -query uupid=dfisher mail
+
+- Search a ldap directory returning all attributes in dsmlv1 format
+
+ ldapsearch -ldapUrl ldap://directory.vt.edu -baseDn ou=People,dc=vt,dc=edu \
+ -dsmlv1 -query uupid=dfisher
+
+- Search a ldap directory as an authenticated user returning all attributes in dsmlv2 format
+
+ ldapsearch -ldapUrl ldap://directory.vt.edu -baseDn ou=People,dc=vt,dc=edu \
+ -bindDn uid=818037,ou=People,dc=vt,dc=edu -tls true \
+ -dsmlv2 -query uupid=dfisher
+
+- Display all the command line options available
+
+ ldapsearch -help
diff --git a/src/main/resources/edu/vt/middleware/ldap/auth/AuthenticatorCli.args b/src/main/resources/edu/vt/middleware/ldap/auth/AuthenticatorCli.args
new file mode 100644
index 0000000..f753e69
--- /dev/null
+++ b/src/main/resources/edu/vt/middleware/ldap/auth/AuthenticatorCli.args
@@ -0,0 +1,32 @@
+authoritative:whether to require an authoritative source (true|false)
+authtype:type of authentication (none|simple|strong)
+baseDn:base dn
+batchSize:result batch size
+binaryAttributes:space delimited list of binary attributes
+bindCredential:password for the bindDn
+bindDn:fully qualified dn to bind as
+countLimit:maximum number of entries to return
+derefAliases:how to handle aliases (always|never|finding|searching)
+derefLinkFlag:whether to force dereferencing (true|false)
+handlerIgnoreExceptions:comma delimited list of exceptions that the handler should ignore
+hostnameVerifier:fully qualified class name which implements HostnameVerifier
+ignoreCase:whether to ignore case in attribute names (true|false)
+language:preferred language
+ldapUrl:URL that identifies the LDAP server to search
+referral:how to handle referrals (throw|ignore|follow)
+searchResultHandlers:comma delimited list of search result handlers to use
+searchScope:scope of the search (0:object|1:one level|2: subtree)
+ssl:whether to use ssl (true|false)
+sslSocketFactory:fully qualified class name which implements SSLSocketFactory
+timeout: amount of time in milliseconds that connect operations will block
+timeLimit:amount of time in milliseconds that search operations will block
+tls:whether to use tls (true|false)
+typesOnly:whether to return only attribute types (true|false)
+authenticationResultHandlers:comma delimited list of authentication result handlers to use
+authorizationFilter:ldap filter to authorize user with
+constructDn:whether to construct the user dn instead of looking it up (true|false)
+credential:password to bind with
+subtreeSearch:whether to lookup user dn with a subtree search (true|false)
+user:combined with userField to lookup user dn
+userField:combined with user to lookup user dn
+userFilter:ldap filter to lookup user dn
diff --git a/src/main/resources/edu/vt/middleware/ldap/auth/AuthenticatorCli.examples b/src/main/resources/edu/vt/middleware/ldap/auth/AuthenticatorCli.examples
new file mode 100644
index 0000000..093d746
--- /dev/null
+++ b/src/main/resources/edu/vt/middleware/ldap/auth/AuthenticatorCli.examples
@@ -0,0 +1,18 @@
+- Authenticate a user and return their ldap entry
+
+ ldapauth -ldapUrl ldap://directory.vt.edu -baseDn ou=People,dc=vt,dc=edu \
+ -tls true -userField uupid
+
+- Authenticate and authorize a user on their accountState attribute
+
+ ldapauth -ldapUrl ldap://directory.vt.edu -baseDn ou=People,dc=vt,dc=edu \
+ -tls true -userField uupid -authorizationFilter accountState=active
+
+- Authenticate a user and return their mail attribute in dsmlv1 format
+
+ ldapauth -ldapUrl ldap://directory.vt.edu -baseDn ou=People,dc=vt,dc=edu \
+ -tls true -userField uupid -dsmlv1 mail
+
+- Display all the command line options available
+
+ ldapauth -help
diff --git a/src/test/java/edu/vt/middleware/ldap/AnyHostnameVerifier.java b/src/test/java/edu/vt/middleware/ldap/AnyHostnameVerifier.java
new file mode 100644
index 0000000..d18ba00
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/AnyHostnameVerifier.java
@@ -0,0 +1,72 @@
+/*
+ $Id: AnyHostnameVerifier.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSession;
+
+/**
+ * <code>AnyHostnameVerifier</code> returns true for any host.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class AnyHostnameVerifier implements HostnameVerifier
+{
+
+
+ /** {@inheritDoc} */
+ public boolean verify(final String hostname, final SSLSession seession)
+ {
+ return true;
+ }
+
+
+ /**
+ * Dummy getter method.
+ *
+ * @return 'foo'
+ */
+ public String getFoo()
+ {
+ return "foo";
+ }
+
+
+ /**
+ * Dummy setter method. Noop.
+ *
+ * @param s <code>String</code>
+ */
+ public void setFoo(final String s) {}
+
+
+ /**
+ * Dummy getter method.
+ *
+ * @return true
+ */
+ public boolean getBar()
+ {
+ return true;
+ }
+
+
+ /**
+ * Dummy setter method. Noop.
+ *
+ * @param b <code>boolean</code>
+ */
+ public void setBar(final boolean b) {}
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/LdapCliTest.java b/src/test/java/edu/vt/middleware/ldap/LdapCliTest.java
new file mode 100644
index 0000000..9226c39
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/LdapCliTest.java
@@ -0,0 +1,104 @@
+/*
+ $Id: LdapCliTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link LdapCli} class.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class LdapCliTest
+{
+
+ /** Entry created for ldap tests. */
+ private static LdapEntry testLdapEntry;
+
+
+ /**
+ * @param ldifFile to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry5" })
+ @BeforeClass(groups = {"ldapclitest"})
+ public void createLdapEntry(final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"ldapclitest"})
+ public void deleteLdapEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param args List of delimited arguments to pass to the CLI.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "cliSearchArgs", "cliSearchResults" })
+ @Test(groups = {"ldapclitest"})
+ public void search(final String args, final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ final PrintStream oldStdOut = System.out;
+ try {
+ final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(outStream));
+
+ LdapCli.main(args.split("\\|"));
+ AssertJUnit.assertEquals(
+ TestUtil.convertLdifToEntry(ldif),
+ TestUtil.convertLdifToEntry(outStream.toString()));
+ } finally {
+ // Restore STDOUT
+ System.setOut(oldStdOut);
+ }
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/LdapConfigTest.java b/src/test/java/edu/vt/middleware/ldap/LdapConfigTest.java
new file mode 100644
index 0000000..5c14b9f
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/LdapConfigTest.java
@@ -0,0 +1,92 @@
+/*
+ $Id: LdapConfigTest.java 1500 2010-08-18 15:01:02Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1500 $
+ Updated: $Date: 2010-08-18 16:01:02 +0100 (Wed, 18 Aug 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.util.Arrays;
+import edu.vt.middleware.ldap.handler.BinarySearchResultHandler;
+import edu.vt.middleware.ldap.handler.EntryDnSearchResultHandler;
+import edu.vt.middleware.ldap.handler.MergeSearchResultHandler;
+import edu.vt.middleware.ldap.handler.RecursiveSearchResultHandler;
+import edu.vt.middleware.ldap.handler.SearchResultHandler;
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link LdapConfig}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1500 $
+ */
+public class LdapConfigTest
+{
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"ldaptest"})
+ public void nullProperties()
+ throws Exception
+ {
+ final Ldap l = new Ldap();
+ l.loadFromProperties(
+ LdapConfigTest.class.getResourceAsStream("/ldap.null.properties"));
+
+ AssertJUnit.assertNull(l.getLdapConfig().getSslSocketFactory());
+ AssertJUnit.assertNull(l.getLdapConfig().getHostnameVerifier());
+ AssertJUnit.assertNull(l.getLdapConfig().getOperationRetryExceptions());
+ AssertJUnit.assertNull(l.getLdapConfig().getSearchResultHandlers());
+ AssertJUnit.assertNull(l.getLdapConfig().getHandlerIgnoreExceptions());
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"ldaptest"})
+ public void parserProperties()
+ throws Exception
+ {
+ final Ldap l = new Ldap();
+ l.loadFromProperties(
+ LdapConfigTest.class.getResourceAsStream("/ldap.parser.properties"));
+
+ AssertJUnit.assertEquals(
+ LdapConfig.SearchScope.OBJECT, l.getLdapConfig().getSearchScope());
+ AssertJUnit.assertEquals(10, l.getLdapConfig().getBatchSize());
+ AssertJUnit.assertEquals(5000, l.getLdapConfig().getTimeLimit());
+ AssertJUnit.assertEquals(8000, l.getLdapConfig().getTimeout());
+ AssertJUnit.assertEquals(
+ "jpegPhoto", l.getLdapConfig().getBinaryAttributes());
+
+ for (SearchResultHandler srh :
+ l.getLdapConfig().getSearchResultHandlers()) {
+ if (RecursiveSearchResultHandler.class.isInstance(srh)) {
+ final RecursiveSearchResultHandler h = (RecursiveSearchResultHandler)
+ srh;
+ AssertJUnit.assertEquals("member", h.getSearchAttribute());
+ AssertJUnit.assertEquals(
+ Arrays.asList(new String[] {"mail", "department"}),
+ Arrays.asList(h.getMergeAttributes()));
+ } else if (MergeSearchResultHandler.class.isInstance(srh)) {
+ final MergeSearchResultHandler h = (MergeSearchResultHandler) srh;
+ AssertJUnit.assertTrue(h.getAllowDuplicates());
+ } else if (BinarySearchResultHandler.class.isInstance(srh)) {
+ final BinarySearchResultHandler h = (BinarySearchResultHandler) srh;
+ AssertJUnit.assertNotNull(h);
+ } else if (EntryDnSearchResultHandler.class.isInstance(srh)) {
+ final EntryDnSearchResultHandler h = (EntryDnSearchResultHandler) srh;
+ AssertJUnit.assertEquals("myDN", h.getDnAttributeName());
+ } else {
+ throw new Exception("Unknown search result handler type " + srh);
+ }
+ }
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/LdapConnStrategyTest.java b/src/test/java/edu/vt/middleware/ldap/LdapConnStrategyTest.java
new file mode 100644
index 0000000..04a74d0
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/LdapConnStrategyTest.java
@@ -0,0 +1,70 @@
+/*
+ $Id: LdapConnStrategyTest.java 1442 2010-07-01 18:05:58Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1442 $
+ Updated: $Date: 2010-07-01 19:05:58 +0100 (Thu, 01 Jul 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import edu.vt.middleware.ldap.handler.ConnectionHandler;
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link ConnectionHandler} with different strategies.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1442 $
+ */
+public class LdapConnStrategyTest
+{
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"ldaptest"})
+ public void connect()
+ throws Exception
+ {
+ final Ldap l = new Ldap();
+ l.loadFromProperties(
+ LdapConnStrategyTest.class.getResourceAsStream("/ldap.conn.properties"));
+
+ AssertJUnit.assertTrue(l.connect());
+ l.close();
+ AssertJUnit.assertTrue(l.connect());
+ l.close();
+ AssertJUnit.assertTrue(l.connect());
+
+ l.getLdapConfig().getConnectionHandler().setConnectionStrategy(
+ ConnectionHandler.ConnectionStrategy.DEFAULT);
+ AssertJUnit.assertTrue(l.connect());
+ l.close();
+ AssertJUnit.assertTrue(l.connect());
+ l.close();
+ AssertJUnit.assertTrue(l.connect());
+
+ l.getLdapConfig().getConnectionHandler().setConnectionStrategy(
+ ConnectionHandler.ConnectionStrategy.ACTIVE_PASSIVE);
+ AssertJUnit.assertTrue(l.connect());
+ l.close();
+ AssertJUnit.assertTrue(l.connect());
+ l.close();
+ AssertJUnit.assertTrue(l.connect());
+
+ l.getLdapConfig().getConnectionHandler().setConnectionStrategy(
+ ConnectionHandler.ConnectionStrategy.RANDOM);
+ AssertJUnit.assertTrue(l.connect());
+ l.close();
+ AssertJUnit.assertTrue(l.connect());
+ l.close();
+ AssertJUnit.assertTrue(l.connect());
+ l.close();
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/LdapConnTest.java b/src/test/java/edu/vt/middleware/ldap/LdapConnTest.java
new file mode 100644
index 0000000..446634b
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/LdapConnTest.java
@@ -0,0 +1,58 @@
+/*
+ $Id: LdapConnTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.Parameters;
+
+/**
+ * Sleeps at the end of all tests and check open connections.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class LdapConnTest
+{
+
+
+ /**
+ * @param host to check for connections with.
+ * @param sleepTime time to sleep for.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "ldapHost", "sleepTime" })
+ @AfterSuite(groups = {"conntest"})
+ public void sleep(final String host, final int sleepTime)
+ throws Exception
+ {
+ Thread.sleep(sleepTime);
+
+ /*
+ * -- expected open connections --
+ * LdapTest: 1
+ * LdapCliTest:0
+ * AuthenticatorTest: 2
+ * AuthenticatorCliTest: 0
+ * LdapResultTest: 0
+ * LdapLoginModuleTest: 0
+ * SessionManagerTest: 1
+ * SearchServletTest: 6
+ * AttributeServletTest: 3
+ */
+ final int openConns = TestUtil.countOpenConnections(host);
+ AssertJUnit.assertEquals(13, openConns);
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/LdapTest.java b/src/test/java/edu/vt/middleware/ldap/LdapTest.java
new file mode 100644
index 0000000..76de5b8
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/LdapTest.java
@@ -0,0 +1,1529 @@
+/*
+ $Id: LdapTest.java 1998 2011-06-20 17:56:35Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1998 $
+ Updated: $Date: 2011-06-20 18:56:35 +0100 (Mon, 20 Jun 2011) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.LimitExceededException;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import javax.naming.SizeLimitExceededException;
+import javax.naming.TimeLimitExceededException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InvalidSearchFilterException;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.Ldap.AttributeModification;
+import edu.vt.middleware.ldap.bean.LdapAttribute;
+import edu.vt.middleware.ldap.bean.LdapAttributes;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import edu.vt.middleware.ldap.handler.AttributeHandler;
+import edu.vt.middleware.ldap.handler.BinaryAttributeHandler;
+import edu.vt.middleware.ldap.handler.BinarySearchResultHandler;
+import edu.vt.middleware.ldap.handler.CaseChangeSearchResultHandler;
+import edu.vt.middleware.ldap.handler.CaseChangeSearchResultHandler.CaseChange;
+import edu.vt.middleware.ldap.handler.EntryDnSearchResultHandler;
+import edu.vt.middleware.ldap.handler.FqdnSearchResultHandler;
+import edu.vt.middleware.ldap.handler.MergeSearchResultHandler;
+import edu.vt.middleware.ldap.handler.RecursiveAttributeHandler;
+import edu.vt.middleware.ldap.handler.RecursiveSearchResultHandler;
+import edu.vt.middleware.ldap.handler.SearchResultHandler;
+import edu.vt.middleware.ldap.ldif.Ldif;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link Ldap}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1998 $
+ */
+public class LdapTest
+{
+
+ /** Invalid search filter. */
+ public static final String INVALID_FILTER = "(cn=not-a-name)";
+
+ /** Entry created for ldap tests. */
+ private static LdapEntry testLdapEntry;
+
+ /** Entry created for ldap tests. */
+ private static LdapEntry specialCharsLdapEntry;
+
+ /** Entries for group tests. */
+ private static Map<String, LdapEntry[]> groupEntries =
+ new HashMap<String, LdapEntry[]>();
+
+ /**
+ * Initialize the map of group entries.
+ */
+ static {
+ for (int i = 2; i <= 5; i++) {
+ groupEntries.put(String.valueOf(i), new LdapEntry[2]);
+ }
+ }
+
+ /** Ldap instance for concurrency testing. */
+ private Ldap singleLdap;
+
+
+ /**
+ * Default constructor.
+ *
+ * @throws Exception if ldap cannot be constructed
+ */
+ public LdapTest()
+ throws Exception
+ {
+ this.singleLdap = TestUtil.createLdap();
+ }
+
+
+ /**
+ * @param ldifFile to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry2" })
+ @BeforeClass(groups = {"ldaptest"})
+ public void createLdapEntry(final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+ }
+
+
+ /**
+ * @param ldifFile2 to create.
+ * @param ldifFile3 to create.
+ * @param ldifFile4 to create.
+ * @param ldifFile5 to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "createGroup2",
+ "createGroup3",
+ "createGroup4",
+ "createGroup5"
+ }
+ )
+ @BeforeClass(groups = {"ldaptest"})
+ public void createGroupEntry(
+ final String ldifFile2,
+ final String ldifFile3,
+ final String ldifFile4,
+ final String ldifFile5)
+ throws Exception
+ {
+ groupEntries.get("2")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile2));
+ groupEntries.get("3")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile3));
+ groupEntries.get("4")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile4));
+ groupEntries.get("5")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile5));
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ for (Map.Entry<String, LdapEntry[]> e : groupEntries.entrySet()) {
+ ldap.create(
+ e.getValue()[0].getDn(),
+ e.getValue()[0].getLdapAttributes().toAttributes());
+ }
+ ldap.close();
+
+ ldap = TestUtil.createLdap();
+ for (Map.Entry<String, LdapEntry[]> e : groupEntries.entrySet()) {
+ while (
+ !ldap.compare(
+ e.getValue()[0].getDn(),
+ new SearchFilter(e.getValue()[0].getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ }
+
+ // setup group relationships
+ ldap.modifyAttributes(
+ groupEntries.get("2")[0].getDn(),
+ AttributeModification.ADD,
+ AttributesFactory.createAttributes(
+ "member",
+ "uugid=group3,ou=test,dc=vt,dc=edu"));
+ ldap.modifyAttributes(
+ groupEntries.get("3")[0].getDn(),
+ AttributeModification.ADD,
+ AttributesFactory.createAttributes(
+ "member",
+ new String[] {
+ "uugid=group4,ou=test,dc=vt,dc=edu",
+ "uugid=group5,ou=test,dc=vt,dc=edu",
+ }));
+ ldap.modifyAttributes(
+ groupEntries.get("4")[0].getDn(),
+ AttributeModification.ADD,
+ AttributesFactory.createAttributes(
+ "member",
+ "uugid=group3,ou=test,dc=vt,dc=edu"));
+ ldap.close();
+ }
+
+
+ /**
+ * @param ldifFile to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createSpecialCharsEntry" })
+ @BeforeClass(groups = {"ldaptest"})
+ public void createSpecialCharsEntry(final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ specialCharsLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ specialCharsLdapEntry.getDn(),
+ specialCharsLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ specialCharsLdapEntry.getDn(),
+ new SearchFilter(specialCharsLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+ }
+
+
+ /**
+ * @param oldDn to rename.
+ * @param newDn to rename to.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "renameOldDn", "renameNewDn" })
+ @AfterClass(groups = {"ldaptest"})
+ public void renameLdapEntry(final String oldDn, final String newDn)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(true);
+ AssertJUnit.assertNotNull(ldap.getAttributes(oldDn));
+ ldap.rename(oldDn, newDn);
+ AssertJUnit.assertNotNull(ldap.getAttributes(newDn));
+ try {
+ ldap.getAttributes(oldDn);
+ AssertJUnit.fail(
+ "Should have thrown NameNotFoundException, no exception thrown");
+ } catch (NameNotFoundException e) {
+ AssertJUnit.assertEquals(NameNotFoundException.class, e.getClass());
+ } catch (Exception e) {
+ AssertJUnit.fail("Should have thrown NameNotFoundException, threw " + e);
+ }
+ ldap.rename(newDn, oldDn);
+ AssertJUnit.assertNotNull(ldap.getAttributes(oldDn));
+ try {
+ ldap.getAttributes(newDn);
+ AssertJUnit.fail(
+ "Should have thrown NameNotFoundException, no exception thrown");
+ } catch (NameNotFoundException e) {
+ AssertJUnit.assertEquals(NameNotFoundException.class, e.getClass());
+ } catch (Exception e) {
+ AssertJUnit.fail("Should have thrown NameNotFoundException, threw " + e);
+ }
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(
+ groups = {"ldaptest"},
+ dependsOnMethods = {"renameLdapEntry"}
+ )
+ public void deleteLdapEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.delete(specialCharsLdapEntry.getDn());
+ ldap.delete(groupEntries.get("2")[0].getDn());
+ ldap.delete(groupEntries.get("3")[0].getDn());
+ ldap.delete(groupEntries.get("4")[0].getDn());
+ ldap.delete(groupEntries.get("5")[0].getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param createNew whether to construct a new ldap instance.
+ *
+ * @return <code>Ldap</code>
+ *
+ * @throws Exception On ldap construction failure.
+ */
+ public Ldap createLdap(final boolean createNew)
+ throws Exception
+ {
+ if (createNew) {
+ return TestUtil.createLdap();
+ }
+ return singleLdap;
+ }
+
+
+ /**
+ * @param dn to compare.
+ * @param filter to compare with.
+ * @param filterArgs to replace args in filter with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "compareDn", "compareFilter", "compareFilterArgs" })
+ @Test(
+ groups = {"ldaptest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void compare(
+ final String dn,
+ final String filter,
+ final String filterArgs)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(false);
+ AssertJUnit.assertFalse(
+ ldap.compare(dn, INVALID_FILTER, filterArgs.split("\\|")));
+ AssertJUnit.assertTrue(ldap.compare(dn, filter, filterArgs.split("\\|")));
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param filterArgs to replace args in filter with.
+ * @param returnAttrs to return from search.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "searchDn",
+ "searchFilter",
+ "searchFilterArgs",
+ "searchReturnAttrs",
+ "searchResults"
+ }
+ )
+ @Test(
+ groups = {"ldaptest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void search(
+ final String dn,
+ final String filter,
+ final String filterArgs,
+ final String returnAttrs,
+ final String ldifFile)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(false);
+
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ final LdapEntry entry = TestUtil.convertLdifToEntry(expected);
+ final LdapEntry shortDnEntry = TestUtil.convertLdifToEntry(expected);
+ shortDnEntry.setDn(
+ shortDnEntry.getDn().substring(0, shortDnEntry.getDn().indexOf(",")));
+
+ final LdapEntry entryDnEntry = TestUtil.convertLdifToEntry(expected);
+ entryDnEntry.getLdapAttributes().addAttribute(
+ "entryDN",
+ entryDnEntry.getDn());
+
+ // test searching
+ Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"));
+ AssertJUnit.assertEquals(
+ entry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+
+ // test searching without handler
+ iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"),
+ new SearchResultHandler[0]);
+ AssertJUnit.assertEquals(
+ shortDnEntry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+
+ // test searching with multiple handlers
+ final EntryDnSearchResultHandler srh = new EntryDnSearchResultHandler();
+ iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"),
+ new FqdnSearchResultHandler(),
+ srh);
+ AssertJUnit.assertEquals(
+ entryDnEntry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+
+ // test that entry dn handler is no-op if attribute name conflicts
+ srh.setDnAttributeName("givenName");
+ iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"),
+ new FqdnSearchResultHandler(),
+ srh);
+ AssertJUnit.assertEquals(
+ entry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({
+ "pagedSearchDn",
+ "pagedSearchFilter",
+ "pagedSearchResults"
+ })
+ @Test(groups = {"ldaptest"})
+ public void pagedSearch(
+ final String dn,
+ final String filter,
+ final String ldifFile)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(true);
+ ldap.getLdapConfig().setPagedResultsSize(1);
+
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ final LdapResult result = TestUtil.convertLdifToResult(expected);
+
+ // test searching
+ final Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter));
+ AssertJUnit.assertEquals(
+ result,
+ TestUtil.convertLdifToResult((new Ldif()).createLdif(iter)));
+
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param filterArgs to replace args in filter with.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "recursiveSearchDn",
+ "recursiveSearchFilter",
+ "recursiveSearchFilterArgs",
+ "recursiveAttributeHandlerResults"
+ }
+ )
+ @Test(groups = {"ldaptest"})
+ public void recursiveAttributeHandlerSearch(
+ final String dn,
+ final String filter,
+ final String filterArgs,
+ final String ldifFile)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(false);
+
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ final LdapEntry entry = TestUtil.convertLdifToEntry(expected);
+
+ // test recursive searching
+ final FqdnSearchResultHandler handler = new FqdnSearchResultHandler();
+ handler.setAttributeHandler(
+ new AttributeHandler[] {new RecursiveAttributeHandler("member")});
+
+ final Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ (String[]) null,
+ handler);
+ AssertJUnit.assertEquals(
+ entry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param filterArgs to replace args in filter with.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "recursiveSearchDn",
+ "recursiveSearchFilter",
+ "recursiveSearchFilterArgs",
+ "recursiveSearchResultHandlerResults"
+ }
+ )
+ @Test(groups = {"ldaptest"})
+ public void recursiveSearchResultHandlerSearch(
+ final String dn,
+ final String filter,
+ final String filterArgs,
+ final String ldifFile)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(false);
+
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ final LdapEntry entry = TestUtil.convertLdifToEntry(expected);
+
+ // test recursive searching
+ final FqdnSearchResultHandler fsrh = new FqdnSearchResultHandler();
+ final RecursiveSearchResultHandler rsrh = new RecursiveSearchResultHandler(
+ "member",
+ new String[] {"uugid", "uid"});
+
+ final Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ (String[]) null,
+ fsrh,
+ rsrh);
+ AssertJUnit.assertEquals(
+ entry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({
+ "mergeSearchDn",
+ "mergeSearchFilter",
+ "mergeSearchResults"
+ })
+ @Test(groups = {"ldaptest"})
+ public void mergeSearch(
+ final String dn,
+ final String filter,
+ final String ldifFile)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(false);
+
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ final LdapEntry entry = TestUtil.convertLdifToEntry(expected);
+
+ // test merge searching
+ final MergeSearchResultHandler handler = new MergeSearchResultHandler();
+
+ final Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter),
+ (String[]) null,
+ new FqdnSearchResultHandler(),
+ handler);
+ AssertJUnit.assertEquals(
+ entry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "mergeDuplicateSearchDn",
+ "mergeDuplicateSearchFilter",
+ "mergeDuplicateSearchResults"
+ }
+ )
+ @Test(groups = {"ldaptest"})
+ public void mergeDuplicateSearch(
+ final String dn,
+ final String filter,
+ final String ldifFile)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(false);
+
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ final LdapEntry entry = TestUtil.convertLdifToEntry(expected);
+
+ // test merge searching
+ final MergeSearchResultHandler handler = new MergeSearchResultHandler();
+ handler.setAllowDuplicates(true);
+
+ final Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter),
+ (String[]) null,
+ new FqdnSearchResultHandler(),
+ handler);
+ AssertJUnit.assertEquals(
+ entry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param returnAttr to return from search.
+ * @param base64Value to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "binarySearchDn",
+ "binarySearchFilter",
+ "binarySearchReturnAttr",
+ "binarySearchResult"
+ }
+ )
+ @Test(groups = {"ldaptest"})
+ public void binarySearch(
+ final String dn,
+ final String filter,
+ final String returnAttr,
+ final String base64Value)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(false);
+
+ // test binary searching
+ Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter),
+ new String[] {returnAttr},
+ new FqdnSearchResultHandler());
+ AssertJUnit.assertNotSame(
+ base64Value,
+ iter.next().getAttributes().get(returnAttr).get());
+
+ iter = ldap.search(
+ dn,
+ new SearchFilter(filter),
+ new String[] {returnAttr},
+ new FqdnSearchResultHandler(),
+ new BinarySearchResultHandler());
+ AssertJUnit.assertEquals(
+ base64Value,
+ iter.next().getAttributes().get(returnAttr).get());
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param filterArgs to replace args in filter with.
+ * @param returnAttrs to return from search.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "searchDn",
+ "searchFilter",
+ "searchFilterArgs",
+ "searchReturnAttrs",
+ "searchResults"
+ }
+ )
+ @Test(groups = {"ldaptest"})
+ public void caseChangeSearch(
+ final String dn,
+ final String filter,
+ final String filterArgs,
+ final String returnAttrs,
+ final String ldifFile)
+ throws Exception
+ {
+ final CaseChangeSearchResultHandler srh =
+ new CaseChangeSearchResultHandler();
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ final Ldap ldap = this.createLdap(true);
+
+ // test no case change
+ final LdapEntry noChangeEntry = TestUtil.convertLdifToEntry(expected);
+ Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"),
+ new FqdnSearchResultHandler(),
+ srh);
+ AssertJUnit.assertEquals(
+ noChangeEntry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+
+ // test lower case attribute values
+ srh.setAttributeValueCaseChange(CaseChange.LOWER);
+ final LdapEntry lcValuesChangeEntry = TestUtil.convertLdifToEntry(expected);
+ for (LdapAttribute la :
+ lcValuesChangeEntry.getLdapAttributes().getAttributes()) {
+ final Set<Object> s = new HashSet<Object>();
+ for (Object o : la.getValues()) {
+ if (o instanceof String) {
+ s.add(((String) o).toLowerCase());
+ }
+ }
+ la.getValues().clear();
+ la.getValues().addAll(s);
+ }
+ iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"),
+ new FqdnSearchResultHandler(),
+ srh);
+ AssertJUnit.assertEquals(
+ lcValuesChangeEntry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+
+ // test upper case attribute names
+ srh.setAttributeValueCaseChange(CaseChange.NONE);
+ srh.setAttributeNameCaseChange(CaseChange.UPPER);
+ final LdapEntry ucNamesChangeEntry = TestUtil.convertLdifToEntry(expected);
+ for (LdapAttribute la :
+ ucNamesChangeEntry.getLdapAttributes().getAttributes()) {
+ la.setName(la.getName().toUpperCase());
+ }
+ iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"),
+ new FqdnSearchResultHandler(),
+ srh);
+ AssertJUnit.assertEquals(
+ ucNamesChangeEntry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+
+ // test lower case everything
+ srh.setAttributeValueCaseChange(CaseChange.LOWER);
+ srh.setAttributeNameCaseChange(CaseChange.LOWER);
+ srh.setDnCaseChange(CaseChange.LOWER);
+ final LdapEntry lcAllChangeEntry = TestUtil.convertLdifToEntry(expected);
+ for (LdapAttribute la :
+ ucNamesChangeEntry.getLdapAttributes().getAttributes()) {
+ lcAllChangeEntry.setDn(lcAllChangeEntry.getDn().toLowerCase());
+ la.setName(la.getName().toLowerCase());
+ final Set<Object> s = new HashSet<Object>();
+ for (Object o : la.getValues()) {
+ if (o instanceof String) {
+ s.add(((String) o).toLowerCase());
+ }
+ }
+ la.getValues().clear();
+ la.getValues().addAll(s);
+ }
+ iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"),
+ new FqdnSearchResultHandler(),
+ srh);
+ AssertJUnit.assertEquals(
+ ucNamesChangeEntry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param resultsSize of search results.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "searchExceptionDn",
+ "searchExceptionFilter",
+ "searchExceptionResultsSize"
+ }
+ )
+ @Test(groups = {"ldaptest"})
+ public void searchWithException(
+ final String dn,
+ final String filter,
+ final int resultsSize)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(true);
+
+ // test exception searching
+ ldap.getLdapConfig().setCountLimit(resultsSize);
+ ldap.getLdapConfig().setHandlerIgnoreExceptions(null);
+
+ try {
+ ldap.search(dn, new SearchFilter("(uugid=*)"));
+ AssertJUnit.fail("Should have thrown SizeLimitExceededException");
+ } catch (NamingException e) {
+ AssertJUnit.assertEquals(SizeLimitExceededException.class, e.getClass());
+ }
+
+ ldap.getLdapConfig().setHandlerIgnoreExceptions(
+ new Class[] {TimeLimitExceededException.class});
+ try {
+ ldap.search(dn, new SearchFilter("(uugid=*)"));
+ AssertJUnit.fail("Should have thrown SizeLimitExceededException");
+ } catch (NamingException e) {
+ AssertJUnit.assertEquals(SizeLimitExceededException.class, e.getClass());
+ }
+
+ ldap.getLdapConfig().setHandlerIgnoreExceptions(
+ new Class[] {LimitExceededException.class});
+
+ final Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter));
+ AssertJUnit.assertEquals(resultsSize, TestUtil.newLdapResult(iter).size());
+
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"ldaptest"})
+ public void searchWithRetry()
+ throws Exception
+ {
+ final RetryLdap ldap = new RetryLdap();
+ ldap.setLdapConfig(this.createLdap(true).getLdapConfig());
+ ldap.getLdapConfig().setOperationRetryExceptions(
+ new Class[] {InvalidSearchFilterException.class});
+
+ // test defaults
+ try {
+ ldap.search(new SearchFilter("(("));
+ } catch (InvalidSearchFilterException e) {
+ AssertJUnit.assertEquals(
+ InvalidSearchFilterException.class,
+ e.getClass());
+ }
+ AssertJUnit.assertEquals(1, ldap.getRetryCount());
+ AssertJUnit.assertEquals(
+ ldap.getRunTime(),
+ Math.min(ldap.getRunTime(), 50));
+
+ // test no retry
+ ldap.reset();
+ ldap.getLdapConfig().setOperationRetry(0);
+
+ try {
+ ldap.search(new SearchFilter("(("));
+ } catch (InvalidSearchFilterException e) {
+ AssertJUnit.assertEquals(
+ InvalidSearchFilterException.class,
+ e.getClass());
+ }
+ AssertJUnit.assertEquals(0, ldap.getRetryCount());
+ AssertJUnit.assertEquals(0, ldap.getRunTime());
+
+ // test no exception
+ ldap.reset();
+ ldap.getLdapConfig().setOperationRetry(1);
+ ldap.getLdapConfig().setOperationRetryExceptions(null);
+
+ try {
+ ldap.search(new SearchFilter("(("));
+ } catch (InvalidSearchFilterException e) {
+ AssertJUnit.assertEquals(
+ InvalidSearchFilterException.class,
+ e.getClass());
+ }
+ AssertJUnit.assertEquals(0, ldap.getRetryCount());
+ AssertJUnit.assertEquals(0, ldap.getRunTime());
+
+ // test retry count and wait time
+ ldap.reset();
+ ldap.getLdapConfig().setOperationRetry(3);
+ ldap.getLdapConfig().setOperationRetryWait(1000);
+ ldap.getLdapConfig().setOperationRetryExceptions(
+ new Class[] {InvalidSearchFilterException.class});
+
+ try {
+ ldap.search(new SearchFilter("(("));
+ } catch (InvalidSearchFilterException e) {
+ AssertJUnit.assertEquals(
+ InvalidSearchFilterException.class,
+ e.getClass());
+ }
+ AssertJUnit.assertEquals(3, ldap.getRetryCount());
+ AssertJUnit.assertTrue(ldap.getRunTime() % 3000 < 30);
+
+ // test backoff interval
+ ldap.reset();
+ ldap.getLdapConfig().setOperationRetryBackoff(2);
+ try {
+ ldap.search(new SearchFilter("(("));
+ } catch (InvalidSearchFilterException e) {
+ AssertJUnit.assertEquals(
+ InvalidSearchFilterException.class,
+ e.getClass());
+ }
+ AssertJUnit.assertEquals(3, ldap.getRetryCount());
+ AssertJUnit.assertTrue(ldap.getRunTime() % 7000 < 70);
+
+ // test infinite retries
+ ldap.reset();
+ ldap.setStopCount(10);
+ ldap.getLdapConfig().setOperationRetry(-1);
+ try {
+ ldap.search(new SearchFilter("(("));
+ } catch (InvalidSearchFilterException e) {
+ AssertJUnit.assertEquals(
+ InvalidSearchFilterException.class,
+ e.getClass());
+ }
+ AssertJUnit.assertEquals(10, ldap.getRetryCount());
+ AssertJUnit.assertTrue(ldap.getRunTime() % 111000 < 111);
+
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param returnAttrs to return from search.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "searchAttributesDn",
+ "searchAttributesFilter",
+ "searchAttributesReturnAttrs",
+ "searchAttributesResults"
+ }
+ )
+ @Test(
+ groups = {"ldaptest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void searchAttributes(
+ final String dn,
+ final String filter,
+ final String returnAttrs,
+ final String ldifFile)
+ throws Exception
+ {
+ final String[] matchAttrs = filter.split("=");
+ final Ldap ldap = this.createLdap(false);
+ // test searching
+ Iterator<SearchResult> iter = ldap.searchAttributes(
+ dn,
+ AttributesFactory.createAttributes(matchAttrs[0], matchAttrs[1]),
+ returnAttrs.split("\\|"));
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ AssertJUnit.assertEquals(
+ TestUtil.convertLdifToEntry(expected),
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ // test searching without handler
+ iter = ldap.searchAttributes(
+ dn,
+ AttributesFactory.createAttributes(matchAttrs[0], matchAttrs[1]),
+ returnAttrs.split("\\|"),
+ new SearchResultHandler[0]);
+
+ final LdapEntry entry = TestUtil.convertLdifToEntry(expected);
+ entry.setDn(entry.getDn().substring(0, entry.getDn().indexOf(",")));
+ AssertJUnit.assertEquals(
+ entry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "specialCharSearchDn",
+ "specialCharSearchFilter",
+ "specialCharSearchResults"
+ }
+ )
+ @Test(groups = {"ldaptest"})
+ public void searchSpecialChars(
+ final String dn,
+ final String filter,
+ final String ldifFile)
+ throws Exception
+ {
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ final LdapEntry entry = TestUtil.convertLdifToEntry(expected);
+ // only remove escaped '/'
+ entry.setDn(entry.getDn().replaceAll("\\\\/", "/"));
+
+ final Ldap ldap = this.createLdap(false);
+
+ final Iterator<SearchResult> iter = ldap.search(
+ dn, new SearchFilter(filter));
+ AssertJUnit.assertEquals(
+ entry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "rewriteSearchDn",
+ "rewriteSearchFilter",
+ "rewriteSearchResults"
+ }
+ )
+ @Test(groups = {"ldaptest"})
+ public void searchRewrite(
+ final String dn,
+ final String filter,
+ final String ldifFile)
+ throws Exception
+ {
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ final LdapEntry entry = TestUtil.convertLdifToEntry(expected);
+ // remove all escaped characters
+ entry.setDn(entry.getDn().replaceAll("\\\\", ""));
+
+ final Ldap ldap = this.createLdap(false);
+
+ final Iterator<SearchResult> iter = ldap.search(
+ dn, new SearchFilter(filter));
+ AssertJUnit.assertEquals(
+ entry,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param results to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "listDn", "listResults" })
+ @Test(groups = {"ldaptest"})
+ public void list(final String dn, final String results)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(true);
+ final Iterator<NameClassPair> iter = ldap.list(dn);
+ final List<String> l = new ArrayList<String>();
+ while (iter.hasNext()) {
+ final NameClassPair ncp = iter.next();
+ l.add(ncp.getName());
+ }
+
+ final List<String> expected = Arrays.asList(results.split("\\|"));
+ AssertJUnit.assertTrue(l.containsAll(expected));
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param results to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "listBindingsDn", "listBindingsResults" })
+ @Test(groups = {"ldaptest"})
+ public void listBindings(final String dn, final String results)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(true);
+ final Iterator<Binding> iter = ldap.listBindings(dn);
+ final List<String> l = new ArrayList<String>();
+ while (iter.hasNext()) {
+ final Binding b = iter.next();
+ if (Context.class.isAssignableFrom(b.getObject().getClass())) {
+ ((Context) b.getObject()).close();
+ }
+ l.add(b.getName());
+ }
+
+ final List<String> expected = Arrays.asList(results.split("\\|"));
+ AssertJUnit.assertTrue(l.containsAll(expected));
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param returnAttrs to return from search.
+ * @param results to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "getAttributesDn",
+ "getAttributesReturnAttrs",
+ "getAttributesResults"
+ }
+ )
+ @Test(
+ groups = {"ldaptest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void getAttributes(
+ final String dn,
+ final String returnAttrs,
+ final String results)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(false);
+ final Attributes attrs = ldap.getAttributes(dn, returnAttrs.split("\\|"));
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(results);
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(attrs));
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param returnAttrs to return from search.
+ * @param results to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "getAttributesBase64Dn",
+ "getAttributesBase64ReturnAttrs",
+ "getAttributesBase64Results"
+ }
+ )
+ @Test(groups = {"ldaptest"})
+ public void getAttributesBase64(
+ final String dn,
+ final String returnAttrs,
+ final String results)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(true);
+ final Attributes attrs = ldap.getAttributes(
+ dn,
+ returnAttrs.split("\\|"),
+ new BinaryAttributeHandler());
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(results);
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(attrs));
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "getSchemaDn", "getSchemaResults" })
+ @Test(groups = {"ldaptest"})
+ public void getSchema(final String dn, final String ldifFile)
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(true);
+ final Iterator<SearchResult> iter = ldap.getSchema(dn);
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ AssertJUnit.assertEquals(
+ TestUtil.convertLdifToResult(expected),
+ TestUtil.convertLdifToResult((new Ldif()).createLdif(iter)));
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(
+ groups = {"ldaptest"},
+ enabled = false
+ )
+ public void getSaslMechanisms()
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(true);
+ ldap.getSaslMechanisms();
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(
+ groups = {"ldaptest"},
+ enabled = false
+ )
+ public void getSupportedControls()
+ throws Exception
+ {
+ final Ldap ldap = this.createLdap(true);
+ ldap.getSupportedControls();
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to modify.
+ * @param attrs to add.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "addAttributeDn", "addAttributeAttribute" })
+ @Test(groups = {"ldaptest"})
+ public void addAttribute(final String dn, final String attrs)
+ throws Exception
+ {
+ final LdapAttribute expected = TestUtil.convertStringToAttributes(attrs)
+ .getAttributes().iterator().next();
+ final Ldap ldap = this.createLdap(true);
+ ldap.modifyAttributes(
+ dn,
+ AttributeModification.ADD,
+ AttributesFactory.createAttributes(
+ expected.getName(),
+ expected.getValues().toArray()));
+
+ final Attributes a = ldap.getAttributes(
+ dn,
+ new String[] {expected.getName()});
+ AssertJUnit.assertEquals(
+ expected,
+ TestUtil.newLdapAttributes(a).getAttribute(expected.getName()));
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to modify.
+ * @param attrs to add.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "addAttributesDn", "addAttributesAttributes" })
+ @Test(groups = {"ldaptest"})
+ public void addAttributes(final String dn, final String attrs)
+ throws Exception
+ {
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(attrs);
+ final Ldap ldap = this.createLdap(true);
+ final ModificationItem[] mods = new ModificationItem[expected.size()];
+ int i = 0;
+ for (LdapAttribute la : expected.getAttributes()) {
+ mods[i] = new ModificationItem(
+ DirContext.ADD_ATTRIBUTE,
+ la.toAttribute());
+ i++;
+ }
+ ldap.modifyAttributes(dn, mods);
+
+ final Attributes a = ldap.getAttributes(dn, expected.getAttributeNames());
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(a));
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to modify.
+ * @param attrs to replace.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "replaceAttributeDn", "replaceAttributeAttribute" })
+ @Test(
+ groups = {"ldaptest"},
+ dependsOnMethods = {"addAttribute"}
+ )
+ public void replaceAttribute(final String dn, final String attrs)
+ throws Exception
+ {
+ final LdapAttribute expected = TestUtil.convertStringToAttributes(attrs)
+ .getAttributes().iterator().next();
+ final Ldap ldap = this.createLdap(true);
+ ldap.modifyAttributes(
+ dn,
+ AttributeModification.REPLACE,
+ AttributesFactory.createAttributes(
+ expected.getName(),
+ expected.getValues().toArray()));
+
+ final Attributes a = ldap.getAttributes(
+ dn,
+ new String[] {expected.getName()});
+ AssertJUnit.assertEquals(
+ expected,
+ TestUtil.newLdapAttributes(a).getAttribute(expected.getName()));
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to modify.
+ * @param attrs to replace.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "replaceAttributesDn", "replaceAttributesAttributes" })
+ @Test(
+ groups = {"ldaptest"},
+ dependsOnMethods = {"addAttributes"}
+ )
+ public void replaceAttributes(final String dn, final String attrs)
+ throws Exception
+ {
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(attrs);
+ final Ldap ldap = this.createLdap(true);
+ final ModificationItem[] mods = new ModificationItem[expected.size()];
+ int i = 0;
+ for (LdapAttribute la : expected.getAttributes()) {
+ mods[i] = new ModificationItem(
+ DirContext.REPLACE_ATTRIBUTE,
+ la.toAttribute());
+ i++;
+ }
+ ldap.modifyAttributes(dn, mods);
+
+ final Attributes a = ldap.getAttributes(dn, expected.getAttributeNames());
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(a));
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to modify.
+ * @param attrs to remove.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "removeAttributeDn", "removeAttributeAttribute" })
+ @Test(
+ groups = {"ldaptest"},
+ dependsOnMethods = {"replaceAttribute"}
+ )
+ public void removeAttribute(final String dn, final String attrs)
+ throws Exception
+ {
+ final LdapAttribute expected = TestUtil.convertStringToAttributes(attrs)
+ .getAttributes().iterator().next();
+ final LdapAttribute remove = TestUtil.convertStringToAttributes(attrs)
+ .getAttributes().iterator().next();
+ remove.getValues().remove("Unit Test User");
+ expected.getValues().remove("Best Test User");
+
+ final Ldap ldap = this.createLdap(true);
+ ldap.modifyAttributes(
+ dn,
+ AttributeModification.REMOVE,
+ AttributesFactory.createAttributes(
+ remove.getName(),
+ remove.getValues().toArray()));
+
+ final Attributes a = ldap.getAttributes(
+ dn,
+ new String[] {expected.getName()});
+ AssertJUnit.assertEquals(
+ expected,
+ TestUtil.newLdapAttributes(a).getAttribute(expected.getName()));
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to modify.
+ * @param attrs to remove.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "removeAttributesDn", "removeAttributesAttributes" })
+ @Test(
+ groups = {"ldaptest"},
+ dependsOnMethods = {"replaceAttributes"}
+ )
+ public void removeAttributes(final String dn, final String attrs)
+ throws Exception
+ {
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(attrs);
+ final LdapAttributes remove = TestUtil.convertStringToAttributes(attrs);
+
+ final String[] attrsName = remove.getAttributeNames();
+ remove.getAttributes().remove(remove.getAttribute(attrsName[0]));
+ expected.getAttributes().remove(expected.getAttribute(attrsName[1]));
+
+ final Ldap ldap = this.createLdap(true);
+ final ModificationItem[] mods = new ModificationItem[expected.size()];
+ int i = 0;
+ for (LdapAttribute la : remove.getAttributes()) {
+ mods[i] = new ModificationItem(
+ DirContext.REMOVE_ATTRIBUTE,
+ la.toAttribute());
+ i++;
+ }
+ ldap.modifyAttributes(dn, mods);
+
+ final Attributes a = ldap.getAttributes(dn, expected.getAttributeNames());
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(a));
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"ldaptest"})
+ public void saslExternalConnect()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSaslExternalLdap();
+ AssertJUnit.assertTrue(ldap.connect());
+ ldap.close();
+ }
+
+
+ /**
+ * @param krb5Realm kerberos realm
+ * @param krb5Kdc kerberos kdc
+ * @param dn to search on.
+ * @param filter to search with.
+ * @param filterArgs to replace args in filter with.
+ * @param returnAttrs to return from search.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "krb5Realm",
+ "krb5Kdc",
+ "gssApiSearchDn",
+ "gssApiSearchFilter",
+ "gssApiSearchFilterArgs",
+ "gssApiSearchReturnAttrs",
+ "gssApiSearchResults"
+ }
+ )
+ @Test(groups = {"ldaptest"})
+ public void gssApiSearch(
+ final String krb5Realm,
+ final String krb5Kdc,
+ final String dn,
+ final String filter,
+ final String filterArgs,
+ final String returnAttrs,
+ final String ldifFile)
+ throws Exception
+ {
+ System.setProperty(
+ "java.security.auth.login.config",
+ "src/test/resources/ldap_jaas.config");
+ System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
+ System.setProperty("java.security.krb5.realm", krb5Realm);
+ System.setProperty("java.security.krb5.kdc", krb5Kdc);
+
+ final Ldap ldap = TestUtil.createGssApiLdap();
+ final Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"));
+ final String expected = TestUtil.readFileIntoString(ldifFile);
+ AssertJUnit.assertEquals(
+ TestUtil.convertLdifToEntry(expected),
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ ldap.close();
+
+ System.clearProperty("java.security.auth.login.config");
+ System.clearProperty("javax.security.auth.useSubjectCredsOnly");
+ System.clearProperty("java.security.krb5.realm");
+ System.clearProperty("java.security.krb5.kdc");
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/RetryLdap.java b/src/test/java/edu/vt/middleware/ldap/RetryLdap.java
new file mode 100644
index 0000000..00db6b0
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/RetryLdap.java
@@ -0,0 +1,100 @@
+/*
+ $Id: RetryLdap.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+
+/**
+ * <code>RetyLdap</code> provides a wrapper class for testing {@link
+ * #operationRetry()}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class RetryLdap extends Ldap
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = 4247614583961731974L;
+
+ /** retry counter. */
+ private int retryCount;
+
+ /** run time counter. */
+ private long runTime;
+
+ /** stop counter. */
+ private int stopCount;
+
+
+ /**
+ * Returns the retry count.
+ *
+ * @return retry count
+ */
+ public int getRetryCount()
+ {
+ return this.retryCount;
+ }
+
+
+ /**
+ * Returns the run time counter.
+ *
+ * @return run time
+ */
+ public long getRunTime()
+ {
+ return this.runTime;
+ }
+
+
+ /**
+ * Sets the count at which to stop retries.
+ *
+ * @param i stop count
+ */
+ public void setStopCount(final int i)
+ {
+ this.stopCount = i;
+ }
+
+
+ /** Resets all the counters. */
+ public void reset()
+ {
+ this.retryCount = 0;
+ this.runTime = 0;
+ this.stopCount = 0;
+ }
+
+
+ /** {@inheritDoc} */
+ protected void operationRetry(
+ final LdapContext ctx,
+ final NamingException e,
+ final int count)
+ throws NamingException
+ {
+ this.retryCount = count;
+
+ final long t = System.currentTimeMillis();
+ super.operationRetry(ctx, e, count);
+ this.runTime += System.currentTimeMillis() - t;
+ if (this.stopCount > 0 && this.retryCount == this.stopCount) {
+ throw e;
+ }
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/SpringTest.java b/src/test/java/edu/vt/middleware/ldap/SpringTest.java
new file mode 100644
index 0000000..e26e0da
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/SpringTest.java
@@ -0,0 +1,58 @@
+/*
+ $Id: SpringTest.java 1500 2010-08-18 15:01:02Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1500 $
+ Updated: $Date: 2010-08-18 16:01:02 +0100 (Wed, 18 Aug 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import edu.vt.middleware.ldap.pool.BlockingLdapPool;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for Spring integration.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1500 $
+ */
+public class SpringTest
+{
+
+
+ /**
+ * Attempts to load all Spring application context XML files to
+ * verify proper wiring.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(groups = {"ldaptest"})
+ public void testSpringWiring()
+ throws Exception
+ {
+ final ClassPathXmlApplicationContext context =
+ new ClassPathXmlApplicationContext(new String[] {
+ "/spring-context.xml",
+ });
+ AssertJUnit.assertTrue(context.getBeanDefinitionCount() > 0);
+ final Ldap l = (Ldap) context.getBean("ldap");
+ l.close();
+
+ final ClassPathXmlApplicationContext poolContext =
+ new ClassPathXmlApplicationContext(new String[] {
+ "/spring-pool-context.xml",
+ });
+ AssertJUnit.assertTrue(poolContext.getBeanDefinitionCount() > 0);
+ final BlockingLdapPool lp =
+ (BlockingLdapPool) poolContext.getBean("ldapPool");
+ lp.close();
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/TestUtil.java b/src/test/java/edu/vt/middleware/ldap/TestUtil.java
new file mode 100644
index 0000000..4c9cef8
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/TestUtil.java
@@ -0,0 +1,409 @@
+/*
+ $Id: TestUtil.java 1876 2011-04-05 14:36:44Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1876 $
+ Updated: $Date: 2011-04-05 15:36:44 +0100 (Tue, 05 Apr 2011) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.auth.Authenticator;
+import edu.vt.middleware.ldap.auth.NoopDnResolver;
+import edu.vt.middleware.ldap.bean.LdapAttributes;
+import edu.vt.middleware.ldap.bean.LdapBeanProvider;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import edu.vt.middleware.ldap.ldif.Ldif;
+import org.testng.annotations.DataProvider;
+
+/**
+ * Common methods for ldap tests.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1876 $
+ */
+public final class TestUtil
+{
+
+ /** Location of the hostname in the output of netstat. */
+ public static final int NETSTAT_HOST_INDEX = 4;
+
+
+ /**
+ * @return Test configuration.
+ *
+ * @throws Exception On test data generation failure.
+ */
+ @DataProvider(name = "setup-ldap")
+ public static Ldap createSetupLdap()
+ throws Exception
+ {
+ final Ldap l = new Ldap();
+ l.loadFromProperties(
+ TestUtil.class.getResourceAsStream("/ldap.setup.properties"));
+ return l;
+ }
+
+
+ /**
+ * @return Test configuration.
+ *
+ * @throws Exception On test data generation failure.
+ */
+ @DataProvider(name = "ldap")
+ public static Ldap createLdap()
+ throws Exception
+ {
+ final Ldap l = new Ldap();
+ l.loadFromProperties();
+ return l;
+ }
+
+
+ /**
+ * @return Test configuration.
+ *
+ * @throws Exception On test data generation failure.
+ */
+ @DataProvider(name = "sasl-external-ldap")
+ public static Ldap createSaslExternalLdap()
+ throws Exception
+ {
+ final Ldap l = new Ldap();
+ l.loadFromProperties(
+ TestUtil.class.getResourceAsStream("/ldap.sasl.properties"));
+ return l;
+ }
+
+
+ /**
+ * @return Test configuration.
+ *
+ * @throws Exception On test data generation failure.
+ */
+ @DataProvider(name = "gss-api-ldap")
+ public static Ldap createGssApiLdap()
+ throws Exception
+ {
+ final Ldap l = new Ldap();
+ l.loadFromProperties(
+ TestUtil.class.getResourceAsStream("/ldap.gssapi.properties"));
+ return l;
+ }
+
+
+ /**
+ * @return Test configuration.
+ *
+ * @throws Exception On test data generation failure.
+ */
+ @DataProvider(name = "ssl-auth")
+ public static Authenticator createSSLAuthenticator()
+ throws Exception
+ {
+ final Authenticator a = new Authenticator();
+ a.loadFromProperties(
+ TestUtil.class.getResourceAsStream("/ldap.ssl.properties"));
+ return a;
+ }
+
+
+ /**
+ * @return Test configuration.
+ *
+ * @throws Exception On test data generation failure.
+ */
+ @DataProvider(name = "ssl-dn-auth")
+ public static Authenticator createSSLDnAuthenticator()
+ throws Exception
+ {
+ final Authenticator a = new Authenticator();
+ a.loadFromProperties(
+ TestUtil.class.getResourceAsStream("/ldap.ssl.properties"));
+ a.getAuthenticatorConfig().setDnResolver(new NoopDnResolver());
+ return a;
+ }
+
+
+ /**
+ * @return Test configuration.
+ *
+ * @throws Exception On test data generation failure.
+ */
+ @DataProvider(name = "tls-auth")
+ public static Authenticator createTLSAuthenticator()
+ throws Exception
+ {
+ final Authenticator a = new Authenticator();
+ a.loadFromProperties(
+ TestUtil.class.getResourceAsStream("/ldap.tls.properties"));
+ return a;
+ }
+
+
+ /**
+ * @return Test configuration.
+ *
+ * @throws Exception On test data generation failure.
+ */
+ @DataProvider(name = "tls-dn-auth")
+ public static Authenticator createTLSDnAuthenticator()
+ throws Exception
+ {
+ final Authenticator a = new Authenticator();
+ a.loadFromProperties(
+ TestUtil.class.getResourceAsStream("/ldap.tls.properties"));
+ a.getAuthenticatorConfig().setDnResolver(new NoopDnResolver());
+ return a;
+ }
+
+
+ /**
+ * @return Test configuration.
+ *
+ * @throws Exception On test data generation failure.
+ */
+ @DataProvider(name = "digest-md5-auth")
+ public static Authenticator createDigestMD5Authenticator()
+ throws Exception
+ {
+ final Authenticator a = new Authenticator();
+ a.loadFromProperties(
+ TestUtil.class.getResourceAsStream("/ldap.digest-md5.properties"));
+ a.getAuthenticatorConfig().setDnResolver(new NoopDnResolver());
+ return a;
+ }
+
+
+ /**
+ * @return Test configuration.
+ *
+ * @throws Exception On test data generation failure.
+ */
+ @DataProvider(name = "cram-md5-auth")
+ public static Authenticator createCramMD5Authenticator()
+ throws Exception
+ {
+ final Authenticator a = new Authenticator();
+ a.loadFromProperties(
+ TestUtil.class.getResourceAsStream("/ldap.cram-md5.properties"));
+ a.getAuthenticatorConfig().setDnResolver(new NoopDnResolver());
+ return a;
+ }
+
+
+ /**
+ * Reads a file on the classpath into a reader.
+ *
+ * @param filename to open.
+ *
+ * @return reader.
+ *
+ * @throws Exception If file cannot be read.
+ */
+ public static BufferedReader readFile(final String filename)
+ throws Exception
+ {
+ return
+ new BufferedReader(
+ new InputStreamReader(TestUtil.class.getResourceAsStream(filename)));
+ }
+
+
+ /**
+ * Reads a file on the classpath into a string.
+ *
+ * @param filename to open.
+ *
+ * @return string.
+ *
+ * @throws Exception If file cannot be read.
+ */
+ public static String readFileIntoString(final String filename)
+ throws Exception
+ {
+ final StringBuffer result = new StringBuffer();
+ final BufferedReader br = readFile(filename);
+ try {
+ String line;
+ while ((line = br.readLine()) != null) {
+ result.append(line).append(System.getProperty("line.separator"));
+ }
+ } finally {
+ br.close();
+ }
+ return result.toString();
+ }
+
+
+ /**
+ * Creates a new <code>LdapResult</code> with the supplied <code>
+ * Iterator</code> of search results.
+ *
+ * @param iter <code>Iterator</code> of search results
+ *
+ * @return <code>LdapResult</code>
+ *
+ * @throws Exception if search results cannot be read
+ */
+ public static LdapResult newLdapResult(final Iterator<SearchResult> iter)
+ throws Exception
+ {
+ final LdapResult lr = LdapBeanProvider.getLdapBeanFactory().newLdapResult();
+ lr.addEntries(iter);
+ return lr;
+ }
+
+
+ /**
+ * Converts a ldif to a <code>LdapResult</code>.
+ *
+ * @param ldif to convert.
+ *
+ * @return LdapResult.
+ *
+ * @throws Exception if ldif cannot be read
+ */
+ public static LdapResult convertLdifToResult(final String ldif)
+ throws Exception
+ {
+ return (new Ldif()).importLdifToLdapResult(new StringReader(ldif));
+ }
+
+
+ /**
+ * Creates a new <code>LdapEntry</code> with the supplied <code>
+ * SearchResult</code>.
+ *
+ * @param sr <code>SearchResult</code>
+ *
+ * @return <code>LdapEntry</code>
+ *
+ * @throws Exception if search result cannot be read
+ */
+ public static LdapEntry newLdapEntry(final SearchResult sr)
+ throws Exception
+ {
+ final LdapEntry le = LdapBeanProvider.getLdapBeanFactory().newLdapEntry();
+ le.setEntry(sr);
+ return le;
+ }
+
+
+ /**
+ * Converts a ldif to a <code>LdapEntry</code>.
+ *
+ * @param ldif to convert.
+ *
+ * @return LdapEntry.
+ *
+ * @throws Exception if ldif cannot be read
+ */
+ public static LdapEntry convertLdifToEntry(final String ldif)
+ throws Exception
+ {
+ final LdapResult lr =
+ (new Ldif()).importLdifToLdapResult(new StringReader(ldif));
+ if (lr.size() == 1) {
+ return lr.getEntries().iterator().next();
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Creates a new <code>LdapAttributes</code> with the supplied <code>
+ * Attributes</code>.
+ *
+ * @param attrs <code>Attributes</code>
+ *
+ * @return <code>LdapAttributes</code>
+ *
+ * @throws Exception if attributes cannot be read
+ */
+ public static LdapAttributes newLdapAttributes(final Attributes attrs)
+ throws Exception
+ {
+ final LdapAttributes la = LdapBeanProvider.getLdapBeanFactory()
+ .newLdapAttributes();
+ la.addAttributes(attrs);
+ return la;
+ }
+
+
+ /**
+ * Converts a string of the form: givenName=John|sn=Doe into a ldap attributes
+ * object.
+ *
+ * @param attrs to convert.
+ *
+ * @return LdapAttributes.
+ */
+ public static LdapAttributes convertStringToAttributes(final String attrs)
+ {
+ final LdapAttributes la = LdapBeanProvider.getLdapBeanFactory()
+ .newLdapAttributes();
+ final String[] s = attrs.split("\\|");
+ for (int i = 0; i < s.length; i++) {
+ final String[] nameValuePairs = s[i].trim().split("=", 2);
+ if (la.getAttribute(nameValuePairs[0]) != null) {
+ la.getAttribute(nameValuePairs[0]).getValues().add(nameValuePairs[1]);
+ } else {
+ la.addAttribute(nameValuePairs[0], nameValuePairs[1]);
+ }
+ }
+ return la;
+ }
+
+
+ /**
+ * Returns the number of open connections to the supplied host. Uses 'netstat
+ * -al' to uncover open sockets.
+ *
+ * @param host host to look for.
+ *
+ * @return number of open connections.
+ *
+ * @throws IOException if the process cannot be run
+ */
+ public static int countOpenConnections(final String host)
+ throws IOException
+ {
+ final String[] cmd = new String[] {"netstat", "-al"};
+ final Process p = new ProcessBuilder(cmd).start();
+ final BufferedReader br = new BufferedReader(
+ new InputStreamReader(p.getInputStream()));
+ String line;
+ final List<String> openConns = new ArrayList<String>();
+ while ((line = br.readLine()) != null) {
+ if (line.matches(".*ESTABLISHED$")) {
+ final String s = line.split("\\s+")[NETSTAT_HOST_INDEX];
+ openConns.add(s.substring(0, s.lastIndexOf(".")));
+ }
+ }
+
+ int count = 0;
+ for (String o : openConns) {
+ if (o.contains(host)) {
+ count++;
+ }
+ }
+ return count;
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/WikiCodeTest.java b/src/test/java/edu/vt/middleware/ldap/WikiCodeTest.java
new file mode 100644
index 0000000..329835a
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/WikiCodeTest.java
@@ -0,0 +1,490 @@
+/*
+ $Id: WikiCodeTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap;
+
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
+import java.util.Iterator;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.auth.Authenticator;
+import edu.vt.middleware.ldap.auth.AuthenticatorConfig;
+import edu.vt.middleware.ldap.dsml.Dsmlv1;
+import edu.vt.middleware.ldap.handler.AttributeHandler;
+import edu.vt.middleware.ldap.handler.BinaryAttributeHandler;
+import edu.vt.middleware.ldap.handler.EntryDnSearchResultHandler;
+import edu.vt.middleware.ldap.handler.FqdnSearchResultHandler;
+import edu.vt.middleware.ldap.handler.SearchResultHandler;
+import edu.vt.middleware.ldap.ldif.Ldif;
+import edu.vt.middleware.ldap.pool.BlockingLdapPool;
+import edu.vt.middleware.ldap.pool.BlockingTimeoutException;
+import edu.vt.middleware.ldap.pool.CloseLdapPassivator;
+import edu.vt.middleware.ldap.pool.CompareLdapValidator;
+import edu.vt.middleware.ldap.pool.ConnectLdapActivator;
+import edu.vt.middleware.ldap.pool.DefaultLdapFactory;
+import edu.vt.middleware.ldap.pool.LdapActivationException;
+import edu.vt.middleware.ldap.pool.LdapPoolConfig;
+import edu.vt.middleware.ldap.pool.LdapPoolException;
+import edu.vt.middleware.ldap.pool.LdapValidationException;
+import edu.vt.middleware.ldap.pool.PoolInterruptedException;
+import edu.vt.middleware.ldap.pool.SharedLdapPool;
+import edu.vt.middleware.ldap.pool.SoftLimitLdapPool;
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for wiki sample code.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class WikiCodeTest
+{
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleCompare()
+ throws Exception
+ {
+ final Ldap ldap = new Ldap(
+ new LdapConfig("ldap://directory.vt.edu:389", "ou=People,dc=vt,dc=edu"));
+ if (
+ ldap.compare(
+ "uid=818037,ou=People,dc=vt,dc=edu",
+ new SearchFilter("mail=dfisher at vt.edu"))) {
+ System.out.println("Compare succeeded");
+ } else {
+ System.out.println("Compare failed");
+ }
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleSubtreeSearch()
+ throws Exception
+ {
+ final Ldap ldap = new Ldap(
+ new LdapConfig("ldap://directory.vt.edu/dc=vt,dc=edu"));
+ (new Ldif()).outputLdif(
+ ldap.search(new SearchFilter("sn=Fisher")),
+ new BufferedWriter(new OutputStreamWriter(System.out)));
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleAttributeSearch()
+ throws Exception
+ {
+ final Ldap ldap = new Ldap(
+ new LdapConfig("ldap://directory.vt.edu", "ou=People,dc=vt,dc=edu"));
+ (new Dsmlv1()).outputDsml(
+ ldap.searchAttributes(
+ AttributesFactory.createAttributes("mail", "dfisher at vt.edu"),
+ new String[] {"sn", "givenName"}),
+ new BufferedWriter(new OutputStreamWriter(System.out)));
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleAuthentication()
+ throws Exception
+ {
+ final AuthenticatorConfig config = new AuthenticatorConfig(
+ "ldap://authn.directory.vt.edu",
+ "ou=People,dc=vt,dc=edu");
+ config.setTls(true);
+ // attribute to search for user with
+ config.setUserField(new String[] {"uid", "mail"});
+
+ final Authenticator auth = new Authenticator(config);
+ if (auth.authenticate("user", "credential")) {
+ System.out.println("Authentication succeeded");
+ } else {
+ System.out.println("Authentication failed");
+ }
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleAuthorization()
+ throws Exception
+ {
+ final AuthenticatorConfig config = new AuthenticatorConfig(
+ "ldap://authn.directory.vt.edu",
+ "ou=People,dc=vt,dc=edu");
+ config.setTls(true);
+ // attribute to search for user with
+ config.setUserFilter("(|(uid={0})(mail={0}))");
+
+ final Authenticator auth = new Authenticator(config);
+ if (
+ auth.authenticate(
+ "user",
+ "credential",
+ new SearchFilter("eduPersonAffiliation=staff"))) {
+ System.out.println("Authentication/Authorization succeeded");
+ } else {
+ System.out.println("Authentication/Authorization failed");
+ }
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void samplePooling()
+ throws Exception
+ {
+ final DefaultLdapFactory factory = new DefaultLdapFactory(
+ new LdapConfig("ldap://directory.vt.edu/ou=People,dc=vt,dc=edu"));
+ final SoftLimitLdapPool pool = new SoftLimitLdapPool(factory);
+ pool.initialize();
+
+ Ldap ldap = null;
+ try {
+ ldap = pool.checkOut();
+
+ final Iterator<SearchResult> i = ldap.search(
+ new SearchFilter("givenName=Daniel"),
+ new String[] {"uid", "mail"});
+
+ AssertJUnit.assertTrue(i.hasNext());
+
+ } catch (LdapPoolException e) {
+ e.printStackTrace();
+ } finally {
+ pool.checkIn(ldap);
+ }
+
+ pool.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleNoHandler()
+ throws Exception
+ {
+ final Ldap ldap = new Ldap(
+ new LdapConfig("ldap://directory.vt.edu:389", "ou=People,dc=vt,dc=edu"));
+ final Iterator<SearchResult> iter = ldap.search(
+ "ou=People,dc=vt,dc=edu",
+ new SearchFilter("sn=Fisher"),
+ new String[] {"givenName", "mail"},
+ (SearchResultHandler[]) null);
+
+ AssertJUnit.assertTrue(iter.hasNext());
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleBinaryAttributeHandler()
+ throws Exception
+ {
+ final Ldap ldap = new Ldap(
+ new LdapConfig("ldap://directory.vt.edu:389", "ou=People,dc=vt,dc=edu"));
+ final Attributes attrs = ldap.getAttributes(
+ "uid=818037,ou=People,dc=vt,dc=edu",
+ null,
+ new BinaryAttributeHandler());
+
+ AssertJUnit.assertTrue(attrs.size() > 0);
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleSearchBinaryAttributeHandler()
+ throws Exception
+ {
+ final Ldap ldap = new Ldap(
+ new LdapConfig("ldap://directory.vt.edu:389", "ou=People,dc=vt,dc=edu"));
+ final FqdnSearchResultHandler handler = new FqdnSearchResultHandler();
+ handler.setAttributeHandler(
+ new AttributeHandler[] {new BinaryAttributeHandler()});
+
+ final Iterator<SearchResult> iter = ldap.search(
+ "ou=People,dc=vt,dc=edu",
+ new SearchFilter("sn=Fisher"),
+ (String[]) null,
+ handler);
+
+ AssertJUnit.assertTrue(iter.hasNext());
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleEntryDnHandler()
+ throws Exception
+ {
+ final LdapConfig config = new LdapConfig(
+ "ldap://directory.vt.edu:389",
+ "ou=People,dc=vt,dc=edu");
+ config.setSearchResultHandlers(
+ new SearchResultHandler[] {
+ new FqdnSearchResultHandler(),
+ new EntryDnSearchResultHandler(),
+ });
+
+ final Ldap ldap = new Ldap(config);
+ final Iterator<SearchResult> iter = ldap.search(
+ new SearchFilter("sn=Fisher"));
+
+ AssertJUnit.assertTrue(iter.hasNext());
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleBlockingLdapPool()
+ throws Exception
+ {
+ final DefaultLdapFactory factory = new DefaultLdapFactory(
+ new LdapConfig("ldap://directory.vt.edu:389", "ou=People,dc=vt,dc=edu"));
+ final BlockingLdapPool pool = new BlockingLdapPool(factory);
+ // wait for 5sec for an object to be available
+ pool.setBlockWaitTime(5000);
+ pool.initialize();
+
+ Ldap ldap = null;
+ try {
+ ldap = pool.checkOut();
+
+ final Iterator<SearchResult> i = ldap.search(
+ new SearchFilter("givenName=Daniel"),
+ new String[] {"uid", "mail"});
+
+ AssertJUnit.assertTrue(i.hasNext());
+
+ } catch (BlockingTimeoutException e) {
+ e.printStackTrace();
+ } catch (PoolInterruptedException e) {
+ e.printStackTrace();
+ } catch (LdapPoolException e) {
+ e.printStackTrace();
+ } finally {
+ pool.checkIn(ldap);
+ }
+
+ pool.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleSoftLimitLdapPool()
+ throws Exception
+ {
+ final DefaultLdapFactory factory = new DefaultLdapFactory(
+ new LdapConfig("ldap://directory.vt.edu:389", "ou=People,dc=vt,dc=edu"));
+ final SoftLimitLdapPool pool = new SoftLimitLdapPool(factory);
+ // wait for 5sec for an object to be available
+ pool.setBlockWaitTime(5000);
+ pool.initialize();
+
+ Ldap ldap = null;
+ try {
+ ldap = pool.checkOut();
+
+ final Iterator<SearchResult> i = ldap.search(
+ new SearchFilter("givenName=Daniel"),
+ new String[] {"uid", "mail"});
+
+ AssertJUnit.assertTrue(i.hasNext());
+
+ } catch (BlockingTimeoutException e) {
+ e.printStackTrace();
+ } catch (PoolInterruptedException e) {
+ e.printStackTrace();
+ } catch (LdapPoolException e) {
+ e.printStackTrace();
+ } finally {
+ pool.checkIn(ldap);
+ }
+
+ pool.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleSharedLdapPool()
+ throws Exception
+ {
+ final DefaultLdapFactory factory = new DefaultLdapFactory(
+ new LdapConfig("ldap://directory.vt.edu:389", "ou=People,dc=vt,dc=edu"));
+ final SharedLdapPool pool = new SharedLdapPool(factory);
+ pool.initialize();
+
+ Ldap ldap = null;
+ try {
+ ldap = pool.checkOut();
+
+ final Iterator<SearchResult> i = ldap.search(
+ new SearchFilter("givenName=Daniel"),
+ new String[] {"uid", "mail"});
+
+ AssertJUnit.assertTrue(i.hasNext());
+
+ } catch (LdapPoolException e) {
+ e.printStackTrace();
+ } finally {
+ pool.checkIn(ldap);
+ }
+
+ pool.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleActivatePassivatePool()
+ throws Exception
+ {
+ final DefaultLdapFactory factory = new DefaultLdapFactory(
+ new LdapConfig("ldap://directory.vt.edu:389", "ou=People,dc=vt,dc=edu"));
+ factory.setConnectOnCreate(false);
+ factory.setLdapActivator(new ConnectLdapActivator());
+ factory.setLdapPassivator(new CloseLdapPassivator());
+
+ final SoftLimitLdapPool pool = new SoftLimitLdapPool(factory);
+ // wait for 5sec for an object to be available
+ pool.setBlockWaitTime(5000);
+ pool.initialize();
+
+ Ldap ldap = null;
+ try {
+ ldap = pool.checkOut();
+
+ final Iterator<SearchResult> i = ldap.search(
+ new SearchFilter("givenName=Daniel"),
+ new String[] {"uid", "mail"});
+
+ AssertJUnit.assertTrue(i.hasNext());
+
+ } catch (LdapActivationException e) {
+ e.printStackTrace();
+ } catch (BlockingTimeoutException e) {
+ e.printStackTrace();
+ } catch (PoolInterruptedException e) {
+ e.printStackTrace();
+ } catch (LdapPoolException e) {
+ e.printStackTrace();
+ } finally {
+ pool.checkIn(ldap);
+ }
+
+ pool.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void sampleValidatePool()
+ throws Exception
+ {
+ final LdapPoolConfig config = new LdapPoolConfig();
+ config.setValidateOnCheckOut(true);
+
+ // perform a simple compare
+ final DefaultLdapFactory factory = new DefaultLdapFactory(
+ new LdapConfig("ldap://directory.vt.edu:389", "ou=People,dc=vt,dc=edu"));
+ factory.setLdapValidator(
+ new CompareLdapValidator(
+ "ou=People,dc=vt,dc=edu",
+ new SearchFilter("ou=People")));
+
+ final SoftLimitLdapPool pool = new SoftLimitLdapPool(config, factory);
+ // wait for 5sec for an object to be available
+ pool.setBlockWaitTime(5000);
+ pool.initialize();
+
+ Ldap ldap = null;
+ try {
+ ldap = pool.checkOut();
+
+ final Iterator<SearchResult> i = ldap.search(
+ new SearchFilter("givenName=Daniel"),
+ new String[] {"uid", "mail"});
+
+ AssertJUnit.assertTrue(i.hasNext());
+
+ } catch (LdapValidationException e) {
+ e.printStackTrace();
+ } catch (BlockingTimeoutException e) {
+ e.printStackTrace();
+ } catch (PoolInterruptedException e) {
+ e.printStackTrace();
+ } catch (LdapPoolException e) {
+ e.printStackTrace();
+ } finally {
+ pool.checkIn(ldap);
+ }
+
+ pool.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"wikitest"})
+ public void samplePeriodicValidatePool()
+ throws Exception
+ {
+ final LdapPoolConfig config = new LdapPoolConfig();
+ // by default validate the pool every 30 min, if idle
+ config.setValidatePeriodically(true);
+
+ final DefaultLdapFactory factory = new DefaultLdapFactory(
+ new LdapConfig("ldap://directory.vt.edu:389", "ou=People,dc=vt,dc=edu"));
+ // perform a simple compare
+ factory.setLdapValidator(
+ new CompareLdapValidator(
+ "ou=People,dc=vt,dc=edu",
+ new SearchFilter("ou=People")));
+
+ final SoftLimitLdapPool pool = new SoftLimitLdapPool(config, factory);
+ pool.initialize();
+
+ Ldap ldap = null;
+ try {
+ ldap = pool.checkOut();
+
+ final Iterator<SearchResult> i = ldap.search(
+ new SearchFilter("givenName=Daniel"),
+ new String[] {"uid", "mail"});
+
+ AssertJUnit.assertTrue(i.hasNext());
+
+ } catch (LdapPoolException e) {
+ e.printStackTrace();
+ } finally {
+ pool.checkIn(ldap);
+ }
+
+ pool.close();
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/auth/AuthenticatorCliTest.java b/src/test/java/edu/vt/middleware/ldap/auth/AuthenticatorCliTest.java
new file mode 100644
index 0000000..9d83ae2
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/auth/AuthenticatorCliTest.java
@@ -0,0 +1,117 @@
+/*
+ $Id: AuthenticatorCliTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link AuthenticatorCli} class.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class AuthenticatorCliTest
+{
+
+ /** Entry created for ldap tests. */
+ private static LdapEntry testLdapEntry;
+
+
+ /**
+ * @param ldifFile to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry6" })
+ @BeforeClass(groups = {"authclitest"})
+ public void createLdapEntry(final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"authclitest"})
+ public void deleteLdapEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param args List of delimited arguments to pass to the CLI.
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "cliAuthArgs", "cliAuthResults" })
+ @Test(groups = {"authclitest"})
+ public void authenticate(final String args, final String ldifFile)
+ throws Exception
+ {
+ System.setProperty(
+ "javax.net.ssl.trustStore",
+ "src/test/resources/ed.truststore");
+ System.setProperty("javax.net.ssl.trustStoreType", "BKS");
+ System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
+
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ final PrintStream oldStdOut = System.out;
+ try {
+ final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(outStream));
+
+ AuthenticatorCli.main(args.split("\\|"));
+ AssertJUnit.assertEquals(
+ TestUtil.convertLdifToEntry(ldif),
+ TestUtil.convertLdifToEntry(outStream.toString()));
+ } finally {
+ // Restore STDOUT
+ System.setOut(oldStdOut);
+ }
+
+ System.clearProperty("javax.net.ssl.trustStore");
+ System.clearProperty("javax.net.ssl.trustStoreType");
+ System.clearProperty("javax.net.ssl.trustStorePassword");
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/auth/AuthenticatorLoadTest.java b/src/test/java/edu/vt/middleware/ldap/auth/AuthenticatorLoadTest.java
new file mode 100644
index 0000000..b42ea8e
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/auth/AuthenticatorLoadTest.java
@@ -0,0 +1,297 @@
+/*
+ $Id: AuthenticatorLoadTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.naming.directory.Attributes;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import edu.vt.middleware.ldap.bean.LdapAttributes;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Load test for {@link Authenticator}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class AuthenticatorLoadTest
+{
+
+ /** Invalid password test data. */
+ public static final String INVALID_PASSWD = "not-a-password";
+
+ /** Invalid filter test data. */
+ public static final String INVALID_FILTER = "departmentNumber=1111";
+
+ /** Entries for auth tests. */
+ private static Map<String, LdapEntry[]> entries =
+ new HashMap<String, LdapEntry[]>();
+
+ /**
+ * Initialize the map of entries.
+ */
+ static {
+ for (int i = 2; i <= 10; i++) {
+ entries.put(String.valueOf(i), new LdapEntry[2]);
+ }
+ }
+
+ /** Ldap instance for concurrency testing. */
+ private Authenticator singleTLSAuth;
+
+
+ /**
+ * Default constructor.
+ *
+ * @throws Exception On test failure.
+ */
+ public AuthenticatorLoadTest()
+ throws Exception
+ {
+ this.singleTLSAuth = new Authenticator();
+ this.singleTLSAuth.loadFromProperties(
+ AuthenticatorLoadTest.class.getResourceAsStream(
+ "/ldap.tls.load.properties"));
+ }
+
+
+ /**
+ * @param ldifFile2 to create.
+ * @param ldifFile3 to create.
+ * @param ldifFile4 to create.
+ * @param ldifFile5 to create.
+ * @param ldifFile6 to create.
+ * @param ldifFile7 to create.
+ * @param ldifFile8 to create.
+ * @param ldifFile9 to create.
+ * @param ldifFile10 to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "createEntry2",
+ "createEntry3",
+ "createEntry4",
+ "createEntry5",
+ "createEntry6",
+ "createEntry7",
+ "createEntry8",
+ "createEntry9",
+ "createEntry10"
+ }
+ )
+ @BeforeClass(groups = {"authloadtest"})
+ public void createAuthEntry(
+ final String ldifFile2,
+ final String ldifFile3,
+ final String ldifFile4,
+ final String ldifFile5,
+ final String ldifFile6,
+ final String ldifFile7,
+ final String ldifFile8,
+ final String ldifFile9,
+ final String ldifFile10)
+ throws Exception
+ {
+ entries.get("2")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile2));
+ entries.get("3")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile3));
+ entries.get("4")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile4));
+ entries.get("5")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile5));
+ entries.get("6")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile6));
+ entries.get("7")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile7));
+ entries.get("8")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile8));
+ entries.get("9")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile9));
+ entries.get("10")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile10));
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ for (Map.Entry<String, LdapEntry[]> e : entries.entrySet()) {
+ ldap.create(
+ e.getValue()[0].getDn(),
+ e.getValue()[0].getLdapAttributes().toAttributes());
+ }
+ ldap.close();
+
+ ldap = TestUtil.createLdap();
+ for (Map.Entry<String, LdapEntry[]> e : entries.entrySet()) {
+ while (
+ !ldap.compare(
+ e.getValue()[0].getDn(),
+ new SearchFilter(e.getValue()[0].getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ }
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"authloadtest"})
+ public void deleteAuthEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(entries.get("2")[0].getDn());
+ ldap.delete(entries.get("3")[0].getDn());
+ ldap.delete(entries.get("4")[0].getDn());
+ ldap.delete(entries.get("5")[0].getDn());
+ ldap.delete(entries.get("6")[0].getDn());
+ ldap.delete(entries.get("7")[0].getDn());
+ ldap.delete(entries.get("8")[0].getDn());
+ ldap.delete(entries.get("9")[0].getDn());
+ ldap.delete(entries.get("10")[0].getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * Sample authentication data.
+ *
+ * @return user authentication data
+ */
+ @DataProvider(name = "auth-data")
+ public Object[][] createAuthData()
+ {
+ return
+ new Object[][] {
+ {
+ "jdoe2 at vt.edu",
+ "password2",
+ "departmentNumber={1}",
+ "0822",
+ "givenName|sn",
+ "givenName=John|sn=Doe",
+ },
+ {
+ "jdoe3 at vt.edu",
+ "password3",
+ "departmentNumber={1}",
+ "0823",
+ "givenName|sn",
+ "givenName=Joho|sn=Dof",
+ },
+ {
+ "jdoe4 at vt.edu",
+ "password4",
+ "departmentNumber={1}",
+ "0824",
+ "givenName|sn",
+ "givenName=Johp|sn=Dog",
+ },
+ {
+ "jdoe5 at vt.edu",
+ "password5",
+ "departmentNumber={1}",
+ "0825",
+ "givenName|sn",
+ "givenName=Johq|sn=Doh",
+ },
+ {
+ "jdoe6 at vt.edu",
+ "password6",
+ "departmentNumber={1}",
+ "0826",
+ "givenName|sn",
+ "givenName=Johr|sn=Doi",
+ },
+ {
+ "jdoe7 at vt.edu",
+ "password7",
+ "departmentNumber={1}",
+ "0827",
+ "givenName|sn",
+ "givenName=Johs|sn=Doj",
+ },
+ {
+ "jdoe8 at vt.edu",
+ "password8",
+ "departmentNumber={1}",
+ "0828",
+ "givenName|sn",
+ "givenName=Joht|sn=Dok",
+ },
+ {
+ "jdoe9 at vt.edu",
+ "password9",
+ "departmentNumber={1}",
+ "0829",
+ "givenName|sn",
+ "givenName=Johu|sn=Dol",
+ },
+ {
+ "jdoe10 at vt.edu",
+ "password10",
+ "departmentNumber={1}",
+ "0830",
+ "givenName|sn",
+ "givenName=Johv|sn=Dom",
+ },
+ };
+ }
+
+
+ /**
+ * @param user to authenticate.
+ * @param credential to authenticate with.
+ * @param filter to authorize with.
+ * @param filterArgs to authorize with
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"authloadtest"},
+ dataProvider = "auth-data",
+ threadPoolSize = 50,
+ invocationCount = 1000,
+ timeOut = 60000
+ )
+ public void authenticateAndAuthorize(
+ final String user,
+ final String credential,
+ final String filter,
+ final String filterArgs,
+ final String returnAttrs,
+ final String results)
+ throws Exception
+ {
+ // test auth with return attributes
+ final Attributes attrs = this.singleTLSAuth.authenticate(
+ user,
+ credential,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"));
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(results);
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(attrs));
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/auth/AuthenticatorTest.java b/src/test/java/edu/vt/middleware/ldap/auth/AuthenticatorTest.java
new file mode 100644
index 0000000..c23681c
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/auth/AuthenticatorTest.java
@@ -0,0 +1,847 @@
+/*
+ $Id: AuthenticatorTest.java 2023 2011-07-11 14:50:38Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2023 $
+ Updated: $Date: 2011-07-11 15:50:38 +0100 (Mon, 11 Jul 2011) $
+*/
+package edu.vt.middleware.ldap.auth;
+
+import javax.naming.AuthenticationException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.LdapConstants;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import edu.vt.middleware.ldap.auth.handler.AuthenticationResultHandler;
+import edu.vt.middleware.ldap.auth.handler.AuthorizationHandler;
+import edu.vt.middleware.ldap.auth.handler.CompareAuthenticationHandler;
+import edu.vt.middleware.ldap.auth.handler.TestAuthenticationResultHandler;
+import edu.vt.middleware.ldap.auth.handler.TestAuthorizationHandler;
+import edu.vt.middleware.ldap.bean.LdapAttributes;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link Authenticator}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2023 $
+ */
+public class AuthenticatorTest
+{
+
+ /** Invalid password test data. */
+ public static final String INVALID_PASSWD = "not-a-password";
+
+ /** Invalid filter test data. */
+ public static final String INVALID_FILTER = "departmentNumber=1111";
+
+ /** Entry created for auth tests. */
+ private static LdapEntry testLdapEntry;
+
+ /** Entry created for auth tests. */
+ private static LdapEntry specialCharsLdapEntry;
+
+ /** Ldap instance for concurrency testing. */
+ private Authenticator singleTLSAuth;
+
+ /** Ldap instance for concurrency testing. */
+ private Authenticator singleSSLAuth;
+
+ /** Ldap instance for concurrency testing. */
+ private Authenticator singleTLSDnAuth;
+
+ /** Ldap instance for concurrency testing. */
+ private Authenticator singleSSLDnAuth;
+
+
+ /**
+ * Default constructor.
+ *
+ * @throws Exception if ldap cannot be constructed
+ */
+ public AuthenticatorTest()
+ throws Exception
+ {
+ this.singleTLSAuth = TestUtil.createTLSAuthenticator();
+ this.singleSSLAuth = TestUtil.createSSLAuthenticator();
+ this.singleTLSDnAuth = TestUtil.createTLSDnAuthenticator();
+ this.singleSSLDnAuth = TestUtil.createSSLDnAuthenticator();
+ }
+
+
+ /**
+ * @param ldifFile to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry3" })
+ @BeforeClass(groups = {"authtest"})
+ public void createAuthEntry(final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+ }
+
+
+ /**
+ * @param ldifFile to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createSpecialCharsEntry2" })
+ @BeforeClass(groups = {"authtest"})
+ public void createSpecialCharsEntry(final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ specialCharsLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ specialCharsLdapEntry.getDn(),
+ specialCharsLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ specialCharsLdapEntry.getDn(),
+ new SearchFilter(specialCharsLdapEntry.getDn().split(",ou")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"authtest"})
+ public void deleteAuthEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.delete(specialCharsLdapEntry.getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param createNew whether to construct a new ldap instance.
+ *
+ * @return <code>Authenticator</code>
+ *
+ * @throws Exception On ldap construction failure.
+ */
+ public Authenticator createTLSAuthenticator(final boolean createNew)
+ throws Exception
+ {
+ if (createNew) {
+ return TestUtil.createTLSAuthenticator();
+ }
+ return singleTLSAuth;
+ }
+
+
+ /**
+ * @param createNew whether to construct a new ldap instance.
+ *
+ * @return <code>Authenticator</code>
+ *
+ * @throws Exception On ldap construction failure.
+ */
+ public Authenticator createTLSDnAuthenticator(final boolean createNew)
+ throws Exception
+ {
+ if (createNew) {
+ return TestUtil.createTLSDnAuthenticator();
+ }
+ return singleTLSDnAuth;
+ }
+
+
+ /**
+ * @param createNew whether to construct a new ldap instance.
+ *
+ * @return <code>Authenticator</code>
+ *
+ * @throws Exception On ldap construction failure.
+ */
+ public Authenticator createSSLAuthenticator(final boolean createNew)
+ throws Exception
+ {
+ if (createNew) {
+ return TestUtil.createSSLAuthenticator();
+ }
+ return singleSSLAuth;
+ }
+
+
+ /**
+ * @param createNew whether to construct a new ldap instance.
+ *
+ * @return <code>Authenticator</code>
+ *
+ * @throws Exception On ldap construction failure.
+ */
+ public Authenticator createSSLDnAuthenticator(final boolean createNew)
+ throws Exception
+ {
+ if (createNew) {
+ return TestUtil.createSSLDnAuthenticator();
+ }
+ return singleSSLDnAuth;
+ }
+
+
+ /**
+ * @param ldapUrl to check
+ * @param baseDn to check
+ */
+ @Parameters({ "loadPropertiesUrl", "loadPropertiesBaseDn" })
+ @Test(groups = {"authtest"})
+ public void loadProperties(final String ldapUrl, final String baseDn)
+ {
+ final Authenticator a = new Authenticator();
+ a.loadFromProperties(
+ TestUtil.class.getResourceAsStream("/ldap.tls.properties"));
+ AssertJUnit.assertEquals(ldapUrl, a.getAuthenticatorConfig().getLdapUrl());
+ AssertJUnit.assertEquals(baseDn, a.getAuthenticatorConfig().getBaseDn());
+ }
+
+
+ /**
+ * @param uid to get dn for.
+ * @param user to get dn for.
+ * @param duplicateFilter for user lookups
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "getDnUid", "getDnUser", "getDnDuplicateFilter" })
+ @Test(groups = {"authtest"})
+ public void getDn(
+ final String uid,
+ final String user,
+ final String duplicateFilter)
+ throws Exception
+ {
+ final Authenticator ldap = this.createTLSAuthenticator(true);
+
+ // test input
+ AssertJUnit.assertNull(ldap.getDn(null));
+ AssertJUnit.assertNull(ldap.getDn(""));
+
+ // test empty user field
+ final String[] userField = ldap.getAuthenticatorConfig().getUserField();
+ ldap.getAuthenticatorConfig().setUserField(new String[] {});
+ AssertJUnit.assertNull(ldap.getDn(user));
+ ldap.getAuthenticatorConfig().setUserField(userField);
+
+ // test construct dn
+ ldap.getAuthenticatorConfig().setConstructDn(true);
+ AssertJUnit.assertEquals(ldap.getDn(uid), testLdapEntry.getDn());
+ ldap.getAuthenticatorConfig().setConstructDn(false);
+
+ // test subtree searching
+ ldap.getAuthenticatorConfig().setSubtreeSearch(true);
+
+ final String baseDn = ldap.getAuthenticatorConfig().getBaseDn();
+ ldap.getAuthenticatorConfig().setBaseDn(
+ baseDn.substring(baseDn.indexOf(",") + 1));
+ AssertJUnit.assertEquals(ldap.getDn(user), testLdapEntry.getDn());
+ ldap.getAuthenticatorConfig().setBaseDn(baseDn);
+ ldap.getAuthenticatorConfig().setSubtreeSearch(false);
+
+ // test one level searching
+ AssertJUnit.assertEquals(ldap.getDn(user), testLdapEntry.getDn());
+
+ // test duplicate DNs
+ ldap.getAuthenticatorConfig().setUserFilter(duplicateFilter);
+ try {
+ ldap.getDn(user);
+ AssertJUnit.fail("Should have thrown NamingException");
+ } catch (Exception e) {
+ AssertJUnit.assertEquals(e.getClass(), NamingException.class);
+ }
+
+ ldap.getAuthenticatorConfig().setAllowMultipleDns(true);
+ ldap.getDn(user);
+
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to authenticate.
+ * @param credential to authenticate with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "authenticateDn",
+ "authenticateDnCredential",
+ "authenticateDnReturnAttrs",
+ "authenticateDnResults"
+ }
+ )
+ @Test(
+ groups = {"authtest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void authenticateDn(
+ final String dn,
+ final String credential,
+ final String returnAttrs,
+ final String results)
+ throws Exception
+ {
+ // test plain auth
+ final Authenticator ldap = this.createTLSDnAuthenticator(false);
+ AssertJUnit.assertFalse(ldap.authenticate(dn, INVALID_PASSWD));
+ AssertJUnit.assertTrue(ldap.authenticate(dn, credential));
+
+ // test auth with return attributes
+ final Attributes attrs = ldap.authenticate(
+ dn,
+ credential,
+ returnAttrs.split("\\|"));
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(results);
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(attrs));
+ }
+
+
+ /**
+ * @param dn to authenticate.
+ * @param credential to authenticate with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "authenticateDn",
+ "authenticateDnCredential",
+ "authenticateDnReturnAttrs",
+ "authenticateDnResults"
+ }
+ )
+ @Test(
+ groups = {"authtest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void authenticateDnSsl(
+ final String dn,
+ final String credential,
+ final String returnAttrs,
+ final String results)
+ throws Exception
+ {
+ // test plain auth
+ final Authenticator ldap = this.createSSLDnAuthenticator(false);
+ AssertJUnit.assertFalse(ldap.authenticate(dn, INVALID_PASSWD));
+ AssertJUnit.assertTrue(ldap.authenticate(dn, credential));
+
+ // test auth with return attributes
+ final Attributes attrs = ldap.authenticate(
+ dn,
+ credential,
+ returnAttrs.split("\\|"));
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(results);
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(attrs));
+ }
+
+
+ /**
+ * @param dn to authenticate.
+ * @param credential to authenticate with.
+ * @param filter to authorize with.
+ * @param filterArgs to authorize with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "authenticateDn",
+ "authenticateDnCredential",
+ "authenticateDnFilter",
+ "authenticateDnFilterArgs",
+ "authenticateDnReturnAttrs",
+ "authenticateDnResults"
+ }
+ )
+ @Test(
+ groups = {"authtest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void authenticateDnAndAuthorize(
+ final String dn,
+ final String credential,
+ final String filter,
+ final String filterArgs,
+ final String returnAttrs,
+ final String results)
+ throws Exception
+ {
+ final Authenticator ldap = this.createTLSDnAuthenticator(false);
+
+ // test plain auth
+ AssertJUnit.assertFalse(
+ ldap.authenticate(dn, INVALID_PASSWD, new SearchFilter(filter)));
+ AssertJUnit.assertFalse(
+ ldap.authenticate(dn, credential, new SearchFilter(INVALID_FILTER)));
+ AssertJUnit.assertTrue(
+ ldap.authenticate(
+ dn,
+ credential,
+ new SearchFilter(filter, filterArgs.split("\\|"))));
+
+ // test auth with return attributes
+ final Attributes attrs = ldap.authenticate(
+ dn,
+ credential,
+ new SearchFilter(filter, filterArgs.split("\\|")),
+ returnAttrs.split("\\|"));
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(results);
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(attrs));
+ }
+
+
+ /**
+ * @param dn to authenticate.
+ * @param credential to authenticate with.
+ * @param filter to authorize with.
+ * @param filterArgs to authorize with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "authenticateDn",
+ "authenticateDnCredential",
+ "authenticateDnFilter",
+ "authenticateDnFilterArgs"
+ }
+ )
+ @Test(groups = {"authtest"})
+ public void authenticateDnHandler(
+ final String dn,
+ final String credential,
+ final String filter,
+ final String filterArgs)
+ throws Exception
+ {
+ // test authenticator handler
+ final Authenticator ldap = this.createTLSDnAuthenticator(true);
+ final TestAuthenticationResultHandler authHandler =
+ new TestAuthenticationResultHandler();
+ ldap.getAuthenticatorConfig().setAuthenticationResultHandlers(
+ new AuthenticationResultHandler[] {authHandler});
+
+ final TestAuthorizationHandler authzHandler =
+ new TestAuthorizationHandler();
+ ldap.getAuthenticatorConfig().setAuthorizationHandlers(
+ new AuthorizationHandler[] {authzHandler});
+
+ AssertJUnit.assertFalse(ldap.authenticate(dn, INVALID_PASSWD));
+ AssertJUnit.assertFalse(authHandler.getResults().get(dn).booleanValue());
+ AssertJUnit.assertFalse(!authzHandler.getResults().isEmpty());
+
+ AssertJUnit.assertFalse(ldap.authenticate(dn, credential));
+ AssertJUnit.assertFalse(authHandler.getResults().get(dn).booleanValue());
+ AssertJUnit.assertFalse(!authzHandler.getResults().isEmpty());
+
+ authzHandler.setSucceed(true);
+
+ AssertJUnit.assertTrue(ldap.authenticate(dn, credential));
+ AssertJUnit.assertTrue(authHandler.getResults().get(dn).booleanValue());
+ AssertJUnit.assertTrue(authzHandler.getResults().get(0).equals(dn));
+
+ authHandler.getResults().clear();
+ authzHandler.getResults().clear();
+
+ AssertJUnit.assertTrue(
+ ldap.authenticate(
+ dn,
+ credential,
+ new SearchFilter(filter, filterArgs.split("\\|"))));
+ AssertJUnit.assertTrue(authHandler.getResults().get(dn).booleanValue());
+ AssertJUnit.assertTrue(authzHandler.getResults().get(0).equals(dn));
+ }
+
+
+ /**
+ * @param user to authenticate.
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "digestMd5User", "digestMd5Credential" })
+ @Test(groups = {"authtest"})
+ public void authenticateDigestMd5(final String user, final String credential)
+ throws Exception
+ {
+ final Authenticator ldap = TestUtil.createDigestMD5Authenticator();
+ AssertJUnit.assertFalse(ldap.authenticate(user, INVALID_PASSWD));
+ AssertJUnit.assertTrue(ldap.authenticate(user, credential));
+ ldap.close();
+ }
+
+
+ /**
+ * @param user to authenticate.
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "cramMd5User", "cramMd5Credential" })
+ @Test(groups = {"authtest"})
+ public void authenticateCramMd5(final String user, final String credential)
+ throws Exception
+ {
+ final Authenticator ldap = TestUtil.createCramMD5Authenticator();
+ AssertJUnit.assertFalse(ldap.authenticate(user, INVALID_PASSWD));
+ AssertJUnit.assertTrue(ldap.authenticate(user, credential));
+ ldap.close();
+ }
+
+
+ /**
+ * @param user to authenticate.
+ * @param credential to authenticate with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "authenticateUser",
+ "authenticateCredential",
+ "authenticateReturnAttrs",
+ "authenticateResults"
+ }
+ )
+ @Test(
+ groups = {"authtest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void authenticate(
+ final String user,
+ final String credential,
+ final String returnAttrs,
+ final String results)
+ throws Exception
+ {
+ final Authenticator ldap = this.createTLSAuthenticator(false);
+
+ // test plain auth
+ AssertJUnit.assertFalse(ldap.authenticate(user, INVALID_PASSWD));
+ AssertJUnit.assertTrue(ldap.authenticate(user, credential));
+
+ // test auth with return attributes
+ final Attributes attrs = ldap.authenticate(
+ user,
+ credential,
+ returnAttrs.split("\\|"));
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(results);
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(attrs));
+ }
+
+
+ /**
+ * @param user to authenticate.
+ * @param credential to authenticate with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "authenticateUser",
+ "authenticateCredential",
+ "authenticateReturnAttrs",
+ "authenticateResults"
+ }
+ )
+ @Test(
+ groups = {"authtest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void authenticateSsl(
+ final String user,
+ final String credential,
+ final String returnAttrs,
+ final String results)
+ throws Exception
+ {
+ final Authenticator ldap = this.createSSLAuthenticator(false);
+
+ // test plain auth
+ AssertJUnit.assertFalse(ldap.authenticate(user, INVALID_PASSWD));
+ AssertJUnit.assertTrue(ldap.authenticate(user, credential));
+
+ // test auth with return attributes
+ final Attributes attrs = ldap.authenticate(
+ user,
+ credential,
+ returnAttrs.split("\\|"));
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(results);
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(attrs));
+ }
+
+
+ /**
+ * @param user to authenticate.
+ * @param credential to authenticate with.
+ * @param filter to authorize with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "authenticateUser",
+ "authenticateCredential",
+ "authenticateFilter",
+ "authenticateReturnAttrs",
+ "authenticateResults"
+ }
+ )
+ @Test(
+ groups = {"authtest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void authenticateAndAuthorize(
+ final String user,
+ final String credential,
+ final String filter,
+ final String returnAttrs,
+ final String results)
+ throws Exception
+ {
+ final Authenticator ldap = this.createTLSAuthenticator(false);
+
+ // test plain auth
+ AssertJUnit.assertFalse(
+ ldap.authenticate(user, INVALID_PASSWD, new SearchFilter(filter)));
+ AssertJUnit.assertFalse(
+ ldap.authenticate(user, credential, new SearchFilter(INVALID_FILTER)));
+ AssertJUnit.assertTrue(
+ ldap.authenticate(user, credential, new SearchFilter(filter)));
+
+ // test auth with return attributes
+ final Attributes attrs = ldap.authenticate(
+ user,
+ credential,
+ new SearchFilter(filter),
+ returnAttrs.split("\\|"));
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(results);
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(attrs));
+ }
+
+
+ /**
+ * @param user to authenticate.
+ * @param credential to authenticate with.
+ * @param filter to authorize with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "authenticateUser",
+ "authenticateCredential",
+ "authenticateFilter",
+ "authenticateReturnAttrs",
+ "authenticateResults"
+ }
+ )
+ @Test(
+ groups = {"authtest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void authenticateAndAuthorizeCompare(
+ final String user,
+ final String credential,
+ final String filter,
+ final String returnAttrs,
+ final String results)
+ throws Exception
+ {
+ final Authenticator ldap = this.createTLSAuthenticator(true);
+ ldap.getAuthenticatorConfig().setAuthenticationHandler(
+ new CompareAuthenticationHandler());
+
+ // test plain auth
+ AssertJUnit.assertFalse(
+ ldap.authenticate(user, INVALID_PASSWD, new SearchFilter(filter)));
+ AssertJUnit.assertFalse(
+ ldap.authenticate(user, credential, new SearchFilter(INVALID_FILTER)));
+ AssertJUnit.assertTrue(
+ ldap.authenticate(user, credential, new SearchFilter(filter)));
+
+ // test auth with return attributes
+ final Attributes attrs = ldap.authenticate(
+ user,
+ credential,
+ new SearchFilter(filter),
+ returnAttrs.split("\\|"));
+ final LdapAttributes expected = TestUtil.convertStringToAttributes(results);
+ AssertJUnit.assertEquals(expected, TestUtil.newLdapAttributes(attrs));
+
+ ldap.close();
+ }
+
+
+ /**
+ * @param user to authenticate.
+ * @param credential to authenticate with.
+ * @param returnAttrs to search for.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "authenticateUser",
+ "authenticateCredential",
+ "authenticateReturnAttrs"
+ }
+ )
+ @Test(groups = {"authtest"})
+ public void authenticateExceptions(
+ final String user,
+ final String credential,
+ final String returnAttrs)
+ throws Exception
+ {
+ final Authenticator ldap = this.createTLSAuthenticator(true);
+
+ try {
+ ldap.authenticate(user, new Object(), returnAttrs.split("\\|"));
+ AssertJUnit.fail("Should have thrown AuthenticationException");
+ } catch (Exception e) {
+ AssertJUnit.assertEquals(e.getClass(), AuthenticationException.class);
+ }
+
+ try {
+ ldap.authenticate(null, credential, returnAttrs.split("\\|"));
+ AssertJUnit.fail("Should have thrown AuthenticationException");
+ } catch (Exception e) {
+ AssertJUnit.assertEquals(e.getClass(), AuthenticationException.class);
+ }
+
+ try {
+ ldap.authenticate("", credential, returnAttrs.split("\\|"));
+ AssertJUnit.fail("Should have thrown AuthenticationException");
+ } catch (Exception e) {
+ AssertJUnit.assertEquals(e.getClass(), AuthenticationException.class);
+ }
+
+ // must do this test after the search connection has been setup or
+ // the anon auth will block the search
+ ldap.getAuthenticatorConfig().setAuthtype(LdapConstants.NONE_AUTHTYPE);
+ try {
+ ldap.authenticate(user, credential, returnAttrs.split("\\|"));
+ AssertJUnit.fail("Should have thrown AuthenticationException");
+ } catch (Exception e) {
+ AssertJUnit.assertEquals(e.getClass(), AuthenticationException.class);
+ }
+
+ ldap.getAuthenticatorConfig().setAuthtype(LdapConstants.SIMPLE_AUTHTYPE);
+ try {
+ ldap.authenticate(
+ user,
+ credential,
+ new SearchFilter(INVALID_FILTER),
+ returnAttrs.split("\\|"));
+ AssertJUnit.fail("Should have thrown AuthorizationException");
+ } catch (Exception e) {
+ AssertJUnit.assertEquals(e.getClass(), AuthorizationException.class);
+ }
+
+ ldap.close();
+ }
+
+
+ /**
+ * @param user to authenticate.
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "authenticateSpecialCharsUser",
+ "authenticateSpecialCharsCredential"
+ }
+ )
+ @Test(groups = {"authtest"})
+ public void authenticateSpecialChars(
+ final String user, final String credential)
+ throws Exception
+ {
+ final Authenticator ldap = this.createTLSAuthenticator(true);
+
+ // test without rewrite
+ AssertJUnit.assertFalse(ldap.authenticate(user, INVALID_PASSWD));
+ AssertJUnit.assertTrue(ldap.authenticate(user, credential));
+
+ // test with rewrite
+ ldap.getAuthenticatorConfig().setBaseDn("dc=blah");
+ ldap.getAuthenticatorConfig().setSubtreeSearch(true);
+ AssertJUnit.assertFalse(ldap.authenticate(user, INVALID_PASSWD));
+ AssertJUnit.assertTrue(ldap.authenticate(user, credential));
+
+ ldap.close();
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/auth/handler/TestAuthenticationResultHandler.java b/src/test/java/edu/vt/middleware/ldap/auth/handler/TestAuthenticationResultHandler.java
new file mode 100644
index 0000000..62dd99d
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/auth/handler/TestAuthenticationResultHandler.java
@@ -0,0 +1,49 @@
+/*
+ $Id: TestAuthenticationResultHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth.handler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <code>TestAuthenticationResultHandler</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class TestAuthenticationResultHandler
+ implements AuthenticationResultHandler
+{
+
+ /** results. */
+ private Map<String, Boolean> results = new HashMap<String, Boolean>();
+
+
+ /** {@inheritDoc} */
+ public void process(final AuthenticationCriteria ac, final boolean success)
+ {
+ this.results.put(ac.getDn(), Boolean.valueOf(success));
+ }
+
+
+ /**
+ * Returns the authentication results.
+ *
+ * @return authentication results
+ */
+ public Map<String, Boolean> getResults()
+ {
+ return this.results;
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/auth/handler/TestAuthorizationHandler.java b/src/test/java/edu/vt/middleware/ldap/auth/handler/TestAuthorizationHandler.java
new file mode 100644
index 0000000..abed850
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/auth/handler/TestAuthorizationHandler.java
@@ -0,0 +1,69 @@
+/*
+ $Id: TestAuthorizationHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.auth.handler;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+import edu.vt.middleware.ldap.auth.AuthorizationException;
+
+/**
+ * <code>TestAuthenticationResultHandler</code>.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class TestAuthorizationHandler implements AuthorizationHandler
+{
+
+ /** results. */
+ private List<String> results = new ArrayList<String>();
+
+ /** whether process should succeed. */
+ private boolean succeed;
+
+
+ /** {@inheritDoc} */
+ public void process(final AuthenticationCriteria ac, final LdapContext ctx)
+ throws NamingException
+ {
+ if (!succeed) {
+ throw new AuthorizationException("Succeed is false");
+ }
+ this.results.add(ac.getDn());
+ }
+
+
+ /**
+ * Returns the authentication results.
+ *
+ * @return authentication results
+ */
+ public List<String> getResults()
+ {
+ return this.results;
+ }
+
+
+ /**
+ * Sets whether process will succeed.
+ *
+ * @param b <code>boolean</code>
+ */
+ public void setSucceed(final boolean b)
+ {
+ succeed = b;
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/bean/LdapResultTest.java b/src/test/java/edu/vt/middleware/ldap/bean/LdapResultTest.java
new file mode 100644
index 0000000..e823b38
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/bean/LdapResultTest.java
@@ -0,0 +1,111 @@
+/*
+ $Id: LdapResultTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.bean;
+
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link LdapResult}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class LdapResultTest
+{
+
+ /** Entry created for tests. */
+ private static LdapEntry testLdapEntry;
+
+
+ /**
+ * @param ldifFile to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry4" })
+ @BeforeClass(groups = {"beantest"})
+ public void createLdapEntry(final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"beantest"})
+ public void deleteLdapEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to search for.
+ * @param filter to search with.
+ * @param returnAttrs attributes to return from search
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "toSearchResultsDn",
+ "toSearchResultsFilter",
+ "toSearchResultsAttrs",
+ "toSearchResultsResults"
+ }
+ )
+ @Test(groups = {"beantest"})
+ public void toSearchResults(
+ final String dn,
+ final String filter,
+ final String returnAttrs,
+ final String ldifFile)
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createLdap();
+
+ final LdapResult found = TestUtil.newLdapResult(
+ ldap.search(dn, new SearchFilter(filter), returnAttrs.split("\\|")));
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ final LdapResult expected = TestUtil.convertLdifToResult(ldif);
+ AssertJUnit.assertEquals(expected, found);
+ ldap.close();
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/dsml/DsmlTest.java b/src/test/java/edu/vt/middleware/ldap/dsml/DsmlTest.java
new file mode 100644
index 0000000..3598679
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/dsml/DsmlTest.java
@@ -0,0 +1,207 @@
+/*
+ $Id: DsmlTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.dsml;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Iterator;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import edu.vt.middleware.ldap.bean.SortedLdapBeanFactory;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link Dsmlv1} and {@link Dsmlv2}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class DsmlTest
+{
+
+ /** Entry created for ldap tests. */
+ private static LdapEntry testLdapEntry;
+
+
+ /**
+ * @param ldifFile to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry11" })
+ @BeforeClass(groups = {"dsmltest"})
+ public void createLdapEntry(final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"dsmltest"})
+ public void deleteLdapEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({
+ "dsmlSearchDn",
+ "dsmlSearchFilter"
+ })
+ @Test(groups = {"dsmltest"})
+ public void searchAndCompareDsmlv1(final String dn, final String filter)
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createLdap();
+ final Dsmlv1 dsml = new Dsmlv1();
+
+ final Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter));
+
+ final LdapResult result1 = TestUtil.newLdapResult(iter);
+ final StringWriter writer = new StringWriter();
+ dsml.outputDsml(result1.toSearchResults().iterator(), writer);
+
+ final StringReader reader = new StringReader(writer.toString());
+ final LdapResult result2 = dsml.importDsmlToLdapResult(reader);
+
+ AssertJUnit.assertEquals(result1, result2);
+ ldap.close();
+ }
+
+
+ /**
+ * @param dsmlFile to test with.
+ * @param dsmlSortedFile to test with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({
+ "dsmlv1Entry",
+ "dsmlv1SortedEntry"
+ })
+ @Test(groups = {"dsmltest"})
+ public void readAndCompareDsmlv1(
+ final String dsmlFile,
+ final String dsmlSortedFile)
+ throws Exception
+ {
+ final Dsmlv1 dsml = new Dsmlv1();
+ dsml.setLdapBeanFactory(new SortedLdapBeanFactory());
+
+ final String dsmlStringSorted = TestUtil.readFileIntoString(dsmlSortedFile);
+ final Iterator<SearchResult> iter = dsml.importDsml(
+ new StringReader(TestUtil.readFileIntoString(dsmlFile)));
+ final StringWriter writer = new StringWriter();
+ dsml.outputDsml(iter, writer);
+
+ AssertJUnit.assertEquals(dsmlStringSorted, writer.toString());
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({
+ "dsmlSearchDn",
+ "dsmlSearchFilter"
+ })
+ @Test(groups = {"dsmltest"})
+ public void searchAndCompareDsmlv2(final String dn, final String filter)
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createLdap();
+ final Dsmlv2 dsml = new Dsmlv2();
+
+ final Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter));
+
+ final LdapResult result1 = TestUtil.newLdapResult(iter);
+ final StringWriter writer = new StringWriter();
+ dsml.outputDsml(result1.toSearchResults().iterator(), writer);
+
+ final StringReader reader = new StringReader(writer.toString());
+ final LdapResult result2 = dsml.importDsmlToLdapResult(reader);
+
+ AssertJUnit.assertEquals(result1, result2);
+ ldap.close();
+ }
+
+
+ /**
+ * @param dsmlFile to test with.
+ * @param dsmlSortedFile to test with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({
+ "dsmlv2Entry",
+ "dsmlv2SortedEntry"
+ })
+ @Test(groups = {"dsmltest"})
+ public void readAndCompareDsmlv2(
+ final String dsmlFile,
+ final String dsmlSortedFile)
+ throws Exception
+ {
+ final Dsmlv2 dsml = new Dsmlv2();
+ dsml.setLdapBeanFactory(new SortedLdapBeanFactory());
+
+ final String dsmlStringSorted = TestUtil.readFileIntoString(dsmlSortedFile);
+ final Iterator<SearchResult> iter = dsml.importDsml(
+ new StringReader(TestUtil.readFileIntoString(dsmlFile)));
+ final StringWriter writer = new StringWriter();
+ dsml.outputDsml(iter, writer);
+
+ AssertJUnit.assertEquals(dsmlStringSorted, writer.toString());
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/jaas/LdapLoginModuleTest.java b/src/test/java/edu/vt/middleware/ldap/jaas/LdapLoginModuleTest.java
new file mode 100644
index 0000000..d208d3f
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/jaas/LdapLoginModuleTest.java
@@ -0,0 +1,771 @@
+/*
+ $Id: LdapLoginModuleTest.java 2218 2012-01-23 19:58:08Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2218 $
+ Updated: $Date: 2012-01-23 19:58:08 +0000 (Mon, 23 Jan 2012) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import edu.vt.middleware.ldap.AttributesFactory;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.Ldap.AttributeModification;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link LdapLoginModule}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2218 $
+ */
+public class LdapLoginModuleTest
+{
+
+ /** Invalid password test data. */
+ public static final String INVALID_PASSWD = "not-a-password";
+
+ /** Entry created for auth tests. */
+ private static LdapEntry testLdapEntry;
+
+ /** Entries for group tests. */
+ private static Map<String, LdapEntry[]> groupEntries =
+ new HashMap<String, LdapEntry[]>();
+
+ /**
+ * Initialize the map of group entries.
+ */
+ static {
+ for (int i = 6; i <= 9; i++) {
+ groupEntries.put(String.valueOf(i), new LdapEntry[2]);
+ }
+ }
+
+
+ /**
+ * @param ldifFile to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry7" })
+ @BeforeClass(groups = {"jaastest"})
+ public void createAuthEntry(final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+
+ System.setProperty(
+ "java.security.auth.login.config",
+ "src/test/resources/ldap_jaas.config");
+ }
+
+
+ /**
+ * @param ldifFile6 to create.
+ * @param ldifFile7 to create.
+ * @param ldifFile8 to create.
+ * @param ldifFile9 to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "createGroup6",
+ "createGroup7",
+ "createGroup8",
+ "createGroup9"
+ }
+ )
+ @BeforeClass(groups = {"jaastest"})
+ public void createGroupEntry(
+ final String ldifFile6,
+ final String ldifFile7,
+ final String ldifFile8,
+ final String ldifFile9)
+ throws Exception
+ {
+ groupEntries.get("6")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile6));
+ groupEntries.get("7")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile7));
+ groupEntries.get("8")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile8));
+ groupEntries.get("9")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile9));
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ for (Map.Entry<String, LdapEntry[]> e : groupEntries.entrySet()) {
+ ldap.create(
+ e.getValue()[0].getDn(),
+ e.getValue()[0].getLdapAttributes().toAttributes());
+ }
+ ldap.close();
+
+ ldap = TestUtil.createLdap();
+ for (Map.Entry<String, LdapEntry[]> e : groupEntries.entrySet()) {
+ while (
+ !ldap.compare(
+ e.getValue()[0].getDn(),
+ new SearchFilter(e.getValue()[0].getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ }
+
+ // setup group relationships
+ ldap.modifyAttributes(
+ groupEntries.get("6")[0].getDn(),
+ AttributeModification.ADD,
+ AttributesFactory.createAttributes(
+ "member",
+ new String[] {
+ "uid=7,ou=test,dc=vt,dc=edu",
+ "uugid=group7,ou=test,dc=vt,dc=edu",
+ }));
+ ldap.modifyAttributes(
+ groupEntries.get("7")[0].getDn(),
+ AttributeModification.ADD,
+ AttributesFactory.createAttributes(
+ "member",
+ new String[] {
+ "uugid=group8,ou=test,dc=vt,dc=edu",
+ "uugid=group9,ou=test,dc=vt,dc=edu",
+ }));
+ ldap.modifyAttributes(
+ groupEntries.get("8")[0].getDn(),
+ AttributeModification.ADD,
+ AttributesFactory.createAttributes(
+ "member",
+ "uugid=group7,ou=test,dc=vt,dc=edu"));
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"jaastest"})
+ public void deleteAuthEntry()
+ throws Exception
+ {
+ System.clearProperty("java.security.auth.login.config");
+
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.delete(groupEntries.get("6")[0].getDn());
+ ldap.delete(groupEntries.get("7")[0].getDn());
+ ldap.delete(groupEntries.get("8")[0].getDn());
+ ldap.delete(groupEntries.get("9")[0].getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" })
+ @Test(
+ groups = {"jaastest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void contextTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap", dn, user, role, credential, false);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" })
+ @Test(
+ groups = {"jaastest"},
+ enabled = false, // requires JVM wide truststore to pass
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void contextSslTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap-ssl", dn, user, role, credential, false);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" })
+ @Test(
+ groups = {"jaastest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void contextSsl2Test(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap-ssl-2", dn, user, role, credential, false);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" })
+ @Test(
+ groups = {"jaastest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void authzContextTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap-authz", dn, user, role, credential, true);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" })
+ @Test(
+ groups = {"jaastest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void randomContextTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap-random", dn, user, role, credential, true);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasCredential" })
+ @Test(
+ groups = {"jaastest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void filterContextTest(
+ final String dn,
+ final String user,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap-filter", dn, user, "", credential, false);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" })
+ @Test(groups = {"jaastest"})
+ public void handlerContextTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ final TestCallbackHandler callback = new TestCallbackHandler();
+ callback.setName(user);
+ callback.setPassword(credential);
+
+ final LoginContext lc = new LoginContext("vt-ldap-handler", callback);
+ try {
+ lc.login();
+ AssertJUnit.fail(
+ "Handler succeed set to false, login should have failed");
+ } catch (Exception e) {
+ AssertJUnit.assertEquals(e.getClass(), LoginException.class);
+ }
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasRoleCombined", "jaasCredential" })
+ @Test(
+ groups = {"jaastest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void rolesContextTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap-roles", dn, user, role, credential, false);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "jaasDn", "jaasUser", "jaasRoleCombinedRecursive", "jaasCredential"
+ }
+ )
+ @Test(groups = {"jaastest"})
+ public void rolesRecursiveContextTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest(
+ "vt-ldap-roles-recursive",
+ dn,
+ user,
+ role,
+ credential,
+ false);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasUserRoleDefault", "jaasCredential" })
+ @Test(groups = {"jaastest"})
+ public void useFirstContextTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap-use-first", dn, user, role, credential, false);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasRoleCombined", "jaasCredential" })
+ @Test(groups = {"jaastest"})
+ public void tryFirstContextTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap-try-first", dn, user, role, credential, false);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" })
+ @Test(groups = {"jaastest"})
+ public void sufficientContextTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap-sufficient", dn, user, role, credential, false);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasUserRole", "jaasCredential" })
+ @Test(groups = {"jaastest"})
+ public void oldContextTest(
+ final String dn,
+ final String user,
+ final String role,
+ final String credential)
+ throws Exception
+ {
+ this.doContextTest("vt-ldap-deprecated", dn, user, role, credential, false);
+ }
+
+
+ /**
+ * @param name of the jaas configuration
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ * @param credential to authenticate with.
+ * @param checkLdapDn whether to check the LdapDnPrincipal
+ *
+ * @throws Exception On test failure.
+ */
+ private void doContextTest(
+ final String name,
+ final String dn,
+ final String user,
+ final String role,
+ final String credential,
+ final boolean checkLdapDn)
+ throws Exception
+ {
+ final TestCallbackHandler callback = new TestCallbackHandler();
+ callback.setName(user);
+ callback.setPassword(INVALID_PASSWD);
+
+ LoginContext lc = new LoginContext(name, callback);
+ try {
+ lc.login();
+ AssertJUnit.fail("Invalid password, login should have failed");
+ } catch (Exception e) {
+ AssertJUnit.assertEquals(e.getClass(), LoginException.class);
+ }
+
+ callback.setPassword(credential);
+ lc = new LoginContext(name, callback);
+ try {
+ lc.login();
+ } catch (Exception e) {
+ AssertJUnit.fail(e.getMessage());
+ }
+
+ final Set<LdapPrincipal> principals = lc.getSubject().getPrincipals(
+ LdapPrincipal.class);
+ AssertJUnit.assertEquals(1, principals.size());
+
+ final LdapPrincipal p = principals.iterator().next();
+ AssertJUnit.assertEquals(p.getName(), user);
+ if (!"".equals(role)) {
+ AssertJUnit.assertTrue(p.getLdapAttributes().size() > 0);
+ }
+
+ final Set<LdapDnPrincipal> dnPrincipals = lc.getSubject().getPrincipals(
+ LdapDnPrincipal.class);
+ if (checkLdapDn) {
+ AssertJUnit.assertEquals(1, dnPrincipals.size());
+
+ final LdapDnPrincipal dnP = dnPrincipals.iterator().next();
+ AssertJUnit.assertEquals(dnP.getName(), dn);
+ if (!"".equals(role)) {
+ AssertJUnit.assertTrue(dnP.getLdapAttributes().size() > 0);
+ }
+ } else {
+ AssertJUnit.assertEquals(0, dnPrincipals.size());
+ }
+
+ final Set<LdapRole> roles = lc.getSubject().getPrincipals(LdapRole.class);
+
+ final Iterator<LdapRole> roleIter = roles.iterator();
+ String[] checkRoles = role.split("\\|");
+ if (checkRoles.length == 1 && "".equals(checkRoles[0])) {
+ checkRoles = new String[0];
+ }
+ AssertJUnit.assertEquals(checkRoles.length, roles.size());
+ while (roleIter.hasNext()) {
+ final LdapRole r = roleIter.next();
+ boolean match = false;
+ for (String s : checkRoles) {
+ if (s.equals(r.getName())) {
+ match = true;
+ }
+ }
+ AssertJUnit.assertTrue(match);
+ }
+
+ final Set<LdapCredential> credentials = lc.getSubject()
+ .getPrivateCredentials(LdapCredential.class);
+ AssertJUnit.assertEquals(1, credentials.size());
+
+ final LdapCredential c = credentials.iterator().next();
+ AssertJUnit.assertEquals(
+ new String((char[]) c.getCredential()),
+ credential);
+
+ try {
+ lc.logout();
+ } catch (Exception e) {
+ AssertJUnit.fail(e.getMessage());
+ }
+
+ AssertJUnit.assertEquals(0, lc.getSubject().getPrincipals().size());
+ AssertJUnit.assertEquals(0, lc.getSubject().getPrivateCredentials().size());
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasRoleCombined" })
+ @Test(
+ groups = {"jaastest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void rolesOnlyContextTest(
+ final String dn,
+ final String user,
+ final String role)
+ throws Exception
+ {
+ this.doRolesContextTest("vt-ldap-roles-only", dn, user, role);
+ }
+
+
+ /**
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "jaasDn", "jaasUser", "jaasRoleCombined" })
+ @Test(
+ groups = {"jaastest"},
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void dnRolesOnlyContextTest(
+ final String dn,
+ final String user,
+ final String role)
+ throws Exception
+ {
+ this.doRolesContextTest("vt-ldap-dn-roles-only", dn, user, role);
+ }
+
+
+ /**
+ * @param name of the jaas configuration
+ * @param dn of this user
+ * @param user to authenticate.
+ * @param role to set for this user
+ *
+ * @throws Exception On test failure.
+ */
+ private void doRolesContextTest(
+ final String name,
+ final String dn,
+ final String user,
+ final String role)
+ throws Exception
+ {
+ final TestCallbackHandler callback = new TestCallbackHandler();
+ callback.setName(user);
+
+ final LoginContext lc = new LoginContext(name, callback);
+ try {
+ lc.login();
+ } catch (Exception e) {
+ AssertJUnit.fail(e.getMessage());
+ }
+
+ final Set<LdapRole> roles = lc.getSubject().getPrincipals(LdapRole.class);
+
+ final Iterator<LdapRole> roleIter = roles.iterator();
+ final String[] checkRoles = role.split("\\|");
+ AssertJUnit.assertEquals(checkRoles.length, roles.size());
+ while (roleIter.hasNext()) {
+ final LdapRole r = roleIter.next();
+ boolean match = false;
+ for (String s : checkRoles) {
+ if (s.equals(r.getName())) {
+ match = true;
+ }
+ }
+ AssertJUnit.assertTrue(match);
+ }
+
+ final Set<Group> roleGroups = lc.getSubject().getPrincipals(Group.class);
+ AssertJUnit.assertTrue(roleGroups.size() == 2);
+ for (Group g : roleGroups) {
+ if ("Roles".equals(g.getName())) {
+ final Enumeration<? extends Principal> members = g.members();
+ int count = 0;
+ while (members.hasMoreElements()) {
+ final Principal p = members.nextElement();
+ boolean match = false;
+ for (LdapRole lr : lc.getSubject().getPrincipals(LdapRole.class)) {
+ if (lr.getName().equals(p.getName())) {
+ match = true;
+ }
+ }
+ AssertJUnit.assertTrue(match);
+ count++;
+ }
+ AssertJUnit.assertEquals(
+ count,
+ lc.getSubject().getPrincipals(LdapRole.class).size());
+ } else if ("Principals".equals(g.getName())) {
+ final Enumeration<? extends Principal> members = g.members();
+ int count = 0;
+ while (members.hasMoreElements()) {
+ final Principal p = members.nextElement();
+ boolean match = false;
+ for (
+ LdapPrincipal lp :
+ lc.getSubject().getPrincipals(LdapPrincipal.class)) {
+ if (lp.getName().equals(p.getName())) {
+ match = true;
+ }
+ }
+ AssertJUnit.assertTrue(match);
+ count++;
+ }
+ AssertJUnit.assertEquals(
+ count,
+ lc.getSubject().getPrincipals(LdapPrincipal.class).size());
+ } else {
+ AssertJUnit.fail("Found invalid group");
+ }
+ }
+
+ final Set<?> credentials = lc.getSubject().getPrivateCredentials();
+ AssertJUnit.assertEquals(0, credentials.size());
+
+ try {
+ lc.logout();
+ } catch (Exception e) {
+ AssertJUnit.fail(e.getMessage());
+ }
+
+ AssertJUnit.assertEquals(0, lc.getSubject().getPrincipals().size());
+ AssertJUnit.assertEquals(0, lc.getSubject().getPrivateCredentials().size());
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/jaas/TestCallbackHandler.java b/src/test/java/edu/vt/middleware/ldap/jaas/TestCallbackHandler.java
new file mode 100644
index 0000000..5661e17
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/jaas/TestCallbackHandler.java
@@ -0,0 +1,77 @@
+/*
+ $Id: TestCallbackHandler.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.io.IOException;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+/**
+ * Class that implements a callback handler to help with jaas testing.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class TestCallbackHandler implements CallbackHandler
+{
+
+ /** test name. */
+ private String name;
+
+ /** test password. */
+ private String password;
+
+
+ /** @param s to set name with */
+ public void setName(final String s)
+ {
+ this.name = s;
+ }
+
+
+ /** @param s to set password with */
+ public void setPassword(final String s)
+ {
+ this.password = s;
+ }
+
+
+ /**
+ * @param callbacks to handle
+ *
+ * @throws IOException if an input or output error occurs
+ * @throws UnsupportedCallbackException if a supplied callback cannot be
+ * handled
+ */
+ public void handle(final Callback[] callbacks)
+ throws IOException, UnsupportedCallbackException
+ {
+ for (int i = 0; i < callbacks.length; i++) {
+ if (callbacks[i] instanceof NameCallback) {
+ final NameCallback nc = (NameCallback) callbacks[i];
+ nc.setName(name);
+ } else if (callbacks[i] instanceof PasswordCallback) {
+ final PasswordCallback pc = (PasswordCallback) callbacks[i];
+ if (password != null) {
+ pc.setPassword(password.toCharArray());
+ }
+ } else {
+ throw new UnsupportedCallbackException(callbacks[i], "Unsupported");
+ }
+ }
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/jaas/TestLoginModule.java b/src/test/java/edu/vt/middleware/ldap/jaas/TestLoginModule.java
new file mode 100644
index 0000000..34173d8
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/jaas/TestLoginModule.java
@@ -0,0 +1,112 @@
+/*
+ $Id: TestLoginModule.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.jaas;
+
+import java.io.IOException;
+import java.util.Map;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+/**
+ * <code>TestLoginModule</code> is a test login module.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class TestLoginModule implements LoginModule
+{
+
+ /** Initialized subject. */
+ protected Subject subject;
+
+ /** Initialized callback handler. */
+ protected CallbackHandler callbackHandler;
+
+ /** Shared state from other login module. */
+ @SuppressWarnings("unchecked")
+ protected Map sharedState;
+
+ /** Whether authentication was successful. */
+ protected boolean success;
+
+
+ /** {@inheritDoc} */
+ public void initialize(
+ final Subject subject,
+ final CallbackHandler callbackHandler,
+ final Map<String, ?> sharedState,
+ final Map<String, ?> options)
+ {
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+ this.sharedState = sharedState;
+ }
+
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("unchecked")
+ public boolean login()
+ throws LoginException
+ {
+ try {
+ final NameCallback nameCb = new NameCallback("Enter user: ");
+ final PasswordCallback passCb = new PasswordCallback(
+ "Enter user password: ",
+ false);
+ this.callbackHandler.handle(new Callback[] {nameCb, passCb});
+
+ this.sharedState.put(LdapLoginModule.LOGIN_NAME, nameCb.getName());
+ this.sharedState.put(
+ LdapLoginModule.LOGIN_PASSWORD,
+ passCb.getPassword());
+ this.success = true;
+ } catch (IOException e) {
+ this.success = false;
+ throw new LoginException(e.toString());
+ } catch (UnsupportedCallbackException e) {
+ this.success = false;
+ throw new LoginException(e.toString());
+ }
+ return true;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean commit()
+ throws LoginException
+ {
+ return true;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean abort()
+ {
+ this.success = false;
+ return true;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean logout()
+ {
+ return true;
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/ldif/LdifTest.java b/src/test/java/edu/vt/middleware/ldap/ldif/LdifTest.java
new file mode 100644
index 0000000..be107f5
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/ldif/LdifTest.java
@@ -0,0 +1,176 @@
+/*
+ $Id: LdifTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.ldif;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Iterator;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import edu.vt.middleware.ldap.bean.SortedLdapBeanFactory;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link Ldif}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class LdifTest
+{
+
+ /** Entry created for ldap tests. */
+ private static LdapEntry testLdapEntry;
+
+
+ /**
+ * @param ldifFile to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry12" })
+ @BeforeClass(groups = {"ldiftest"})
+ public void createLdapEntry(final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"ldiftest"})
+ public void deleteLdapEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param dn to search on.
+ * @param filter to search with.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({
+ "ldifSearchDn",
+ "ldifSearchFilter"
+ })
+ @Test(groups = {"ldiftest"})
+ public void searchAndCompareLdif(final String dn, final String filter)
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createLdap();
+ final Ldif ldif = new Ldif();
+
+ final Iterator<SearchResult> iter = ldap.search(
+ dn,
+ new SearchFilter(filter));
+
+ final LdapResult result1 = TestUtil.newLdapResult(iter);
+ final StringWriter writer = new StringWriter();
+ ldif.outputLdif(result1.toSearchResults().iterator(), writer);
+
+ final StringReader reader = new StringReader(writer.toString());
+ final LdapResult result2 = TestUtil.newLdapResult(ldif.importLdif(reader));
+
+ AssertJUnit.assertEquals(result1, result2);
+ ldap.close();
+ }
+
+
+ /**
+ * @param ldifFile to create with
+ * @param ldifSortedFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({
+ "ldifEntry",
+ "ldifSortedEntry"
+ })
+ @Test(groups = {"ldiftest"})
+ public void readAndCompareSortedLdif(
+ final String ldifFile,
+ final String ldifSortedFile)
+ throws Exception
+ {
+ final Ldif ldif = new Ldif();
+ ldif.setLdapBeanFactory(new SortedLdapBeanFactory());
+
+ final String ldifStringSorted = TestUtil.readFileIntoString(ldifSortedFile);
+ final Iterator<SearchResult> iter = ldif.importLdif(
+ new StringReader(TestUtil.readFileIntoString(ldifFile)));
+ final StringWriter writer = new StringWriter();
+ ldif.outputLdif(iter, writer);
+
+ AssertJUnit.assertEquals(ldifStringSorted, writer.toString());
+ }
+
+
+ /**
+ * @param ldifFileIn to create with
+ * @param ldifFileOut to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({
+ "multipleLdifResultsIn",
+ "multipleLdifResultsOut"
+ })
+ @Test(groups = {"ldiftest"})
+ public void readAndCompareMultipleLdif(
+ final String ldifFileIn,
+ final String ldifFileOut)
+ throws Exception
+ {
+ final Ldif ldif = new Ldif();
+ final String ldifStringIn = TestUtil.readFileIntoString(ldifFileIn);
+ Iterator<SearchResult> iter = ldif.importLdif(
+ new StringReader(ldifStringIn));
+
+ final LdapResult ldif1 = TestUtil.newLdapResult(iter);
+
+ final String ldifStringOut = TestUtil.readFileIntoString(ldifFileOut);
+ iter = ldif.importLdif(new StringReader(ldifStringOut));
+
+ final LdapResult ldif2 = TestUtil.newLdapResult(iter);
+ AssertJUnit.assertEquals(ldif1, ldif2);
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/pool/LdapPoolTest.java b/src/test/java/edu/vt/middleware/ldap/pool/LdapPoolTest.java
new file mode 100644
index 0000000..d23a4e2
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/pool/LdapPoolTest.java
@@ -0,0 +1,1109 @@
+/*
+ $Id: LdapPoolTest.java 2218 2012-01-23 19:58:08Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2218 $
+ Updated: $Date: 2012-01-23 19:58:08 +0000 (Mon, 23 Jan 2012) $
+*/
+package edu.vt.middleware.ldap.pool;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import javax.naming.directory.SearchResult;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.LdapConfig;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.handler.ConnectionHandler;
+import edu.vt.middleware.ldap.ldif.Ldif;
+import edu.vt.middleware.ldap.pool.commons.CommonsLdapPool;
+import edu.vt.middleware.ldap.pool.commons.DefaultLdapPoolableObjectFactory;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Load test for ldap pools.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2218 $
+ */
+public class LdapPoolTest
+{
+
+ /** Entries for pool tests. */
+ private static Map<String, LdapEntry[]> entries =
+ new HashMap<String, LdapEntry[]>();
+
+ /**
+ * Intialize the map of entries.
+ */
+ static {
+ for (int i = 2; i <= 10; i++) {
+ entries.put(String.valueOf(i), new LdapEntry[2]);
+ }
+ }
+
+ /** Log for this class. */
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ /** LdapPool instance for concurrency testing. */
+ private SoftLimitLdapPool softLimitPool;
+
+ /** LdapPool instance for concurrency testing. */
+ private BlockingLdapPool blockingPool;
+
+ /** LdapPool instance for concurrency testing. */
+ private BlockingLdapPool blockingTimeoutPool;
+
+ /** LdapPool instance for concurrency testing. */
+ private SharedLdapPool sharedPool;
+
+ /** LdapPool instance for concurrency testing. */
+ private BlockingLdapPool connStrategyPool;
+
+ /** LdapPool instance for concurrency testing. */
+ private BlockingLdapPool vtComparisonPool;
+
+ /** Commons LdapPool for comparison testing. */
+ private CommonsLdapPool commonsComparisonPool;
+
+ /** Time in millis it takes the pool test to run. */
+ private long softLimitRuntime;
+
+ /** Time in millis it takes the pool test to run. */
+ private long blockingRuntime;
+
+ /** Time in millis it takes the pool test to run. */
+ private long blockingTimeoutRuntime;
+
+ /** Time in millis it takes the pool test to run. */
+ private long sharedRuntime;
+
+ /** Time in millis it takes the pool test to run. */
+ private long vtPoolRuntime;
+
+ /** Time in millis it takes the pool test to run. */
+ private long commonsPoolRuntime;
+
+
+ /**
+ * Default constructor.
+ *
+ * @throws Exception On test failure.
+ */
+ public LdapPoolTest()
+ throws Exception
+ {
+ final DefaultLdapFactory factory = new DefaultLdapFactory(
+ TestUtil.createLdap().getLdapConfig());
+ factory.setLdapValidator(
+ new CompareLdapValidator(
+ "ou=test,dc=vt,dc=edu",
+ new SearchFilter("ou=test")));
+
+ final LdapPoolConfig softLimitLpc = new LdapPoolConfig();
+ softLimitLpc.setValidateOnCheckIn(true);
+ softLimitLpc.setValidateOnCheckOut(true);
+ softLimitLpc.setValidatePeriodically(true);
+ softLimitLpc.setPruneTimerPeriod(5000L);
+ softLimitLpc.setExpirationTime(1000L);
+ softLimitLpc.setValidateTimerPeriod(5000L);
+ this.softLimitPool = new SoftLimitLdapPool(softLimitLpc, factory);
+
+ final LdapPoolConfig blockingLpc = new LdapPoolConfig();
+ blockingLpc.setValidateOnCheckIn(true);
+ blockingLpc.setValidateOnCheckOut(true);
+ blockingLpc.setValidatePeriodically(true);
+ blockingLpc.setPruneTimerPeriod(5000L);
+ blockingLpc.setExpirationTime(1000L);
+ blockingLpc.setValidateTimerPeriod(5000L);
+ this.blockingPool = new BlockingLdapPool(blockingLpc, factory);
+
+ final LdapPoolConfig blockingTimeoutLpc = new LdapPoolConfig();
+ blockingTimeoutLpc.setValidateOnCheckIn(true);
+ blockingTimeoutLpc.setValidateOnCheckOut(true);
+ blockingTimeoutLpc.setValidatePeriodically(true);
+ blockingTimeoutLpc.setPruneTimerPeriod(5000L);
+ blockingTimeoutLpc.setExpirationTime(1000L);
+ blockingTimeoutLpc.setValidateTimerPeriod(5000L);
+ this.blockingTimeoutPool = new BlockingLdapPool(blockingLpc, factory);
+ this.blockingTimeoutPool.setBlockWaitTime(1000L);
+
+ final LdapPoolConfig sharedLpc = new LdapPoolConfig();
+ sharedLpc.setValidateOnCheckIn(true);
+ sharedLpc.setValidateOnCheckOut(true);
+ sharedLpc.setValidatePeriodically(true);
+ sharedLpc.setPruneTimerPeriod(5000L);
+ sharedLpc.setExpirationTime(1000L);
+ sharedLpc.setValidateTimerPeriod(5000L);
+ this.sharedPool = new SharedLdapPool(sharedLpc, factory);
+
+ final LdapConfig connStrategyLc = TestUtil.createLdap().getLdapConfig();
+ connStrategyLc.setLdapUrl(
+ "ldap://ldap-test-1.middleware.vt.edu:10389 " +
+ "ldap://dne.middleware.vt.edu");
+ connStrategyLc.getConnectionHandler().setConnectionStrategy(
+ ConnectionHandler.ConnectionStrategy.ROUND_ROBIN);
+ final DefaultLdapFactory connStrategyFactory = new DefaultLdapFactory(
+ connStrategyLc);
+ this.connStrategyPool = new BlockingLdapPool(
+ new LdapPoolConfig(), connStrategyFactory);
+
+ // configure comparison pools
+ final LdapPoolConfig vtComparisonLpc = new LdapPoolConfig();
+ vtComparisonLpc.setValidateOnCheckIn(true);
+ vtComparisonLpc.setValidateOnCheckOut(true);
+ this.vtComparisonPool = new BlockingLdapPool(vtComparisonLpc, factory);
+
+ final DefaultLdapPoolableObjectFactory commonsFactory =
+ new DefaultLdapPoolableObjectFactory();
+ commonsFactory.setLdapValidator(
+ new CompareLdapValidator(
+ "ou=test,dc=vt,dc=edu",
+ new SearchFilter("ou=test")));
+ this.commonsComparisonPool = new CommonsLdapPool(commonsFactory);
+ this.commonsComparisonPool.setTestOnReturn(true);
+ this.commonsComparisonPool.setTestOnBorrow(true);
+ }
+
+
+ /**
+ * @param ldifFile2 to create.
+ * @param ldifFile3 to create.
+ * @param ldifFile4 to create.
+ * @param ldifFile5 to create.
+ * @param ldifFile6 to create.
+ * @param ldifFile7 to create.
+ * @param ldifFile8 to create.
+ * @param ldifFile9 to create.
+ * @param ldifFile10 to create.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "createEntry2",
+ "createEntry3",
+ "createEntry4",
+ "createEntry5",
+ "createEntry6",
+ "createEntry7",
+ "createEntry8",
+ "createEntry9",
+ "createEntry10"
+ }
+ )
+ @BeforeClass(
+ groups = {
+ "queuepooltest",
+ "softlimitpooltest",
+ "blockingpooltest",
+ "blockingtimeoutpooltest",
+ "sharedpooltest",
+ "connstrategypooltest",
+ "comparisonpooltest"
+ }
+ )
+ public void createPoolEntry(
+ final String ldifFile2,
+ final String ldifFile3,
+ final String ldifFile4,
+ final String ldifFile5,
+ final String ldifFile6,
+ final String ldifFile7,
+ final String ldifFile8,
+ final String ldifFile9,
+ final String ldifFile10)
+ throws Exception
+ {
+ entries.get("2")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile2));
+ entries.get("3")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile3));
+ entries.get("4")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile4));
+ entries.get("5")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile5));
+ entries.get("6")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile6));
+ entries.get("7")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile7));
+ entries.get("8")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile8));
+ entries.get("9")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile9));
+ entries.get("10")[0] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile10));
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ for (Map.Entry<String, LdapEntry[]> e : entries.entrySet()) {
+ ldap.create(
+ e.getValue()[0].getDn(),
+ e.getValue()[0].getLdapAttributes().toAttributes());
+ }
+ ldap.close();
+
+ ldap = TestUtil.createLdap();
+ for (Map.Entry<String, LdapEntry[]> e : entries.entrySet()) {
+ while (
+ !ldap.compare(
+ e.getValue()[0].getDn(),
+ new SearchFilter(e.getValue()[0].getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ }
+ ldap.close();
+
+ this.softLimitPool.initialize();
+ this.blockingPool.initialize();
+ this.blockingTimeoutPool.initialize();
+ this.sharedPool.initialize();
+ this.connStrategyPool.initialize();
+ }
+
+
+ /**
+ * @param ldifFile2 to load.
+ * @param ldifFile3 to load.
+ * @param ldifFile4 to load.
+ * @param ldifFile5 to load.
+ * @param ldifFile6 to load.
+ * @param ldifFile7 to load.
+ * @param ldifFile8 to load.
+ * @param ldifFile9 to load.
+ * @param ldifFile10 to load.
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "searchResults2",
+ "searchResults3",
+ "searchResults4",
+ "searchResults5",
+ "searchResults6",
+ "searchResults7",
+ "searchResults8",
+ "searchResults9",
+ "searchResults10"
+ }
+ )
+ @BeforeClass(
+ groups = {
+ "queuepooltest",
+ "softlimitpooltest",
+ "blockingpooltest",
+ "blockingtimeoutpooltest",
+ "sharedpooltest",
+ "connstrategypooltest",
+ "comparisonpooltest"
+ }
+ )
+ public void loadPoolSearchResults(
+ final String ldifFile2,
+ final String ldifFile3,
+ final String ldifFile4,
+ final String ldifFile5,
+ final String ldifFile6,
+ final String ldifFile7,
+ final String ldifFile8,
+ final String ldifFile9,
+ final String ldifFile10)
+ throws Exception
+ {
+ entries.get("2")[1] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile2));
+ entries.get("3")[1] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile3));
+ entries.get("4")[1] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile4));
+ entries.get("5")[1] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile5));
+ entries.get("6")[1] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile6));
+ entries.get("7")[1] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile7));
+ entries.get("8")[1] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile8));
+ entries.get("9")[1] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile9));
+ entries.get("10")[1] = TestUtil.convertLdifToEntry(
+ TestUtil.readFileIntoString(ldifFile10));
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(
+ groups = {
+ "queuepooltest",
+ "softlimitpooltest",
+ "blockingpooltest",
+ "blockingtimeoutpooltest",
+ "sharedpooltest",
+ "connstrategypooltest",
+ "comparisonpooltest"
+ }
+ )
+ public void deletePoolEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(entries.get("2")[0].getDn());
+ ldap.delete(entries.get("3")[0].getDn());
+ ldap.delete(entries.get("4")[0].getDn());
+ ldap.delete(entries.get("5")[0].getDn());
+ ldap.delete(entries.get("6")[0].getDn());
+ ldap.delete(entries.get("7")[0].getDn());
+ ldap.delete(entries.get("8")[0].getDn());
+ ldap.delete(entries.get("9")[0].getDn());
+ ldap.delete(entries.get("10")[0].getDn());
+ ldap.close();
+
+ this.softLimitPool.close();
+ AssertJUnit.assertEquals(this.softLimitPool.availableCount(), 0);
+ AssertJUnit.assertEquals(this.softLimitPool.activeCount(), 0);
+ this.blockingPool.close();
+ AssertJUnit.assertEquals(this.blockingPool.availableCount(), 0);
+ AssertJUnit.assertEquals(this.blockingPool.activeCount(), 0);
+ this.blockingTimeoutPool.close();
+ AssertJUnit.assertEquals(this.blockingTimeoutPool.availableCount(), 0);
+ AssertJUnit.assertEquals(this.blockingTimeoutPool.activeCount(), 0);
+ this.sharedPool.close();
+ AssertJUnit.assertEquals(this.sharedPool.availableCount(), 0);
+ AssertJUnit.assertEquals(this.sharedPool.activeCount(), 0);
+ this.connStrategyPool.close();
+ AssertJUnit.assertEquals(this.connStrategyPool.availableCount(), 0);
+ AssertJUnit.assertEquals(this.connStrategyPool.activeCount(), 0);
+ this.vtComparisonPool.close();
+ AssertJUnit.assertEquals(this.vtComparisonPool.availableCount(), 0);
+ AssertJUnit.assertEquals(this.vtComparisonPool.activeCount(), 0);
+ this.commonsComparisonPool.clear();
+ this.commonsComparisonPool.close();
+ AssertJUnit.assertEquals(this.commonsComparisonPool.getNumActive(), 0);
+ AssertJUnit.assertEquals(this.commonsComparisonPool.getNumIdle(), 0);
+ // vt pool should be minimally faster
+ AssertJUnit.assertEquals(
+ this.vtPoolRuntime,
+ Math.min(this.vtPoolRuntime, this.commonsPoolRuntime));
+ }
+
+
+ /**
+ * Sample user data.
+ *
+ * @return user data
+ */
+ @DataProvider(name = "pool-data")
+ public Object[][] createPoolData()
+ {
+ return
+ new Object[][] {
+ {
+ new SearchFilter("mail=jdoe2 at vt.edu"),
+ "departmentNumber|givenName|sn",
+ entries.get("2")[1],
+ },
+ {
+ new SearchFilter("mail=jdoe3 at vt.edu"),
+ "departmentNumber|givenName|sn",
+ entries.get("3")[1],
+ },
+ {
+ new SearchFilter("mail=jdoe4 at vt.edu"),
+ "departmentNumber|givenName|sn",
+ entries.get("4")[1],
+ },
+ {
+ new SearchFilter("mail=jdoe5 at vt.edu"),
+ "departmentNumber|givenName|sn",
+ entries.get("5")[1],
+ },
+ {
+ new SearchFilter("mail=jdoe6 at vt.edu"),
+ "departmentNumber|givenName|sn",
+ entries.get("6")[1],
+ },
+ {
+ new SearchFilter("mail=jdoe7 at vt.edu"),
+ "departmentNumber|givenName|sn",
+ entries.get("7")[1],
+ },
+ {
+ new SearchFilter("mail=jdoe8 at vt.edu"),
+ "departmentNumber|givenName|sn|jpegPhoto",
+ entries.get("8")[1],
+ },
+ {
+ new SearchFilter("mail=jdoe9 at vt.edu"),
+ "departmentNumber|givenName|sn",
+ entries.get("9")[1],
+ },
+ {
+ new SearchFilter("mail=jdoe10 at vt.edu"),
+ "departmentNumber|givenName|sn",
+ entries.get("10")[1],
+ },
+ };
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"softlimitpooltest"})
+ public void checkSoftLimitPoolImmutable()
+ throws Exception
+ {
+ try {
+ this.softLimitPool.getLdapPoolConfig().setMinPoolSize(8);
+ AssertJUnit.fail("Expected illegalstateexception to be thrown");
+ } catch (IllegalStateException e) {
+ AssertJUnit.assertEquals(IllegalStateException.class, e.getClass());
+ }
+
+ Ldap ldap = null;
+ try {
+ ldap = this.softLimitPool.checkOut();
+ try {
+ ldap.setLdapConfig(new LdapConfig());
+ AssertJUnit.fail("Expected illegalstateexception to be thrown");
+ } catch (IllegalStateException e) {
+ AssertJUnit.assertEquals(IllegalStateException.class, e.getClass());
+ }
+ try {
+ ldap.getLdapConfig().setTimeout(10000);
+ AssertJUnit.fail("Expected illegalstateexception to be thrown");
+ } catch (IllegalStateException e) {
+ AssertJUnit.assertEquals(IllegalStateException.class, e.getClass());
+ }
+ } finally {
+ this.softLimitPool.checkIn(ldap);
+ }
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "softlimitpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 3,
+ invocationCount = 50,
+ timeOut = 60000
+ )
+ public void softLimitSmallSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ this.softLimitRuntime += this.search(
+ this.softLimitPool,
+ filter,
+ returnAttrs,
+ results);
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "softlimitpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000,
+ dependsOnMethods = {"softLimitSmallSearch"}
+ )
+ public void softLimitMediumSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ this.softLimitRuntime += this.search(
+ this.softLimitPool,
+ filter,
+ returnAttrs,
+ results);
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(
+ groups = {"queuepooltest", "softlimitpooltest"},
+ dependsOnMethods = {"softLimitMediumSearch"}
+ )
+ public void softLimitMaxClean()
+ throws Exception
+ {
+ Thread.sleep(10000);
+ AssertJUnit.assertEquals(0, this.softLimitPool.activeCount());
+ AssertJUnit.assertEquals(
+ LdapPoolConfig.DEFAULT_MIN_POOL_SIZE,
+ this.softLimitPool.availableCount());
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"blockingpooltest"})
+ public void checkBlockingPoolImmutable()
+ throws Exception
+ {
+ try {
+ this.blockingPool.getLdapPoolConfig().setMinPoolSize(8);
+ AssertJUnit.fail("Expected illegalstateexception to be thrown");
+ } catch (IllegalStateException e) {
+ AssertJUnit.assertEquals(IllegalStateException.class, e.getClass());
+ }
+
+ Ldap ldap = null;
+ try {
+ ldap = this.blockingPool.checkOut();
+ try {
+ ldap.setLdapConfig(new LdapConfig());
+ AssertJUnit.fail("Expected illegalstateexception to be thrown");
+ } catch (IllegalStateException e) {
+ AssertJUnit.assertEquals(IllegalStateException.class, e.getClass());
+ }
+ try {
+ ldap.getLdapConfig().setTimeout(10000);
+ AssertJUnit.fail("Expected illegalstateexception to be thrown");
+ } catch (IllegalStateException e) {
+ AssertJUnit.assertEquals(IllegalStateException.class, e.getClass());
+ }
+ } finally {
+ this.blockingPool.checkIn(ldap);
+ }
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "blockingpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 3,
+ invocationCount = 50,
+ timeOut = 60000
+ )
+ public void blockingSmallSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ this.blockingRuntime += this.search(
+ this.blockingPool,
+ filter,
+ returnAttrs,
+ results);
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "blockingpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000,
+ dependsOnMethods = {"blockingSmallSearch"}
+ )
+ public void blockingMediumSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ this.blockingRuntime += this.search(
+ this.blockingPool,
+ filter,
+ returnAttrs,
+ results);
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "blockingpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 50,
+ invocationCount = 1000,
+ timeOut = 60000,
+ dependsOnMethods = {"blockingMediumSearch"}
+ )
+ public void blockingLargeSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ this.blockingRuntime += this.search(
+ this.blockingPool,
+ filter,
+ returnAttrs,
+ results);
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(
+ groups = {"queuepooltest", "blockingpooltest"},
+ dependsOnMethods = {"blockingLargeSearch"}
+ )
+ public void blockingMaxClean()
+ throws Exception
+ {
+ Thread.sleep(10000);
+ AssertJUnit.assertEquals(0, this.blockingPool.activeCount());
+ AssertJUnit.assertEquals(
+ LdapPoolConfig.DEFAULT_MIN_POOL_SIZE,
+ this.blockingPool.availableCount());
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "blockingtimeoutpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 3,
+ invocationCount = 50,
+ timeOut = 60000
+ )
+ public void blockingTimeoutSmallSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ try {
+ this.blockingTimeoutRuntime += this.search(
+ this.blockingTimeoutPool,
+ filter,
+ returnAttrs,
+ results);
+ } catch (BlockingTimeoutException e) {
+ this.logger.info("block timeout exceeded");
+ }
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "blockingtimeoutpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000,
+ dependsOnMethods = {"blockingTimeoutSmallSearch"}
+ )
+ public void blockingTimeoutMediumSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ try {
+ this.blockingTimeoutRuntime += this.search(
+ this.blockingTimeoutPool,
+ filter,
+ returnAttrs,
+ results);
+ } catch (BlockingTimeoutException e) {
+ this.logger.info("block timeout exceeded");
+ }
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "blockingtimeoutpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 50,
+ invocationCount = 1000,
+ timeOut = 60000,
+ dependsOnMethods = {"blockingTimeoutMediumSearch"}
+ )
+ public void blockingTimeoutLargeSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ try {
+ this.blockingTimeoutRuntime += this.search(
+ this.blockingTimeoutPool,
+ filter,
+ returnAttrs,
+ results);
+ } catch (BlockingTimeoutException e) {
+ this.logger.info("block timeout exceeded");
+ }
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(
+ groups = {"queuepooltest", "blockingtimeoutpooltest"},
+ dependsOnMethods = {"blockingTimeoutLargeSearch"}
+ )
+ public void blockingTimeoutMaxClean()
+ throws Exception
+ {
+ Thread.sleep(10000);
+ AssertJUnit.assertEquals(0, this.blockingTimeoutPool.activeCount());
+ AssertJUnit.assertEquals(
+ LdapPoolConfig.DEFAULT_MIN_POOL_SIZE,
+ this.blockingTimeoutPool.availableCount());
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"sharedpooltest"})
+ public void checkSharedPoolImmutable()
+ throws Exception
+ {
+ try {
+ this.sharedPool.getLdapPoolConfig().setMinPoolSize(8);
+ AssertJUnit.fail("Expected illegalstateexception to be thrown");
+ } catch (IllegalStateException e) {
+ AssertJUnit.assertEquals(IllegalStateException.class, e.getClass());
+ }
+
+ Ldap ldap = null;
+ try {
+ ldap = this.sharedPool.checkOut();
+ try {
+ ldap.setLdapConfig(new LdapConfig());
+ AssertJUnit.fail("Expected illegalstateexception to be thrown");
+ } catch (IllegalStateException e) {
+ AssertJUnit.assertEquals(IllegalStateException.class, e.getClass());
+ }
+ try {
+ ldap.getLdapConfig().setTimeout(10000);
+ AssertJUnit.fail("Expected illegalstateexception to be thrown");
+ } catch (IllegalStateException e) {
+ AssertJUnit.assertEquals(IllegalStateException.class, e.getClass());
+ }
+ } finally {
+ this.sharedPool.checkIn(ldap);
+ }
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "sharedpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 3,
+ invocationCount = 50,
+ timeOut = 60000
+ )
+ public void sharedSmallSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ this.sharedRuntime += this.search(
+ this.sharedPool,
+ filter,
+ returnAttrs,
+ results);
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "sharedpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000,
+ dependsOnMethods = {"sharedSmallSearch"}
+ )
+ public void sharedMediumSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ this.sharedRuntime += this.search(
+ this.sharedPool,
+ filter,
+ returnAttrs,
+ results);
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "sharedpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 50,
+ invocationCount = 1000,
+ timeOut = 60000,
+ dependsOnMethods = {"sharedMediumSearch"}
+ )
+ public void sharedLargeSearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ this.sharedRuntime += this.search(
+ this.sharedPool,
+ filter,
+ returnAttrs,
+ results);
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(
+ groups = {"queuepooltest", "sharedpooltest"},
+ dependsOnMethods = {"sharedLargeSearch"}
+ )
+ public void sharedMaxClean()
+ throws Exception
+ {
+ Thread.sleep(10000);
+ AssertJUnit.assertEquals(0, this.sharedPool.activeCount());
+ AssertJUnit.assertEquals(
+ LdapPoolConfig.DEFAULT_MIN_POOL_SIZE,
+ this.sharedPool.availableCount());
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"queuepooltest", "connstrategypooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 10,
+ invocationCount = 100,
+ timeOut = 60000
+ )
+ public void connStrategySearch(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ this.search(this.connStrategyPool, filter, returnAttrs, results);
+ }
+
+
+ /**
+ * @param pool to get ldap object from.
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @return time it takes to checkout/search/checkin from the pool
+ *
+ * @throws Exception On test failure.
+ */
+ private long search(
+ final LdapPool<Ldap> pool,
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ final long startTime = System.currentTimeMillis();
+ Ldap ldap = null;
+ Iterator<SearchResult> iter = null;
+ try {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("waiting for pool checkout");
+ }
+ ldap = pool.checkOut();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("performing search");
+ }
+ iter = ldap.search(filter, returnAttrs.split("\\|"));
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("search completed");
+ }
+ } finally {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("returning ldap to pool");
+ }
+ pool.checkIn(ldap);
+ }
+ AssertJUnit.assertEquals(
+ results,
+ TestUtil.convertLdifToEntry((new Ldif()).createLdif(iter)));
+ return System.currentTimeMillis() - startTime;
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"comparisonpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 50,
+ invocationCount = 1000,
+ timeOut = 60000
+ )
+ public void vtPoolComparison(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ final long startTime = System.currentTimeMillis();
+ Ldap ldap = null;
+ try {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("waiting for pool checkout");
+ }
+ ldap = this.vtComparisonPool.checkOut();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("performing search");
+ }
+ ldap.search(filter, returnAttrs.split("\\|"));
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("search completed");
+ }
+ } finally {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("returning ldap to pool");
+ }
+ this.vtComparisonPool.checkIn(ldap);
+ }
+ this.vtPoolRuntime += System.currentTimeMillis() - startTime;
+ }
+
+
+ /**
+ * @param filter to search with.
+ * @param returnAttrs to search for.
+ * @param results to expect from the search.
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(
+ groups = {"comparisonpooltest"},
+ dataProvider = "pool-data",
+ threadPoolSize = 50,
+ invocationCount = 1000,
+ timeOut = 60000
+ )
+ public void commonsPoolComparison(
+ final SearchFilter filter,
+ final String returnAttrs,
+ final LdapEntry results)
+ throws Exception
+ {
+ final long startTime = System.currentTimeMillis();
+ Ldap ldap = null;
+ try {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("waiting for pool checkout");
+ }
+ ldap = (Ldap) this.commonsComparisonPool.borrowObject();
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("performing search");
+ }
+ ldap.search(filter, returnAttrs.split("\\|"));
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("search completed");
+ }
+ } finally {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("returning ldap to pool");
+ }
+ this.commonsComparisonPool.returnObject(ldap);
+ }
+ this.commonsPoolRuntime += System.currentTimeMillis() - startTime;
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/pool/commons/CommonsLdapPool.java b/src/test/java/edu/vt/middleware/ldap/pool/commons/CommonsLdapPool.java
new file mode 100644
index 0000000..1258171
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/pool/commons/CommonsLdapPool.java
@@ -0,0 +1,46 @@
+/*
+ $Id: CommonsLdapPool.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool.commons;
+
+import org.apache.commons.pool.PoolableObjectFactory;
+import org.apache.commons.pool.impl.GenericObjectPool;
+
+/**
+ * <code>CommonsLdapPool</code> provides a implementation of a commons pooling
+ * <code>GenericObjectPool</code> for testing.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class CommonsLdapPool extends GenericObjectPool
+{
+
+
+ /** Creates a new ldap pool using {@link DefaultLdapPoolableObjectFactory}. */
+ public CommonsLdapPool()
+ {
+ this(new DefaultLdapPoolableObjectFactory());
+ }
+
+
+ /**
+ * Creates a new ldap pool using the supplied poolable object factory.
+ *
+ * @param poolableObjectFactory to create Ldap objects with
+ */
+ public CommonsLdapPool(final PoolableObjectFactory poolableObjectFactory)
+ {
+ super(poolableObjectFactory);
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/pool/commons/DefaultLdapPoolableObjectFactory.java b/src/test/java/edu/vt/middleware/ldap/pool/commons/DefaultLdapPoolableObjectFactory.java
new file mode 100644
index 0000000..33319a0
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/pool/commons/DefaultLdapPoolableObjectFactory.java
@@ -0,0 +1,64 @@
+/*
+ $Id: DefaultLdapPoolableObjectFactory.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.pool.commons;
+
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.pool.DefaultLdapFactory;
+import org.apache.commons.pool.PoolableObjectFactory;
+
+/**
+ * <code>DefaultLdapPoolableObjectFactory</code> provides a implementation of a
+ * commons pooling <code>PoolableObjectFactory</code> for testing.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class DefaultLdapPoolableObjectFactory extends DefaultLdapFactory
+ implements PoolableObjectFactory
+{
+
+ /** {@inheritDoc} */
+ public void activateObject(final Object obj)
+ {
+ this.activate((Ldap) obj);
+ }
+
+
+ /** {@inheritDoc} */
+ public void destroyObject(final Object obj)
+ {
+ this.destroy((Ldap) obj);
+ }
+
+
+ /** {@inheritDoc} */
+ public Object makeObject()
+ {
+ return this.create();
+ }
+
+
+ /** {@inheritDoc} */
+ public void passivateObject(final Object obj)
+ {
+ this.passivate((Ldap) obj);
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean validateObject(final Object obj)
+ {
+ return this.validate((Ldap) obj);
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/servlets/AttributeServletTest.java b/src/test/java/edu/vt/middleware/ldap/servlets/AttributeServletTest.java
new file mode 100644
index 0000000..fc1ce15
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/servlets/AttributeServletTest.java
@@ -0,0 +1,161 @@
+/*
+ $Id: AttributeServletTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import com.meterware.httpunit.PostMethodWebRequest;
+import com.meterware.httpunit.WebRequest;
+import com.meterware.httpunit.WebResponse;
+import com.meterware.servletunit.ServletRunner;
+import com.meterware.servletunit.ServletUnitClient;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.LdapUtil;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link AttributeServlet}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class AttributeServletTest
+{
+
+ /** Entry created for tests. */
+ private static LdapEntry testLdapEntry;
+
+ /** To test servlets with. */
+ private ServletRunner servletRunner;
+
+
+ /**
+ * @param ldifFile to create.
+ * @param webXml web.xml for queries
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry9", "webXml" })
+ @BeforeClass(groups = {"servlettest"})
+ public void createLdapEntry(final String ldifFile, final String webXml)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+
+ this.servletRunner = new ServletRunner(new File(webXml));
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"servlettest"})
+ public void deleteLdapEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param query to search for.
+ * @param attr attribute to return from search
+ * @param attributeValue to compare
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "attributeServletQuery",
+ "attributeServletAttr",
+ "attributeServletValue"
+ }
+ )
+ @Test(groups = {"servlettest"})
+ public void attributeServlet(
+ final String query,
+ final String attr,
+ final String attributeValue)
+ throws Exception
+ {
+ final ServletUnitClient sc = this.servletRunner.newClient();
+ final WebRequest request = new PostMethodWebRequest(
+ "http://servlets.ldap.middleware.vt.edu/AttributeSearch");
+ request.setParameter("query", query);
+ request.setParameter("attr", attr);
+ request.setParameter("content-type", "octet");
+
+ final WebResponse response = sc.getResponse(request);
+
+ AssertJUnit.assertNotNull(response);
+ AssertJUnit.assertEquals(
+ "application/octet-stream",
+ response.getContentType());
+ AssertJUnit.assertEquals(
+ "attachment; filename=\"" + attr + ".bin\"",
+ response.getHeaderField("Content-Disposition"));
+
+ final InputStream input = response.getInputStream();
+ final ByteArrayOutputStream data = new ByteArrayOutputStream();
+ if (input != null) {
+ try {
+ final byte[] buffer = new byte[128];
+ int length;
+ while ((length = input.read(buffer)) != -1) {
+ data.write(buffer, 0, length);
+ }
+ } finally {
+ data.close();
+ }
+ }
+ AssertJUnit.assertEquals(
+ attributeValue,
+ LdapUtil.base64Encode(data.toByteArray()));
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(
+ groups = {"servlettest"},
+ dependsOnMethods = {"attributeServlet"}
+ )
+ public void prunePools()
+ throws Exception
+ {
+ Thread.sleep(10000);
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/servlets/SearchServletTest.java b/src/test/java/edu/vt/middleware/ldap/servlets/SearchServletTest.java
new file mode 100644
index 0000000..7c9d27a
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/servlets/SearchServletTest.java
@@ -0,0 +1,211 @@
+/*
+ $Id: SearchServletTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets;
+
+import java.io.File;
+import com.meterware.httpunit.PostMethodWebRequest;
+import com.meterware.httpunit.WebRequest;
+import com.meterware.httpunit.WebResponse;
+import com.meterware.servletunit.ServletRunner;
+import com.meterware.servletunit.ServletUnitClient;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import edu.vt.middleware.ldap.bean.LdapResult;
+import edu.vt.middleware.ldap.dsml.DsmlResultConverter;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link SearchServlet}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class SearchServletTest
+{
+
+ /** Entry created for tests. */
+ private static LdapEntry testLdapEntry;
+
+ /** To test servlets with. */
+ private ServletRunner ldifServletRunner;
+
+ /** To test servlets with. */
+ private ServletRunner dsmlServletRunner;
+
+
+ /**
+ * @param ldifFile to create.
+ * @param webXml web.xml for queries
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry8", "webXml" })
+ @BeforeClass(groups = {"servlettest"})
+ public void createLdapEntry(final String ldifFile, final String webXml)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+
+ this.ldifServletRunner = new ServletRunner(new File(webXml));
+ this.dsmlServletRunner = new ServletRunner(new File(webXml));
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"servlettest"})
+ public void deleteLdapEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param query to search for.
+ * @param attrs attributes to return from search
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "ldifSearchServletQuery",
+ "ldifSearchServletAttrs",
+ "ldifSearchServletLdif"
+ }
+ )
+ @Test(groups = {"servlettest"})
+ public void ldifSearchServlet(
+ final String query,
+ final String attrs,
+ final String ldifFile)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ final LdapEntry entry = TestUtil.convertLdifToEntry(ldif);
+
+ final ServletUnitClient sc = this.ldifServletRunner.newClient();
+ final WebRequest request = new PostMethodWebRequest(
+ "http://servlets.ldap.middleware.vt.edu/LdifSearch");
+ request.setParameter("query", query);
+ request.setParameter("attrs", attrs.split("\\|"));
+
+ final WebResponse response = sc.getResponse(request);
+
+ AssertJUnit.assertNotNull(response);
+ AssertJUnit.assertEquals("text/plain", response.getContentType());
+
+ final LdapEntry result = TestUtil.convertLdifToEntry(response.getText());
+ AssertJUnit.assertEquals(entry, result);
+ }
+
+
+ /**
+ * @param query to search for.
+ * @param attrs attributes to return from search
+ * @param ldifFile to compare with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters(
+ {
+ "dsmlSearchServletQuery",
+ "dsmlSearchServletAttrs",
+ "dsmlSearchServletLdif"
+ }
+ )
+ @Test(groups = {"servlettest"})
+ public void dsmlSearchServlet(
+ final String query,
+ final String attrs,
+ final String ldifFile)
+ throws Exception
+ {
+ final DsmlResultConverter converter = new DsmlResultConverter();
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ final LdapResult result = TestUtil.convertLdifToResult(ldif);
+
+ final ServletUnitClient sc = this.dsmlServletRunner.newClient();
+ // test basic dsml query
+ WebRequest request = new PostMethodWebRequest(
+ "http://servlets.ldap.middleware.vt.edu/DsmlSearch");
+ request.setParameter("query", query);
+ request.setParameter("attrs", attrs.split("\\|"));
+
+ WebResponse response = sc.getResponse(request);
+
+ AssertJUnit.assertNotNull(response);
+ AssertJUnit.assertEquals("text/xml", response.getContentType());
+ AssertJUnit.assertEquals(converter.toDsmlv1(result), response.getText());
+
+ // test plain text
+ request = new PostMethodWebRequest(
+ "http://servlets.ldap.middleware.vt.edu/DsmlSearch");
+ request.setParameter("content-type", "text");
+ request.setParameter("query", query);
+ request.setParameter("attrs", attrs.split("\\|"));
+ response = sc.getResponse(request);
+
+ AssertJUnit.assertNotNull(response);
+ AssertJUnit.assertEquals("text/plain", response.getContentType());
+ AssertJUnit.assertEquals(converter.toDsmlv1(result), response.getText());
+
+ // test dsmlv2
+ request = new PostMethodWebRequest(
+ "http://servlets.ldap.middleware.vt.edu/DsmlSearch");
+ request.setParameter("dsml-version", "2");
+ request.setParameter("query", query);
+ request.setParameter("attrs", attrs.split("\\|"));
+ response = sc.getResponse(request);
+
+ AssertJUnit.assertNotNull(response);
+ AssertJUnit.assertEquals("text/xml", response.getContentType());
+ AssertJUnit.assertEquals(converter.toDsmlv2(result), response.getText());
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(
+ groups = {"servlettest"},
+ dependsOnMethods = {"ldifSearchServlet", "dsmlSearchServlet"}
+ )
+ public void prunePools()
+ throws Exception
+ {
+ Thread.sleep(10000);
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/servlets/SessionCheck.java b/src/test/java/edu/vt/middleware/ldap/servlets/SessionCheck.java
new file mode 100644
index 0000000..459981f
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/servlets/SessionCheck.java
@@ -0,0 +1,60 @@
+/*
+ $Id: SessionCheck.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+/**
+ * <code>SessionCheck</code> prints sessions variables for testing purposes.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $ $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+ */
+public class SessionCheck extends HttpServlet
+{
+
+ /** serial version uid. */
+ private static final long serialVersionUID = 2862964801686577549L;
+
+
+ /**
+ * Handle all requests sent to this servlet.
+ *
+ * @param request <code>HttpServletRequest</code>
+ * @param response <code>HttpServletResponse</code>
+ *
+ * @throws ServletException if this request cannot be serviced
+ * @throws IOException if a response cannot be sent
+ */
+ public void service(
+ final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ final PrintWriter out = response.getWriter();
+ final HttpSession session = request.getSession();
+ final Enumeration<?> e = session.getAttributeNames();
+ while (e.hasMoreElements()) {
+ final String k = (String) e.nextElement();
+ out.println(k + ":" + session.getAttribute(k));
+ }
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/servlets/session/SessionManagerTest.java b/src/test/java/edu/vt/middleware/ldap/servlets/session/SessionManagerTest.java
new file mode 100644
index 0000000..e8428db
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/servlets/session/SessionManagerTest.java
@@ -0,0 +1,144 @@
+/*
+ $Id: SessionManagerTest.java 1330 2010-05-23 22:10:53Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1330 $
+ Updated: $Date: 2010-05-23 23:10:53 +0100 (Sun, 23 May 2010) $
+*/
+package edu.vt.middleware.ldap.servlets.session;
+
+import java.io.File;
+import com.meterware.httpunit.GetMethodWebRequest;
+import com.meterware.httpunit.PostMethodWebRequest;
+import com.meterware.httpunit.WebRequest;
+import com.meterware.httpunit.WebResponse;
+import com.meterware.servletunit.ServletRunner;
+import com.meterware.servletunit.ServletUnitClient;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.SearchFilter;
+import edu.vt.middleware.ldap.TestUtil;
+import edu.vt.middleware.ldap.bean.LdapEntry;
+import org.testng.AssertJUnit;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link SessionManager}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1330 $
+ */
+public class SessionManagerTest
+{
+
+ /** Entry created for tests. */
+ private static LdapEntry testLdapEntry;
+
+ /** To test servlets with. */
+ private ServletRunner servletRunner;
+
+
+ /**
+ * @param ldifFile to create.
+ * @param webXml web.xml for queries
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({ "createEntry10", "webXml" })
+ @BeforeClass(groups = {"servlettest"})
+ public void createLdapEntry(final String ldifFile, final String webXml)
+ throws Exception
+ {
+ final String ldif = TestUtil.readFileIntoString(ldifFile);
+ testLdapEntry = TestUtil.convertLdifToEntry(ldif);
+
+ Ldap ldap = TestUtil.createSetupLdap();
+ ldap.create(
+ testLdapEntry.getDn(),
+ testLdapEntry.getLdapAttributes().toAttributes());
+ ldap.close();
+ ldap = TestUtil.createLdap();
+ while (
+ !ldap.compare(
+ testLdapEntry.getDn(),
+ new SearchFilter(testLdapEntry.getDn().split(",")[0]))) {
+ Thread.sleep(100);
+ }
+ ldap.close();
+
+ this.servletRunner = new ServletRunner(new File(webXml));
+ }
+
+
+ /** @throws Exception On test failure. */
+ @AfterClass(groups = {"servlettest"})
+ public void deleteLdapEntry()
+ throws Exception
+ {
+ final Ldap ldap = TestUtil.createSetupLdap();
+ ldap.delete(testLdapEntry.getDn());
+ ldap.close();
+ }
+
+
+ /**
+ * @param user to authenticate
+ * @param password to authenticate with
+ *
+ * @throws Exception On test failure.
+ */
+ @Parameters({
+ "sessionManagerUser",
+ "sessionManagerPassword"
+ })
+ @Test(groups = {"servlettest"})
+ public void login(final String user, final String password)
+ throws Exception
+ {
+ System.setProperty(
+ "javax.net.ssl.trustStore",
+ "src/test/resources/ed.truststore");
+ System.setProperty("javax.net.ssl.trustStoreType", "BKS");
+ System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
+
+ final ServletUnitClient sc = this.servletRunner.newClient();
+ // login
+ WebRequest request = new PostMethodWebRequest(
+ "http://servlets.ldap.middleware.vt.edu/Login");
+ request.setParameter("user", user);
+ request.setParameter("credential", password);
+ request.setParameter(
+ "url",
+ "http://servlets.ldap.middleware.vt.edu/SessionCheck");
+
+ WebResponse response = sc.getResponse(request);
+
+ AssertJUnit.assertNotNull(response);
+
+ final String[] sessionData = response.getText().trim().split(":");
+ AssertJUnit.assertEquals(user, sessionData[1]);
+
+ // logout
+ request = new GetMethodWebRequest(
+ "http://servlets.ldap.middleware.vt.edu/Logout");
+ request.setParameter(
+ "url",
+ "http://servlets.ldap.middleware.vt.edu/SessionCheck");
+ response = sc.getResponse(request);
+
+ AssertJUnit.assertNotNull(response);
+ AssertJUnit.assertEquals("", response.getText());
+
+ System.clearProperty("javax.net.ssl.trustStore");
+ System.clearProperty("javax.net.ssl.trustStoreType");
+ System.clearProperty("javax.net.ssl.trustStorePassword");
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/ssl/DefaultHostnameVerifierTest.java b/src/test/java/edu/vt/middleware/ldap/ssl/DefaultHostnameVerifierTest.java
new file mode 100644
index 0000000..e50fa91
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/ssl/DefaultHostnameVerifierTest.java
@@ -0,0 +1,344 @@
+/*
+ $Id: DefaultHostnameVerifierTest.java 2217 2012-01-23 19:56:35Z dfisher $
+
+ Copyright (C) 2003-2012 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2217 $
+ Updated: $Date: 2012-01-23 19:56:35 +0000 (Mon, 23 Jan 2012) $
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import edu.vt.middleware.ldap.LdapUtil;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link DefaultHostnameVerifier}.
+ * Generate key with: openssl genrsa -aes256 -out test.key 2048
+ * Generate cert with:
+ * openssl req -new -x509 -sha1 -days 3650 -key test.key -out test.crt \
+ * -subj "/CN=a.foo.com/DC=ldaptive/DC=org" -config openssl.cnf \
+ * -extensions my_ext
+ *
+ * @author Middleware Services
+ * @version $Revision: 2217 $
+ */
+public class DefaultHostnameVerifierTest
+{
+
+ /** Instance of the default hostname verifier. */
+ private static final DefaultHostnameVerifier DEFAULT_VERIFIER =
+ new DefaultHostnameVerifier();
+
+ /** Instance of the default startTLS hostname verifier. */
+ private static final SunTLSHostnameVerifier SUN_VERIFIER =
+ new SunTLSHostnameVerifier();
+
+ /** Certificate with CN=a.foo.com. */
+ private static final String A_FOO_COM_CERT =
+ "MIIDrzCCApegAwIBAgIJAK+nL4I3GkjeMA0GCSqGSIb3DQEBBQUAMEMxEjAQBgNV" +
+ "BAMTCWEuZm9vLmNvbTEYMBYGCgmSJomT8ixkARkWCGxkYXB0aXZlMRMwEQYKCZIm" +
+ "iZPyLGQBGRYDb3JnMB4XDTEyMDExNzIxNDAxNVoXDTIyMDExNDIxNDAxNVowQzES" +
+ "MBAGA1UEAxMJYS5mb28uY29tMRgwFgYKCZImiZPyLGQBGRYIbGRhcHRpdmUxEzAR" +
+ "BgoJkiaJk/IsZAEZFgNvcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB" +
+ "AQDGRxBVvGZqHFWbYdbpOZaBf4H68b7zjiqbpXXq+mTfVOehIUeyL0624JsdmHLx" +
+ "oMNC7K9hAaM88wxcyhBRLRfo4ar1DJspzcFUoz2kFD7ytWGS2zwvV+VWnoXpPNiw" +
+ "9QuK6bdA/UYLIg/fk3TwshuoIT9VBJ4L3TRdOYYgH6WJBerQ2L5vMu91B9nBhNqR" +
+ "4RG8VFqwgwW9IoXBXC8XTZS5jd0bVoEoeA+PWVENQ3my5ilP4VUqo9h/jPdb8dFW" +
+ "3TNoaVHjOiTUOIpH+5cUmi0OkH2NzhTaWmCVoWuzFpvvB6PFHHxut2pDe8eGgc4x" +
+ "NdEvZDizbfY6JEb/fkwZ+Im9AgMBAAGjgaUwgaIwHQYDVR0OBBYEFPUscUXspD8Z" +
+ "LP3b6yybVhXhp5C2MHMGA1UdIwRsMGqAFPUscUXspD8ZLP3b6yybVhXhp5C2oUek" +
+ "RTBDMRIwEAYDVQQDEwlhLmZvby5jb20xGDAWBgoJkiaJk/IsZAEZFghsZGFwdGl2" +
+ "ZTETMBEGCgmSJomT8ixkARkWA29yZ4IJAK+nL4I3GkjeMAwGA1UdEwQFMAMBAf8w" +
+ "DQYJKoZIhvcNAQEFBQADggEBALam5DdoM7cyOS2GbiA7QAfZTJkBcVr4Fef9aDWR" +
+ "cG3kzbEbu1OXf3lkRW11H7gPLOgZGebSsxsv6YhKgAtz7py3lyH5QNkrN0OGI1ZA" +
+ "eXf76eSR4T26pYjxln26xyZUW/dcddQ0nSj9Yl52oFCWj38DqGaxP6hIu3DHGlcE" +
+ "PtpM2T4ZjWgrsqxL8N59zMb0Re9V4Xop7KmsLs3ThF3RWwmZdC1ba5LRPK6lKNF5" +
+ "CnSl5YzFUMnpzFZtneUhAHeFxrF+RV4f3bHLNs+sWjlmJo0ukCCnOzoiyE4oOJiL" +
+ "AhDym4nIfzng6fgYBeLT1Hp/bKHivQP4ef4wgre6r1ztnFA=";
+
+ /** Certificate with CN=*.foo.com. */
+ private static final String WC_FOO_COM_CERT =
+ "MIIDrzCCApegAwIBAgIJAJycqMrRasIKMA0GCSqGSIb3DQEBBQUAMEMxEjAQBgNV" +
+ "BAMUCSouZm9vLmNvbTEYMBYGCgmSJomT8ixkARkWCGxkYXB0aXZlMRMwEQYKCZIm" +
+ "iZPyLGQBGRYDb3JnMB4XDTEyMDExNzIyMjQ1N1oXDTIyMDExNDIyMjQ1N1owQzES" +
+ "MBAGA1UEAxQJKi5mb28uY29tMRgwFgYKCZImiZPyLGQBGRYIbGRhcHRpdmUxEzAR" +
+ "BgoJkiaJk/IsZAEZFgNvcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB" +
+ "AQDGRxBVvGZqHFWbYdbpOZaBf4H68b7zjiqbpXXq+mTfVOehIUeyL0624JsdmHLx" +
+ "oMNC7K9hAaM88wxcyhBRLRfo4ar1DJspzcFUoz2kFD7ytWGS2zwvV+VWnoXpPNiw" +
+ "9QuK6bdA/UYLIg/fk3TwshuoIT9VBJ4L3TRdOYYgH6WJBerQ2L5vMu91B9nBhNqR" +
+ "4RG8VFqwgwW9IoXBXC8XTZS5jd0bVoEoeA+PWVENQ3my5ilP4VUqo9h/jPdb8dFW" +
+ "3TNoaVHjOiTUOIpH+5cUmi0OkH2NzhTaWmCVoWuzFpvvB6PFHHxut2pDe8eGgc4x" +
+ "NdEvZDizbfY6JEb/fkwZ+Im9AgMBAAGjgaUwgaIwHQYDVR0OBBYEFPUscUXspD8Z" +
+ "LP3b6yybVhXhp5C2MHMGA1UdIwRsMGqAFPUscUXspD8ZLP3b6yybVhXhp5C2oUek" +
+ "RTBDMRIwEAYDVQQDFAkqLmZvby5jb20xGDAWBgoJkiaJk/IsZAEZFghsZGFwdGl2" +
+ "ZTETMBEGCgmSJomT8ixkARkWA29yZ4IJAJycqMrRasIKMAwGA1UdEwQFMAMBAf8w" +
+ "DQYJKoZIhvcNAQEFBQADggEBACG6nq5fSL8F1zHH0CP+sPWHJEh5OXErdhOfAKVc" +
+ "g0tfvYSI5gsyYTk87TZPTWkmpUUDn1keoVYqyXEaG8qAwL5cNUeYTze6R0GfB0UP" +
+ "jwmkCxZwKhZnN/ryXhzPIEJQHRsg2fYM0P2S6jUG9m92eyCUWrbolmwfkDotbvsS" +
+ "YE6m8oc7OaOVHQ20LDSLML3JOabONKSZW/BODI/ZzWzLNNU45xT4bGbtoyVwEerT" +
+ "WWsGAYdXbsREzuV9q3naEd4wl5CJRBFZtTIizM1RdxxbFrAhTkiDtURTERxLmFxY" +
+ "Nv3gLLhxykIoUIEtTxDjHgAiA02r3yBy5HfIC409WzmdVQI=";
+
+ /** Certificate with CN=*.foo.bar.com. */
+ private static final String WC_FOO_BAR_COM_CERT =
+ "MIIDuzCCAqOgAwIBAgIJAOxfZwQylIyjMA0GCSqGSIb3DQEBBQUAMEcxFjAUBgNV" +
+ "BAMUDSouZm9vLmJhci5jb20xGDAWBgoJkiaJk/IsZAEZFghsZGFwdGl2ZTETMBEG" +
+ "CgmSJomT8ixkARkWA29yZzAeFw0xMjAxMTgxNzE2MTBaFw0yMjAxMTUxNzE2MTBa" +
+ "MEcxFjAUBgNVBAMUDSouZm9vLmJhci5jb20xGDAWBgoJkiaJk/IsZAEZFghsZGFw" +
+ "dGl2ZTETMBEGCgmSJomT8ixkARkWA29yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP" +
+ "ADCCAQoCggEBAMZHEFW8ZmocVZth1uk5loF/gfrxvvOOKpulder6ZN9U56EhR7Iv" +
+ "Trbgmx2YcvGgw0Lsr2EBozzzDFzKEFEtF+jhqvUMmynNwVSjPaQUPvK1YZLbPC9X" +
+ "5Vaehek82LD1C4rpt0D9RgsiD9+TdPCyG6ghP1UEngvdNF05hiAfpYkF6tDYvm8y" +
+ "73UH2cGE2pHhEbxUWrCDBb0ihcFcLxdNlLmN3RtWgSh4D49ZUQ1DebLmKU/hVSqj" +
+ "2H+M91vx0VbdM2hpUeM6JNQ4ikf7lxSaLQ6QfY3OFNpaYJWha7MWm+8Ho8UcfG63" +
+ "akN7x4aBzjE10S9kOLNt9jokRv9+TBn4ib0CAwEAAaOBqTCBpjAdBgNVHQ4EFgQU" +
+ "9SxxReykPxks/dvrLJtWFeGnkLYwdwYDVR0jBHAwboAU9SxxReykPxks/dvrLJtW" +
+ "FeGnkLahS6RJMEcxFjAUBgNVBAMUDSouZm9vLmJhci5jb20xGDAWBgoJkiaJk/Is" +
+ "ZAEZFghsZGFwdGl2ZTETMBEGCgmSJomT8ixkARkWA29yZ4IJAOxfZwQylIyjMAwG" +
+ "A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABMssljEmqtJ1+2ci+l+8zzk" +
+ "Ak+xrkYNMWSjNVJ7B5pmD6MguMxfAiT2QNc0JaI0Zv4h+EprZeELQN3XsCwKRc13" +
+ "v+YuMyBH7xlXzvRQ+/0Y3x5BJKTUELzOdc95vhtwnPVfEwmNhzJAUXxfi0BnT9XZ" +
+ "J02ikAQ8RmtgeTUKDXLZP2xoIJ0YLc8dtdQ/M+ET6WH14kO01vqmk4ZX7oekHP2R" +
+ "W1oko9r9zXl9AKWqEd2p/hD8GiHdK2oS+Ob4Hc3k9UqxaAUxidsQmhRLBJKuHjIt" +
+ "GVqUK9J39FNxChacraSWTdx8yRQOxaKO5PfJDQRgCPg/9aV1AXQW+Y60ILvvHVA=";
+
+ /**
+ * Certificate with CN=a-c.foo.com
+ * subjAltName=DNS:a.foo.com,DNS:b.foo.com,DNS:c.foo.com.
+ */
+ private static final String A_FOO_COM_ALTNAME_CERT =
+ "MIIEAzCCAuugAwIBAgIJAMMwgpWWMq0YMA0GCSqGSIb3DQEBBQUAMEUxFDASBgNV" +
+ "BAMTC2EtYy5mb28uY29tMRgwFgYKCZImiZPyLGQBGRYIbGRhcHRpdmUxEzARBgoJ" +
+ "kiaJk/IsZAEZFgNvcmcwHhcNMTIwMTE4MTYxMDQwWhcNMjIwMTE1MTYxMDQwWjBF" +
+ "MRQwEgYDVQQDEwthLWMuZm9vLmNvbTEYMBYGCgmSJomT8ixkARkWCGxkYXB0aXZl" +
+ "MRMwEQYKCZImiZPyLGQBGRYDb3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB" +
+ "CgKCAQEAxkcQVbxmahxVm2HW6TmWgX+B+vG+844qm6V16vpk31TnoSFHsi9OtuCb" +
+ "HZhy8aDDQuyvYQGjPPMMXMoQUS0X6OGq9QybKc3BVKM9pBQ+8rVhkts8L1flVp6F" +
+ "6TzYsPULium3QP1GCyIP35N08LIbqCE/VQSeC900XTmGIB+liQXq0Ni+bzLvdQfZ" +
+ "wYTakeERvFRasIMFvSKFwVwvF02UuY3dG1aBKHgPj1lRDUN5suYpT+FVKqPYf4z3" +
+ "W/HRVt0zaGlR4zok1DiKR/uXFJotDpB9jc4U2lpglaFrsxab7wejxRx8brdqQ3vH" +
+ "hoHOMTXRL2Q4s232OiRG/35MGfiJvQIDAQABo4H1MIHyMAwGA1UdEwEB/wQCMAAw" +
+ "CwYDVR0PBAQDAgTwMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB0GA1UdDgQWBBT1LHFF" +
+ "7KQ/GSz92+ssm1YV4aeQtjB1BgNVHSMEbjBsgBT1LHFF7KQ/GSz92+ssm1YV4aeQ" +
+ "tqFJpEcwRTEUMBIGA1UEAxMLYS1jLmZvby5jb20xGDAWBgoJkiaJk/IsZAEZFghs" +
+ "ZGFwdGl2ZTETMBEGCgmSJomT8ixkARkWA29yZ4IJAMMwgpWWMq0YMCoGA1UdEQQj" +
+ "MCGCCWEuZm9vLmNvbYIJYi5mb28uY29tggljLmZvby5jb20wDQYJKoZIhvcNAQEF" +
+ "BQADggEBAH59Ewi4dxchcQwgJgA3KkTu6CAb/5S3BCwjv0ERdnnoshrxqu2lrF3e" +
+ "2oW16kGpPdQiIw0OdD/XB3o2It01PjDzdBBBgCas2JtpoQi7/QH0qrvgFqgbzPLV" +
+ "5Ehv1ObyxYKdOMDO7hqYr3PMkYyu4MhsjKp6LRDuFGHqYGzfdUzIjpfPd+jZtiN8" +
+ "EBH+ZmG/PueGFd+vaQu3CIGIkG9fLrfpckUD87x/n6pa+cuWvuAd814fWJpdvLl1" +
+ "iGkLfFU0E2G5pzlk9AHyWiBwYbuUrwLVW7sT7awpnzQBf0NCNETcuRmML7YnunwI" +
+ "3pJosuWr0LZy4fQbu3CquXgY9GNpto8=";
+
+ /**
+ * Certificate with CN=wc.foo.com
+ * subjAltName=DNS:*.foo.com.
+ */
+ private static final String WC_FOO_COM_ALTNAME_CERT =
+ "MIID6jCCAtKgAwIBAgIJAJrNbvmrBDUOMA0GCSqGSIb3DQEBBQUAMEQxEzARBgNV" +
+ "BAMTCndjLmZvby5jb20xGDAWBgoJkiaJk/IsZAEZFghsZGFwdGl2ZTETMBEGCgmS" +
+ "JomT8ixkARkWA29yZzAeFw0xMjAxMTgxNjI2MjJaFw0yMjAxMTUxNjI2MjJaMEQx" +
+ "EzARBgNVBAMTCndjLmZvby5jb20xGDAWBgoJkiaJk/IsZAEZFghsZGFwdGl2ZTET" +
+ "MBEGCgmSJomT8ixkARkWA29yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC" +
+ "ggEBAMZHEFW8ZmocVZth1uk5loF/gfrxvvOOKpulder6ZN9U56EhR7IvTrbgmx2Y" +
+ "cvGgw0Lsr2EBozzzDFzKEFEtF+jhqvUMmynNwVSjPaQUPvK1YZLbPC9X5Vaehek8" +
+ "2LD1C4rpt0D9RgsiD9+TdPCyG6ghP1UEngvdNF05hiAfpYkF6tDYvm8y73UH2cGE" +
+ "2pHhEbxUWrCDBb0ihcFcLxdNlLmN3RtWgSh4D49ZUQ1DebLmKU/hVSqj2H+M91vx" +
+ "0VbdM2hpUeM6JNQ4ikf7lxSaLQ6QfY3OFNpaYJWha7MWm+8Ho8UcfG63akN7x4aB" +
+ "zjE10S9kOLNt9jokRv9+TBn4ib0CAwEAAaOB3jCB2zAMBgNVHRMBAf8EAjAAMAsG" +
+ "A1UdDwQEAwIE8DATBgNVHSUEDDAKBggrBgEFBQcDATAdBgNVHQ4EFgQU9SxxReyk" +
+ "Pxks/dvrLJtWFeGnkLYwdAYDVR0jBG0wa4AU9SxxReykPxks/dvrLJtWFeGnkLah" +
+ "SKRGMEQxEzARBgNVBAMTCndjLmZvby5jb20xGDAWBgoJkiaJk/IsZAEZFghsZGFw" +
+ "dGl2ZTETMBEGCgmSJomT8ixkARkWA29yZ4IJAJrNbvmrBDUOMBQGA1UdEQQNMAuC" +
+ "CSouZm9vLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAcv8obBTxn7odtbjhc/Du36Zt" +
+ "T+HjeO4B8Claf1XgmX8lki2SDO2qOdwA0eaYcOJyKhbdIpspQrp7W8vzvSmN6NPg" +
+ "8XfAZ/xxDil8SfXwVjhHtAU4xYGeYRPY/1WCm8gKlWriV1ECRPn+sxs6DiG+HF7t" +
+ "fEwFBqg1m6FLGycm6H6NMSLL+1sr9MXqjSVetKIlzvGKi4ZdGMRjobGXSx12aCt9" +
+ "BfnIFAf8523sCADmpMs1th/blpzAfHkPXjtLa/6EC8Xj6EZfUaE8UGofgSpyS7wq" +
+ "2ICWGB2oi1ekDMQmP15GtyNm41B2s11KCdDhSCAJu0dyIqWztO3bAGVxR1YTtQ==";
+
+ /** Certificate with CN=127.0.0.1. */
+ private static final String LOCALHOST_CERT =
+ "MIIDrzCCApegAwIBAgIJAO+cKkPsfU8rMA0GCSqGSIb3DQEBBQUAMEMxEjAQBgNV" +
+ "BAMTCTEyNy4wLjAuMTEYMBYGCgmSJomT8ixkARkWCGxkYXB0aXZlMRMwEQYKCZIm" +
+ "iZPyLGQBGRYDb3JnMB4XDTEyMDExNzIyMzM0MFoXDTIyMDExNDIyMzM0MFowQzES" +
+ "MBAGA1UEAxMJMTI3LjAuMC4xMRgwFgYKCZImiZPyLGQBGRYIbGRhcHRpdmUxEzAR" +
+ "BgoJkiaJk/IsZAEZFgNvcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB" +
+ "AQDGRxBVvGZqHFWbYdbpOZaBf4H68b7zjiqbpXXq+mTfVOehIUeyL0624JsdmHLx" +
+ "oMNC7K9hAaM88wxcyhBRLRfo4ar1DJspzcFUoz2kFD7ytWGS2zwvV+VWnoXpPNiw" +
+ "9QuK6bdA/UYLIg/fk3TwshuoIT9VBJ4L3TRdOYYgH6WJBerQ2L5vMu91B9nBhNqR" +
+ "4RG8VFqwgwW9IoXBXC8XTZS5jd0bVoEoeA+PWVENQ3my5ilP4VUqo9h/jPdb8dFW" +
+ "3TNoaVHjOiTUOIpH+5cUmi0OkH2NzhTaWmCVoWuzFpvvB6PFHHxut2pDe8eGgc4x" +
+ "NdEvZDizbfY6JEb/fkwZ+Im9AgMBAAGjgaUwgaIwHQYDVR0OBBYEFPUscUXspD8Z" +
+ "LP3b6yybVhXhp5C2MHMGA1UdIwRsMGqAFPUscUXspD8ZLP3b6yybVhXhp5C2oUek" +
+ "RTBDMRIwEAYDVQQDEwkxMjcuMC4wLjExGDAWBgoJkiaJk/IsZAEZFghsZGFwdGl2" +
+ "ZTETMBEGCgmSJomT8ixkARkWA29yZ4IJAO+cKkPsfU8rMAwGA1UdEwQFMAMBAf8w" +
+ "DQYJKoZIhvcNAQEFBQADggEBAEy+LQguZ0kDdRone/HDnNQfCtWplHU8rE/8oFZo" +
+ "ZVroGGo55zu5Iv66AljeLkTBp7FqIhH9JbwB8CF57g0Uuok560ttoWV/RPisW86p" +
+ "z7eURpPClyel5+uz/PUt8crdNhXqG5iRvO7NlJONVZfLf3KlilXcoSE13msv8X80" +
+ "pDXqOv61kZ4CKB1eAWMT5PXLsks47g42OtHKdOrGv+KGyiMUXmO/9Jxa44maXP6x" +
+ "s8nJ1c5f2zZaZEANTkvO6UFbYynAHisBn9xD++5OcjVJMgX1qOaoxurO2kov5oyw" +
+ "bLLuQaV6NVa+DPs6X6P1+iAmPQNj+Izqveq+8C1vyYdu9VU=";
+
+ /**
+ * Certificate with CN=localhost
+ * subjAltName=IP:127.0.0.1.
+ */
+ private static final String LOCALHOST_ALTNAME_CERT =
+ "MIID4jCCAsqgAwIBAgIJAK/f77u+7Kw2MA0GCSqGSIb3DQEBBQUAMEMxEjAQBgNV" +
+ "BAMTCWxvY2FsaG9zdDEYMBYGCgmSJomT8ixkARkWCGxkYXB0aXZlMRMwEQYKCZIm" +
+ "iZPyLGQBGRYDb3JnMB4XDTEyMDExODE2MDY1NFoXDTIyMDExNTE2MDY1NFowQzES" +
+ "MBAGA1UEAxMJbG9jYWxob3N0MRgwFgYKCZImiZPyLGQBGRYIbGRhcHRpdmUxEzAR" +
+ "BgoJkiaJk/IsZAEZFgNvcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB" +
+ "AQDGRxBVvGZqHFWbYdbpOZaBf4H68b7zjiqbpXXq+mTfVOehIUeyL0624JsdmHLx" +
+ "oMNC7K9hAaM88wxcyhBRLRfo4ar1DJspzcFUoz2kFD7ytWGS2zwvV+VWnoXpPNiw" +
+ "9QuK6bdA/UYLIg/fk3TwshuoIT9VBJ4L3TRdOYYgH6WJBerQ2L5vMu91B9nBhNqR" +
+ "4RG8VFqwgwW9IoXBXC8XTZS5jd0bVoEoeA+PWVENQ3my5ilP4VUqo9h/jPdb8dFW" +
+ "3TNoaVHjOiTUOIpH+5cUmi0OkH2NzhTaWmCVoWuzFpvvB6PFHHxut2pDe8eGgc4x" +
+ "NdEvZDizbfY6JEb/fkwZ+Im9AgMBAAGjgdgwgdUwDAYDVR0TAQH/BAIwADALBgNV" +
+ "HQ8EBAMCBPAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFPUscUXspD8Z" +
+ "LP3b6yybVhXhp5C2MHMGA1UdIwRsMGqAFPUscUXspD8ZLP3b6yybVhXhp5C2oUek" +
+ "RTBDMRIwEAYDVQQDEwlsb2NhbGhvc3QxGDAWBgoJkiaJk/IsZAEZFghsZGFwdGl2" +
+ "ZTETMBEGCgmSJomT8ixkARkWA29yZ4IJAK/f77u+7Kw2MA8GA1UdEQQIMAaHBH8A" +
+ "AAEwDQYJKoZIhvcNAQEFBQADggEBAGa/YCXT/zUV48INqggR0ielSIXz1ztFKG4R" +
+ "sWDoh76MPwyDqONXA3azXKe5BkeXDQ+6cN+VTgHYCpaHnaWdAWgLVqs4prr5MIzk" +
+ "pxIRiVbayzRi0apUq5MyV/XGMECYOf3dPCT2P9Ph4jGJkLHKg66cKoxEPreoCToy" +
+ "GT/1gh18bJ0xAo1CMlc4rH5C1pOx+hOIurFIxjUg44TGnBxMYUmeH0S1B1rmkuFo" +
+ "h65ugoRzPU690x6DkscPxSQKexEjEZG+z0QnsQgaig6SY3bX2kKMa48QywLp0/Vo" +
+ "HddtVv0q6rQqonRHRuCyD+FuXUg0w7BVVRH9txYAsE5eciIc7z0=";
+
+
+ /**
+ * Certificate test data.
+ *
+ * @return cert test data
+ *
+ * @throws Exception if test data cannot be generated
+ */
+ @DataProvider(name = "certificates")
+ public Object[][] createCerts()
+ throws Exception
+ {
+ final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ final X509Certificate aFooComCert =
+ (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(LdapUtil.base64Decode(A_FOO_COM_CERT)));
+ final X509Certificate wcFooComCert =
+ (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(LdapUtil.base64Decode(WC_FOO_COM_CERT)));
+ final X509Certificate wcFooBarComCert =
+ (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(LdapUtil.base64Decode(WC_FOO_BAR_COM_CERT)));
+ final X509Certificate aFooComAltNameCert =
+ (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(
+ LdapUtil.base64Decode(A_FOO_COM_ALTNAME_CERT)));
+ final X509Certificate wcFooComAltNameCert =
+ (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(
+ LdapUtil.base64Decode(WC_FOO_COM_ALTNAME_CERT)));
+ final X509Certificate localhostCert =
+ (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(LdapUtil.base64Decode(LOCALHOST_CERT)));
+ final X509Certificate localhostAltNameCert =
+ (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(
+ LdapUtil.base64Decode(LOCALHOST_ALTNAME_CERT)));
+
+ return new Object[][] {
+ /* a.foo.com == CN=a.foo.com */
+ new Object[] {
+ "a.foo.com", aFooComCert, true,
+ },
+ /* b.foo.com != CN=a.foo.com */
+ new Object[] {
+ "b.foo.com", aFooComCert, false,
+ },
+ /* a.foo.com == CN=*.foo.com */
+ new Object[] {
+ "a.foo.com", wcFooComCert, true,
+ },
+ /* b.foo.com == CN=*.foo.com */
+ new Object[] {
+ "b.foo.com", wcFooComCert, true,
+ },
+ /* a.b.foo.com != CN=*.foo.com */
+ new Object[] {
+ "a.b.foo.com", wcFooComCert, false,
+ },
+ /* a.foo.com != CN=*.foo.bar.com */
+ new Object[] {
+ "a.foo.com", wcFooBarComCert, false,
+ },
+ /* a.b.foo.bar.com != CN=*.foo.bar.com */
+ new Object[] {
+ "a.b.foo.bar.com", wcFooBarComCert, false,
+ },
+ /* a.foo.bar.com == CN=*.foo.bar.com */
+ new Object[] {
+ "a.foo.bar.com", wcFooBarComCert, true,
+ },
+ /* a.foo.com == subjAltName: DNS=a.foo.com */
+ new Object[] {
+ "a.foo.com", aFooComAltNameCert, true,
+ },
+ /* b.foo.com == subjAltName: DNS=b.foo.com */
+ new Object[] {
+ "b.foo.com", aFooComAltNameCert, true,
+ },
+ /* a.foo.com == subjAltName: DNS=*.foo.com */
+ new Object[] {
+ "a.foo.com", wcFooComAltNameCert, true,
+ },
+ /* b.foo.com == subjAltName: DNS=*.foo.com */
+ new Object[] {
+ "b.foo.com", wcFooComAltNameCert, true,
+ },
+ /* a.b.foo.com != subjAltName: DNS=*.foo.com */
+ new Object[] {
+ "a.b.foo.com", wcFooComAltNameCert, false,
+ },
+ /* 10.0.0.1 != CN=127.0.0.1 */
+ new Object[] {
+ "10.0.0.1", localhostCert, false,
+ },
+ /* 127.0.0.1 != CN=127.0.0.1, IPs can only match subjAltName */
+ new Object[] {
+ "127.0.0.1", localhostCert, false,
+ },
+ /* 127.0.0.1 == subjAltName: IP=127.0.0.1 */
+ new Object[] {
+ "127.0.0.1", localhostAltNameCert, true,
+ },
+ };
+ }
+
+
+ /**
+ * @param hostname to match against the cert
+ * @param cert to extract hostname from
+ * @param pass whether the verify should succeed
+ *
+ * @throws Exception On test failure.
+ */
+ @Test(groups = {"ssl"}, dataProvider = "certificates")
+ public void verify(
+ final String hostname, final X509Certificate cert, final boolean pass)
+ throws Exception
+ {
+ final boolean defaultResult = DEFAULT_VERIFIER.verify(hostname, cert);
+ final boolean sunResult = SUN_VERIFIER.verify(hostname, cert);
+ Assert.assertEquals(defaultResult, sunResult);
+ Assert.assertEquals(defaultResult, pass);
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/ssl/SunTLSHostnameVerifier.java b/src/test/java/edu/vt/middleware/ldap/ssl/SunTLSHostnameVerifier.java
new file mode 100644
index 0000000..5835b07
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/ssl/SunTLSHostnameVerifier.java
@@ -0,0 +1,69 @@
+/*
+ $Id: SunTLSHostnameVerifier.java 2217 2012-01-23 19:56:35Z dfisher $
+
+ Copyright (C) 2003-2012 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 2217 $
+ Updated: $Date: 2012-01-23 19:56:35 +0000 (Mon, 23 Jan 2012) $
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import sun.security.util.HostnameChecker;
+
+/**
+ * A {@link HostnameVerifier} that delegates to the internal Sun implementation
+ * at sun.security.util.HostnameChecker. This is the implementation used by
+ * JNDI with StartTLS.
+ *
+ * @author Middleware Services
+ * @version $Revision: 2217 $ $Date: 2012-01-23 19:56:35 +0000 (Mon, 23 Jan 2012) $
+ */
+public class SunTLSHostnameVerifier implements HostnameVerifier
+{
+
+
+ /** {@inheritDoc} */
+ public boolean verify(final String hostname, final SSLSession session)
+ {
+ boolean b = false;
+ try {
+ b = verify(hostname, (X509Certificate) session.getPeerCertificates()[0]);
+ } catch (SSLPeerUnverifiedException e) {
+ b = false;
+ }
+ return b;
+ }
+
+
+ /**
+ * Expose convenience method for testing.
+ *
+ * @param hostname to verify
+ * @param cert to verify hostname against
+ *
+ * @return whether the certificate is allowed
+ */
+ public boolean verify(final String hostname, final X509Certificate cert)
+ {
+ boolean b = false;
+ final HostnameChecker checker = HostnameChecker.getInstance(
+ HostnameChecker.TYPE_LDAP);
+ try {
+ checker.match(hostname, cert);
+ b = true;
+ } catch (CertificateException e) {
+ b = false;
+ }
+ return b;
+ }
+}
diff --git a/src/test/java/edu/vt/middleware/ldap/ssl/TLSSocketFactoryTest.java b/src/test/java/edu/vt/middleware/ldap/ssl/TLSSocketFactoryTest.java
new file mode 100644
index 0000000..46a5ee9
--- /dev/null
+++ b/src/test/java/edu/vt/middleware/ldap/ssl/TLSSocketFactoryTest.java
@@ -0,0 +1,208 @@
+/*
+ $Id: TLSSocketFactoryTest.java 1486 2010-08-17 18:53:58Z dfisher $
+
+ Copyright (C) 2003-2010 Virginia Tech.
+ All rights reserved.
+
+ SEE LICENSE FOR MORE INFORMATION
+
+ Author: Middleware Services
+ Email: middleware at vt.edu
+ Version: $Revision: 1486 $
+ Updated: $Date: 2010-08-17 19:53:58 +0100 (Tue, 17 Aug 2010) $
+*/
+package edu.vt.middleware.ldap.ssl;
+
+import java.util.Arrays;
+import javax.net.ssl.SSLSocket;
+import edu.vt.middleware.ldap.AnyHostnameVerifier;
+import edu.vt.middleware.ldap.Ldap;
+import edu.vt.middleware.ldap.TestUtil;
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link TLSSocketFactory}.
+ *
+ * @author Middleware Services
+ * @version $Revision: 1486 $
+ */
+public class TLSSocketFactoryTest
+{
+
+ /** List of ciphers. */
+ public static final String[] CIPHERS = new String[] {
+ "TLS_DH_anon_WITH_AES_128_CBC_SHA",
+ "TLS_DH_anon_WITH_AES_256_CBC_SHA",
+ "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
+ "SSL_DH_anon_WITH_RC4_128_MD5",
+ "TLS_RSA_WITH_AES_128_CBC_SHA",
+ "TLS_RSA_WITH_AES_256_CBC_SHA",
+ "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
+ "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
+ "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
+ "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
+ "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
+ "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
+ "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
+ "SSL_RSA_WITH_RC4_128_MD5",
+ "SSL_RSA_WITH_RC4_128_SHA",
+ };
+
+ /** List of ciphers. */
+ public static final String[] UNKNOWN_CIPHERS = new String[] {
+ "TLS_DH_anon_WITH_AES_128_CBC_SHA",
+ "TLS_DH_anon_WITH_3DES_256_CBC_SHA",
+ "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
+ "SSL_DH_anon_WITH_RC4_128_MD5",
+ };
+
+ /** List of protocols. */
+ public static final String[] ALL_PROTOCOLS = new String[] {
+ "SSLv2Hello",
+ "SSLv3",
+ "TLSv1",
+ };
+
+ /** List of protocols. */
+ public static final String[] PROTOCOLS = new String[] {
+ "SSLv3",
+ "TLSv1",
+ };
+
+ /** List of protocols. */
+ public static final String[] FAIL_PROTOCOLS = new String[] {
+ "SSLv2Hello",
+ };
+
+ /** List of protocols. */
+ public static final String[] UNKNOWN_PROTOCOLS = new String[] {
+ "SSLv2Hello",
+ "SSLv3Hello",
+ "TLSv1",
+ };
+
+
+ /**
+ * @return <code>Ldap</code>
+ *
+ * @throws Exception On ldap construction failure.
+ */
+ public Ldap createTLSLdap()
+ throws Exception
+ {
+ // configure TLSSocketFactory
+ final X509CertificatesCredentialReader reader =
+ new X509CertificatesCredentialReader();
+ final X509SSLContextInitializer ctxInit =
+ new X509SSLContextInitializer();
+ ctxInit.setTrustCertificates(
+ reader.read("file:src/test/resources/ed.trust.crt"));
+ final TLSSocketFactory sf = new TLSSocketFactory();
+ sf.setSSLContextInitializer(ctxInit);
+ sf.initialize();
+
+ // configure ldap object to use TLS
+ final Ldap ldap = TestUtil.createLdap();
+ ldap.getLdapConfig().setTls(true);
+ ldap.getLdapConfig().setSslSocketFactory(sf);
+ ldap.getLdapConfig().setHostnameVerifier(new AnyHostnameVerifier());
+ return ldap;
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"ssltest"})
+ public void setEnabledCipherSuites()
+ throws Exception
+ {
+ final Ldap ldap = this.createTLSLdap();
+ final TLSSocketFactory sf =
+ (TLSSocketFactory) ldap.getLdapConfig().getSslSocketFactory();
+
+ AssertJUnit.assertTrue(ldap.connect());
+ ldap.getSchema("ou=test,dc=vt,dc=edu");
+ AssertJUnit.assertEquals(
+ Arrays.asList(((SSLSocket) sf.createSocket()).getEnabledCipherSuites()),
+ Arrays.asList(sf.getDefaultCipherSuites()));
+ AssertJUnit.assertNotSame(
+ Arrays.asList(sf.getDefaultCipherSuites()), Arrays.asList(CIPHERS));
+ ldap.close();
+
+ sf.setEnabledCipherSuites(UNKNOWN_CIPHERS);
+ try {
+ ldap.connect();
+ AssertJUnit.fail(
+ "Should have thrown IllegalArgumentException, no exception thrown");
+ } catch (IllegalArgumentException e) {
+ AssertJUnit.assertEquals(IllegalArgumentException.class, e.getClass());
+ } catch (Exception e) {
+ AssertJUnit.fail(
+ "Should have thrown IllegalArgumentException, threw " + e);
+ }
+ ldap.close();
+
+ sf.setEnabledCipherSuites(CIPHERS);
+ AssertJUnit.assertTrue(ldap.connect());
+ ldap.getSchema("ou=test,dc=vt,dc=edu");
+ AssertJUnit.assertEquals(
+ Arrays.asList(((SSLSocket) sf.createSocket()).getEnabledCipherSuites()),
+ Arrays.asList(CIPHERS));
+ ldap.close();
+ }
+
+
+ /** @throws Exception On test failure. */
+ @Test(groups = {"ssltest"})
+ public void setEnabledProtocols()
+ throws Exception
+ {
+ final Ldap ldap = this.createTLSLdap();
+ final TLSSocketFactory sf =
+ (TLSSocketFactory) ldap.getLdapConfig().getSslSocketFactory();
+
+ AssertJUnit.assertTrue(ldap.connect());
+ ldap.getSchema("ou=test,dc=vt,dc=edu");
+ AssertJUnit.assertEquals(
+ Arrays.asList(((SSLSocket) sf.createSocket()).getEnabledProtocols()),
+ Arrays.asList(ALL_PROTOCOLS));
+ AssertJUnit.assertNotSame(
+ Arrays.asList(((SSLSocket) sf.createSocket()).getEnabledProtocols()),
+ Arrays.asList(PROTOCOLS));
+ ldap.close();
+
+ sf.setEnabledProtocols(FAIL_PROTOCOLS);
+ try {
+ ldap.connect();
+ AssertJUnit.fail(
+ "Should have thrown IllegalArgumentException, no exception thrown");
+ } catch (IllegalArgumentException e) {
+ AssertJUnit.assertEquals(IllegalArgumentException.class, e.getClass());
+ } catch (Exception e) {
+ AssertJUnit.fail(
+ "Should have thrown IllegalArgumentException, threw " + e);
+ }
+ ldap.close();
+
+ sf.setEnabledProtocols(UNKNOWN_PROTOCOLS);
+ try {
+ ldap.connect();
+ AssertJUnit.fail(
+ "Should have thrown IllegalArgumentException, no exception thrown");
+ } catch (IllegalArgumentException e) {
+ AssertJUnit.assertEquals(IllegalArgumentException.class, e.getClass());
+ } catch (Exception e) {
+ AssertJUnit.fail(
+ "Should have thrown IllegalArgumentException, threw " + e);
+ }
+ ldap.close();
+
+ sf.setEnabledProtocols(PROTOCOLS);
+ AssertJUnit.assertTrue(ldap.connect());
+ ldap.getSchema("ou=test,dc=vt,dc=edu");
+ AssertJUnit.assertEquals(
+ Arrays.asList(((SSLSocket) sf.createSocket()).getEnabledProtocols()),
+ Arrays.asList(PROTOCOLS));
+ ldap.close();
+ }
+}
diff --git a/src/test/resources/ed.keystore b/src/test/resources/ed.keystore
new file mode 100644
index 0000000..72750f0
Binary files /dev/null and b/src/test/resources/ed.keystore differ
diff --git a/src/test/resources/ed.trust.crt b/src/test/resources/ed.trust.crt
new file mode 100644
index 0000000..5c61ef7
--- /dev/null
+++ b/src/test/resources/ed.trust.crt
@@ -0,0 +1,42 @@
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAvCgAwIBAgIJAPpeFAkJP5xgMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
+VQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNrc2J1cmcx
+FjAUBgNVBAoTDVZpcmdpbmlhIFRlY2gxEzARBgNVBAsTCk1pZGRsZXdhcmUxJjAk
+BgNVBAMTHWxkYXAtdGVzdC0xLm1pZGRsZXdhcmUudnQuZWR1MB4XDTExMDkyNjE2
+NDczOFoXDTIxMDkyMzE2NDczOFowgYoxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhW
+aXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEWMBQGA1UEChMNVmlyZ2luaWEg
+VGVjaDETMBEGA1UECxMKTWlkZGxld2FyZTEmMCQGA1UEAxMdbGRhcC10ZXN0LTEu
+bWlkZGxld2FyZS52dC5lZHUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJWf
+/vBsfFn6sQo57IHrBzMlPARpDI1DJeqH7zl2UeVzeiZDjGiU4ETSjEsvvQRzLfXZ
+IgJEr1IEAzjCX8wKF4svrmkPK3KN6JvdlknM7Thw5p0NzAh2Bq1R1h7+bUvQJGep
+aizNM0od/mKrJnOnUCWEgcpG91mWg8b1PphGobeNAgMBAAGjgfIwge8wHQYDVR0O
+BBYEFMT2Hkcp6JFq242hWfdMOeT3/hZ1MIG/BgNVHSMEgbcwgbSAFMT2Hkcp6JFq
+242hWfdMOeT3/hZ1oYGQpIGNMIGKMQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmly
+Z2luaWExEzARBgNVBAcTCkJsYWNrc2J1cmcxFjAUBgNVBAoTDVZpcmdpbmlhIFRl
+Y2gxEzARBgNVBAsTCk1pZGRsZXdhcmUxJjAkBgNVBAMTHWxkYXAtdGVzdC0xLm1p
+ZGRsZXdhcmUudnQuZWR1ggkA+l4UCQk/nGAwDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOBgQBe0bV5iZyPupNh2zmdH7opuwldz1sxlkRdUQhKSlYsOqgAKDvS
+DypmR4mqntAULTFGZIdcQ1W8HJcnRc8KuPfNatAV8A9OqMbtDLnmfWkl33JPiDUd
+fIKCXuG4dZ6nn3RbjlKhXzHYADmJzdQNIC3M9eDQBEYmMy8+mV+ErVebBg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAkqgAwIBAgIETwyocDANBgkqhkiG9w0BAQUFADBzMRMwEQYKCZImiZPy
+LGQBGRYDZWR1MRIwEAYKCZImiZPyLGQBGRYCdnQxCzAJBgNVBAoTAnZ0MRMwEQYD
+VQQLEwptaWRkbGV3YXJlMSYwJAYDVQQDEx1sZGFwLXRlc3QtMi5taWRkbGV3YXJl
+LnZ0LmVkdTAeFw0xMjAxMTAyMTA2NTZaFw0xNDAxMDkyMTA2NTZaMHMxEzARBgoJ
+kiaJk/IsZAEZFgNlZHUxEjAQBgoJkiaJk/IsZAEZFgJ2dDELMAkGA1UEChMCdnQx
+EzARBgNVBAsTCm1pZGRsZXdhcmUxJjAkBgNVBAMTHWxkYXAtdGVzdC0yLm1pZGRs
+ZXdhcmUudnQuZWR1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm5xR
+Lm8+NlxUJSR/2tXZrYW8+9kQSJAR+a/xfcxcWETntbLHE4XZzCde28wDRM5QZo5z
+fK91upR3uf4/O+4xmOgiBarP6x1Dtob6JeMeUh6/lSpNuNdkSEbSZ3gS0Vc/OYIr
+mcwLQroqNxi3BPVy6ryzGSnd9/PhOf5R2C3mZcBunCQU9GyEexlvQ4d8UkYBNKz5
+sEHl4hcIIirDNTJ27v752rjVWkxPOtF6KUeRsarP4qK2N5nOipAGRrG5nON5ggFl
+YC3UNdj3tvoDstxRSgPY9RaHgN5uNrMPmLrbwNY3q9YsiRZ7veiqIIvirGc3CXfc
+4tTD5sClKhafguf6BQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQCMvZzuxAVc/G7W
+4Ci/Oxa6YAH6Yq745SVuVAjBAhD9wz+Ney4cuFHeyPFnbYWNlikgeWpoglLkEBta
+SGxSnVM74ZxGLwpaWOuI5d2tv7bWm318a0kDaTiXRrJ+6tg/rjyQ7xxYlI5+CqO+
+nK+rB4hawRhRvzguItNzKJhnCuydM5lZoXMZAPDxRxDy/2LfokvaCKpNjCYb+nGE
+Q66oXAH7AP4OynJ2B5fu4eNgbr8JH4dRH667Y+Pkb03xWabdvp5b+YLmP2iOuq10
+DDm9bpasI5c/SfWdZfWTC3a12WdzZaGBLVxlTRJvegAaktEBXm79OtoSr4TJtNLC
+QTHsT7nJ
+-----END CERTIFICATE-----
diff --git a/src/test/resources/ed.truststore b/src/test/resources/ed.truststore
new file mode 100644
index 0000000..4940c6c
Binary files /dev/null and b/src/test/resources/ed.truststore differ
diff --git a/src/test/resources/edu/vt/middleware/ldap/binaryResults.ldif b/src/test/resources/edu/vt/middleware/ldap/binaryResults.ldif
new file mode 100644
index 0000000..f4436cf
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/binaryResults.ldif
@@ -0,0 +1,5 @@
+dn: uid=2,ou=test,dc=vt,dc=edu
+mail: jdoe2 at vt.edu
+uid: 2
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-2.ldif b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-2.ldif
new file mode 100644
index 0000000..41f9c06
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-2.ldif
@@ -0,0 +1,6 @@
+dn: uugid=group2,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+uid: 2002
+uugid: group2
diff --git a/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-3.ldif b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-3.ldif
new file mode 100644
index 0000000..af80618
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-3.ldif
@@ -0,0 +1,6 @@
+dn: uugid=group3,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+uid: 2003
+uugid: group3
diff --git a/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-4.ldif b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-4.ldif
new file mode 100644
index 0000000..1c182cf
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-4.ldif
@@ -0,0 +1,6 @@
+dn: uugid=group4,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+uid: 2004
+uugid: group4
diff --git a/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-5.ldif b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-5.ldif
new file mode 100644
index 0000000..1396387
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-5.ldif
@@ -0,0 +1,6 @@
+dn: uugid=group5,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+uid: 2005
+uugid: group5
diff --git a/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-6.ldif b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-6.ldif
new file mode 100644
index 0000000..4280be0
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-6.ldif
@@ -0,0 +1,6 @@
+dn: uugid=group6,ou=test,dc=vt,dc=edu
+contactPerson: uid=7,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+uid: 2006
+uugid: group6
diff --git a/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-7.ldif b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-7.ldif
new file mode 100644
index 0000000..9c73ac1
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-7.ldif
@@ -0,0 +1,6 @@
+dn: uugid=group7,ou=test,dc=vt,dc=edu
+contactPerson: uid=7,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+uid: 2007
+uugid: group7
diff --git a/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-8.ldif b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-8.ldif
new file mode 100644
index 0000000..8c58939
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-8.ldif
@@ -0,0 +1,6 @@
+dn: uugid=group8,ou=test,dc=vt,dc=edu
+contactPerson: uid=7,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+uid: 2008
+uugid: group8
diff --git a/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-9.ldif b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-9.ldif
new file mode 100644
index 0000000..60331c5
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createGroupEntry-9.ldif
@@ -0,0 +1,6 @@
+dn: uugid=group9,ou=test,dc=vt,dc=edu
+contactPerson: uid=7,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+uid: 2009
+uugid: group9
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-10.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-10.ldif
new file mode 100644
index 0000000..f3041c3
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-10.ldif
@@ -0,0 +1,16 @@
+dn: uid=10,ou=test,dc=vt,dc=edu
+cn: Johv H Dom
+departmentNumber: 0830
+displayName: Johv H Dom
+givenName: Johv
+initials: JHD
+mail: jdoe10 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Dom
+uid: 10
+userPassword: {SHA}9o7EHN4W9rgG17BMcFdmtzGPux0=
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-11.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-11.ldif
new file mode 100644
index 0000000..dbe6318
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-11.ldif
@@ -0,0 +1,15 @@
+dn: uid=11,ou=test,dc=vt,dc=edu
+cn: Johw H Don
+departmentNumber: 0830
+displayName: Johw H Don
+givenName: Johw
+initials: JHD
+mail: jdoe11 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Don
+uid: 11
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-12.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-12.ldif
new file mode 100644
index 0000000..e137f35
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-12.ldif
@@ -0,0 +1,15 @@
+dn: uid=12,ou=test,dc=vt,dc=edu
+cn: Johx H Doo
+departmentNumber: 0830
+displayName: Johx H Doo
+givenName: Johx
+initials: JHD
+mail: jdoe12 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Doo
+uid: 12
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-2.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-2.ldif
new file mode 100644
index 0000000..932a32f
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-2.ldif
@@ -0,0 +1,16 @@
+dn: uid=2,ou=test,dc=vt,dc=edu
+cn: John H Doe
+departmentNumber: 0822
+displayName: John H Doe
+givenName: John
+initials: JHD
+mail: jdoe2 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Doe
+uid: 2
+userPassword: {SHA}KqYKj/f81HPTIeAUav2eJt85UUc=
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-3.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-3.ldif
new file mode 100644
index 0000000..0f00d3c
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-3.ldif
@@ -0,0 +1,16 @@
+dn: uid=3,ou=test,dc=vt,dc=edu
+cn: Joho H Dof
+departmentNumber: 0823
+displayName: Joho H Dof
+givenName: Joho
+initials: JHD
+mail: jdoe3 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Dof
+uid: 3
+userPassword: {SHA}ERnP037iRzV+A0oI2ETuol9v0g8=
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-4.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-4.ldif
new file mode 100644
index 0000000..2a8eead
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-4.ldif
@@ -0,0 +1,16 @@
+dn: uid=4,ou=test,dc=vt,dc=edu
+cn: Johp H Dog
+departmentNumber: 0824
+displayName: Johp H Dog
+givenName: Johp
+initials: JHD
+mail: jdoe4 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Dog
+uid: 4
+userPassword: {SHA}oddYTarKRzjUma1wgohrARFyddg=
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-5.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-5.ldif
new file mode 100644
index 0000000..fab4e53
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-5.ldif
@@ -0,0 +1,16 @@
+dn: uid=5,ou=test,dc=vt,dc=edu
+cn: Johq H Doh
+departmentNumber: 0825
+displayName: Johq H Doh
+givenName: Johq
+initials: JHD
+mail: jdoe5 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Doh
+uid: 5
+userPassword: {SHA}7bqVXQ6hX9709hcm75flr1B0MMA=
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-6.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-6.ldif
new file mode 100644
index 0000000..4bc39b5
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-6.ldif
@@ -0,0 +1,16 @@
+dn: uid=6,ou=test,dc=vt,dc=edu
+cn: Johr H Doi
+departmentNumber: 0826
+displayName: Johr H Doi
+givenName: Johr
+initials: JHD
+mail: jdoe6 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Doi
+uid: 6
+userPassword: {SHA}bXSeijeKNM8ZtMAveVX1f9uhMKU=
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-7.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-7.ldif
new file mode 100644
index 0000000..66a8e85
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-7.ldif
@@ -0,0 +1,24 @@
+dn: uid=7,ou=test,dc=vt,dc=edu
+cn: Johs H Doj
+creationDate: 2015-06-06T12:00:00
+departmentNumber: 0827
+displayName: Johs H Doj
+eduPersonAffiliation: student
+eduPersonAffiliation: staff
+eduPersonPrimaryAffiliation: student
+givenName: Johs
+initials: JHD
+mail: jdoe7 at vt.edu
+groupMembership: uugid=group6,ou=test,dc=vt,dc=edu
+groupMembership: uugid=group9,ou=test,dc=vt,dc=edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: virginiaTechPerson
+objectClass: top
+personType: Virginia Tech Person
+sn: Doj
+uid: 7
+userPassword: {SHA}MwumDiQxhun6JY+Zkth2bqboi8E=
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-8.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-8.ldif
new file mode 100644
index 0000000..af33503
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-8.ldif
@@ -0,0 +1,16 @@
+dn: uid=8,ou=test,dc=vt,dc=edu
+cn: Joht H Dok
+departmentNumber: 0828
+displayName: Joht H Dok
+givenName: Joht
+initials: JHD
+mail: jdoe8 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Dok
+uid: 8
+userPassword: {SHA}qNu/pBzsgz+N1Cvk0fqaExQshcI=
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-9.ldif b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-9.ldif
new file mode 100644
index 0000000..b58ba56
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/createLdapEntry-9.ldif
@@ -0,0 +1,16 @@
+dn: uid=9,ou=test,dc=vt,dc=edu
+cn: Johu H Dol
+departmentNumber: 0829
+displayName: Johu H Dol
+givenName: Johu
+initials: JHD
+mail: jdoe9 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Dol
+uid: 9
+userPassword: {SHA}AksBkW4+rsZqLEtvxYexcF8ab8g=
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/dfisher.dsmlv1 b/src/test/resources/edu/vt/middleware/ldap/dfisher.dsmlv1
new file mode 100644
index 0000000..489568e
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/dfisher.dsmlv1
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<dsml:dsml xmlns:dsml="http://www.dsml.org/DSML">
+ <dsml:directory-entries>
+ <dsml:entry dn="uid=818037,ou=People,dc=vt,dc=edu">
+ <dsml:objectclass>
+ <dsml:oc-value>virginiaTechPerson</dsml:oc-value>
+ </dsml:objectclass>
+ <dsml:attr name="title">
+ <dsml:value>Project Manager, Middleware</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="uid">
+ <dsml:value>818037</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="authId">
+ <dsml:value>dfisher</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="mail">
+ <dsml:value>dfisher at vt.edu</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="instantMessagingID">
+ <dsml:value>Yahoo!:dfish3r</dsml:value>
+ <dsml:value>Google:dfisher at gmail.com</dsml:value>
+ <dsml:value>Virginia Tech:dfisher at im.vt.edu</dsml:value>
+ <dsml:value>ICQ:8168282</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="department">
+ <dsml:value>MiddleWare</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="sn">
+ <dsml:value>Fisher</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="userCertificate;binary">
+ <dsml:value encoding="base64">MIIC7zCCAligAwIBAgIQCBdZgzdx+yX5+jSRD50SJzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc3VpbmcgQ0EwHhcNMDkwNjI5MTg0NTQ2WhcNMTAwNjI5MTg0NTQ2WjBZMQ8wDQYDVQQEEwZGaXNoZXIxDzANBgNVBCoTBkRhbmllbDEWMBQGA1UEAxMNRGFuaWVsIEZpc2hlcjEdMBsGCSqGSIb3DQEJARYOZGZpc2hlckB2dC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbsC0qvAkU3oWwz39oq/ur/mhHZ+KNODug+pAsg0ZZbSnxWD9EVB [...]
+ </dsml:attr>
+ <dsml:attr name="cn">
+ <dsml:value>Daniel W Fisher</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="mailStop">
+ <dsml:value>0479</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="suppressDisplay">
+ <dsml:value>false</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="suppressedAttribute">
+ <dsml:value>localPostalAddress</dsml:value>
+ <dsml:value>homePostalAddress</dsml:value>
+ <dsml:value>homePhone</dsml:value>
+ <dsml:value>localPhone</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="postalAddress">
+ <dsml:value>SETI-Middleware$1700 Pratt Drive$Blacksburg, VA 24060</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="departmentNumber">
+ <dsml:value>066103</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="uupid">
+ <dsml:value>dfisher</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="middleName">
+ <dsml:value>W</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="givenName">
+ <dsml:value>Daniel</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="telephoneNumber">
+ <dsml:value>5402312096</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="displayName">
+ <dsml:value>Daniel W Fisher</dsml:value>
+ </dsml:attr>
+ </dsml:entry>
+ </dsml:directory-entries>
+</dsml:dsml>
diff --git a/src/test/resources/edu/vt/middleware/ldap/dfisher.dsmlv2 b/src/test/resources/edu/vt/middleware/ldap/dfisher.dsmlv2
new file mode 100644
index 0000000..885baf6
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/dfisher.dsmlv2
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<batchResponse xmlns="urn:oasis:names:tc:DSML:2:0:core">
+ <searchResponse>
+ <searchResultEntry dn="uid=818037,ou=People,dc=vt,dc=edu">
+ <attr name="objectClass">
+ <value>virginiaTechPerson</value>
+ </attr>
+ <attr name="title">
+ <value>Project Manager, Middleware</value>
+ </attr>
+ <attr name="uid">
+ <value>818037</value>
+ </attr>
+ <attr name="authId">
+ <value>dfisher</value>
+ </attr>
+ <attr name="mail">
+ <value>dfisher at vt.edu</value>
+ </attr>
+ <attr name="instantMessagingID">
+ <value>Yahoo!:dfish3r</value>
+ <value>Google:dfisher at gmail.com</value>
+ <value>Virginia Tech:dfisher at im.vt.edu</value>
+ <value>ICQ:8168282</value>
+ </attr>
+ <attr name="department">
+ <value>MiddleWare</value>
+ </attr>
+ <attr name="sn">
+ <value>Fisher</value>
+ </attr>
+ <attr name="userCertificate;binary">
+ <value encoding="base64">MIIC7zCCAligAwIBAgIQCBdZgzdx+yX5+jSRD50SJzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc3VpbmcgQ0EwHhcNMDkwNjI5MTg0NTQ2WhcNMTAwNjI5MTg0NTQ2WjBZMQ8wDQYDVQQEEwZGaXNoZXIxDzANBgNVBCoTBkRhbmllbDEWMBQGA1UEAxMNRGFuaWVsIEZpc2hlcjEdMBsGCSqGSIb3DQEJARYOZGZpc2hlckB2dC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbsC0qvAkU3oWwz39oq/ur/mhHZ+KNODug+pAsg0ZZbSnxWD9EVBiMere [...]
+ </attr>
+ <attr name="cn">
+ <value>Daniel W Fisher</value>
+ </attr>
+ <attr name="mailStop">
+ <value>0479</value>
+ </attr>
+ <attr name="suppressDisplay">
+ <value>false</value>
+ </attr>
+ <attr name="suppressedAttribute">
+ <value>localPostalAddress</value>
+ <value>homePostalAddress</value>
+ <value>homePhone</value>
+ <value>localPhone</value>
+ </attr>
+ <attr name="postalAddress">
+ <value>SETI-Middleware$1700 Pratt Drive$Blacksburg, VA 24060</value>
+ </attr>
+ <attr name="departmentNumber">
+ <value>066103</value>
+ </attr>
+ <attr name="uupid">
+ <value>dfisher</value>
+ </attr>
+ <attr name="middleName">
+ <value>W</value>
+ </attr>
+ <attr name="givenName">
+ <value>Daniel</value>
+ </attr>
+ <attr name="telephoneNumber">
+ <value>5402312096</value>
+ </attr>
+ <attr name="displayName">
+ <value>Daniel W Fisher</value>
+ </attr>
+ </searchResultEntry>
+ <searchResultDone>
+ <resultCode code="0"/>
+ </searchResultDone>
+ </searchResponse>
+</batchResponse>
diff --git a/src/test/resources/edu/vt/middleware/ldap/dfisher.ldif b/src/test/resources/edu/vt/middleware/ldap/dfisher.ldif
new file mode 100644
index 0000000..8d020bb
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/dfisher.ldif
@@ -0,0 +1,28 @@
+dn: uid=818037,ou=People,dc=vt,dc=edu
+middleName: W
+uid: 818037
+mail: dfisher at vt.edu
+sn: Fisher
+instantMessagingID: Yahoo!:dfish3r
+instantMessagingID: Google:dfisher at gmail.com
+instantMessagingID: Virginia Tech:dfisher at im.vt.edu
+instantMessagingID: ICQ:8168282
+department: MiddleWare
+objectClass: virginiaTechPerson
+suppressedAttribute: localPostalAddress
+suppressedAttribute: homePostalAddress
+suppressedAttribute: homePhone
+suppressedAttribute: localPhone
+givenName: Daniel
+title: Project Manager, Middleware
+mailStop: 0479
+departmentNumber: 066103
+cn: Daniel W Fisher
+authId: dfisher
+telephoneNumber: 5402312096
+userCertificate;binary:: MIIC7zCCAligAwIBAgIQCBdZgzdx+yX5+jSRD50SJzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc3VpbmcgQ0EwHhcNMDkwNjI5MTg0NTQ2WhcNMTAwNjI5MTg0NTQ2WjBZMQ8wDQYDVQQEEwZGaXNoZXIxDzANBgNVBCoTBkRhbmllbDEWMBQGA1UEAxMNRGFuaWVsIEZpc2hlcjEdMBsGCSqGSIb3DQEJARYOZGZpc2hlckB2dC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbsC0qvAkU3oWwz39oq/ur/mhHZ+KNODug+pAsg0ZZbSnxWD9EVBiMerevD5bJ0we [...]
+postalAddress: SETI-Middleware$1700 Pratt Drive$Blacksburg, VA 24060
+displayName: Daniel W Fisher
+suppressDisplay: false
+uupid: dfisher
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/dfisher.sorted.dsmlv1 b/src/test/resources/edu/vt/middleware/ldap/dfisher.sorted.dsmlv1
new file mode 100644
index 0000000..457700a
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/dfisher.sorted.dsmlv1
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<dsml:dsml xmlns:dsml="http://www.dsml.org/DSML">
+ <dsml:directory-entries>
+ <dsml:entry dn="uid=818037,ou=People,dc=vt,dc=edu">
+ <dsml:objectclass>
+ <dsml:oc-value>virginiaTechPerson</dsml:oc-value>
+ </dsml:objectclass>
+ <dsml:attr name="authId">
+ <dsml:value>dfisher</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="cn">
+ <dsml:value>Daniel W Fisher</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="department">
+ <dsml:value>MiddleWare</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="departmentNumber">
+ <dsml:value>066103</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="displayName">
+ <dsml:value>Daniel W Fisher</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="givenName">
+ <dsml:value>Daniel</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="instantMessagingID">
+ <dsml:value>Google:dfisher at gmail.com</dsml:value>
+ <dsml:value>ICQ:8168282</dsml:value>
+ <dsml:value>Virginia Tech:dfisher at im.vt.edu</dsml:value>
+ <dsml:value>Yahoo!:dfish3r</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="mail">
+ <dsml:value>dfisher at vt.edu</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="mailStop">
+ <dsml:value>0479</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="middleName">
+ <dsml:value>W</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="postalAddress">
+ <dsml:value>SETI-Middleware$1700 Pratt Drive$Blacksburg, VA 24060</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="sn">
+ <dsml:value>Fisher</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="suppressDisplay">
+ <dsml:value>false</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="suppressedAttribute">
+ <dsml:value>homePhone</dsml:value>
+ <dsml:value>homePostalAddress</dsml:value>
+ <dsml:value>localPhone</dsml:value>
+ <dsml:value>localPostalAddress</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="telephoneNumber">
+ <dsml:value>5402312096</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="title">
+ <dsml:value>Project Manager, Middleware</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="uid">
+ <dsml:value>818037</dsml:value>
+ </dsml:attr>
+ <dsml:attr name="userCertificate;binary">
+ <dsml:value encoding="base64">MIIC7zCCAligAwIBAgIQCBdZgzdx+yX5+jSRD50SJzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc3VpbmcgQ0EwHhcNMDkwNjI5MTg0NTQ2WhcNMTAwNjI5MTg0NTQ2WjBZMQ8wDQYDVQQEEwZGaXNoZXIxDzANBgNVBCoTBkRhbmllbDEWMBQGA1UEAxMNRGFuaWVsIEZpc2hlcjEdMBsGCSqGSIb3DQEJARYOZGZpc2hlckB2dC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbsC0qvAkU3oWwz39oq/ur/mhHZ+KNODug+pAsg0ZZbSnxWD9EVB [...]
+ </dsml:attr>
+ <dsml:attr name="uupid">
+ <dsml:value>dfisher</dsml:value>
+ </dsml:attr>
+ </dsml:entry>
+ </dsml:directory-entries>
+</dsml:dsml>
diff --git a/src/test/resources/edu/vt/middleware/ldap/dfisher.sorted.dsmlv2 b/src/test/resources/edu/vt/middleware/ldap/dfisher.sorted.dsmlv2
new file mode 100644
index 0000000..67e98f0
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/dfisher.sorted.dsmlv2
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<batchResponse xmlns="urn:oasis:names:tc:DSML:2:0:core">
+ <searchResponse>
+ <searchResultEntry dn="uid=818037,ou=People,dc=vt,dc=edu">
+ <attr name="authId">
+ <value>dfisher</value>
+ </attr>
+ <attr name="cn">
+ <value>Daniel W Fisher</value>
+ </attr>
+ <attr name="department">
+ <value>MiddleWare</value>
+ </attr>
+ <attr name="departmentNumber">
+ <value>066103</value>
+ </attr>
+ <attr name="displayName">
+ <value>Daniel W Fisher</value>
+ </attr>
+ <attr name="givenName">
+ <value>Daniel</value>
+ </attr>
+ <attr name="instantMessagingID">
+ <value>Google:dfisher at gmail.com</value>
+ <value>ICQ:8168282</value>
+ <value>Virginia Tech:dfisher at im.vt.edu</value>
+ <value>Yahoo!:dfish3r</value>
+ </attr>
+ <attr name="mail">
+ <value>dfisher at vt.edu</value>
+ </attr>
+ <attr name="mailStop">
+ <value>0479</value>
+ </attr>
+ <attr name="middleName">
+ <value>W</value>
+ </attr>
+ <attr name="objectClass">
+ <value>virginiaTechPerson</value>
+ </attr>
+ <attr name="postalAddress">
+ <value>SETI-Middleware$1700 Pratt Drive$Blacksburg, VA 24060</value>
+ </attr>
+ <attr name="sn">
+ <value>Fisher</value>
+ </attr>
+ <attr name="suppressDisplay">
+ <value>false</value>
+ </attr>
+ <attr name="suppressedAttribute">
+ <value>homePhone</value>
+ <value>homePostalAddress</value>
+ <value>localPhone</value>
+ <value>localPostalAddress</value>
+ </attr>
+ <attr name="telephoneNumber">
+ <value>5402312096</value>
+ </attr>
+ <attr name="title">
+ <value>Project Manager, Middleware</value>
+ </attr>
+ <attr name="uid">
+ <value>818037</value>
+ </attr>
+ <attr name="userCertificate;binary">
+ <value encoding="base64">MIIC7zCCAligAwIBAgIQCBdZgzdx+yX5+jSRD50SJzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc3VpbmcgQ0EwHhcNMDkwNjI5MTg0NTQ2WhcNMTAwNjI5MTg0NTQ2WjBZMQ8wDQYDVQQEEwZGaXNoZXIxDzANBgNVBCoTBkRhbmllbDEWMBQGA1UEAxMNRGFuaWVsIEZpc2hlcjEdMBsGCSqGSIb3DQEJARYOZGZpc2hlckB2dC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbsC0qvAkU3oWwz39oq/ur/mhHZ+KNODug+pAsg0ZZbSnxWD9EVBiMere [...]
+ </attr>
+ <attr name="uupid">
+ <value>dfisher</value>
+ </attr>
+ </searchResultEntry>
+ <searchResultDone>
+ <resultCode code="0"/>
+ </searchResultDone>
+ </searchResponse>
+</batchResponse>
diff --git a/src/test/resources/edu/vt/middleware/ldap/dfisher.sorted.ldif b/src/test/resources/edu/vt/middleware/ldap/dfisher.sorted.ldif
new file mode 100644
index 0000000..352d117
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/dfisher.sorted.ldif
@@ -0,0 +1,28 @@
+dn: uid=818037,ou=People,dc=vt,dc=edu
+authId: dfisher
+cn: Daniel W Fisher
+department: MiddleWare
+departmentNumber: 066103
+displayName: Daniel W Fisher
+givenName: Daniel
+instantMessagingID: Google:dfisher at gmail.com
+instantMessagingID: ICQ:8168282
+instantMessagingID: Virginia Tech:dfisher at im.vt.edu
+instantMessagingID: Yahoo!:dfish3r
+mail: dfisher at vt.edu
+mailStop: 0479
+middleName: W
+objectClass: virginiaTechPerson
+postalAddress: SETI-Middleware$1700 Pratt Drive$Blacksburg, VA 24060
+sn: Fisher
+suppressDisplay: false
+suppressedAttribute: homePhone
+suppressedAttribute: homePostalAddress
+suppressedAttribute: localPhone
+suppressedAttribute: localPostalAddress
+telephoneNumber: 5402312096
+title: Project Manager, Middleware
+uid: 818037
+userCertificate;binary:: MIIC7zCCAligAwIBAgIQCBdZgzdx+yX5+jSRD50SJzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc3VpbmcgQ0EwHhcNMDkwNjI5MTg0NTQ2WhcNMTAwNjI5MTg0NTQ2WjBZMQ8wDQYDVQQEEwZGaXNoZXIxDzANBgNVBCoTBkRhbmllbDEWMBQGA1UEAxMNRGFuaWVsIEZpc2hlcjEdMBsGCSqGSIb3DQEJARYOZGZpc2hlckB2dC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbsC0qvAkU3oWwz39oq/ur/mhHZ+KNODug+pAsg0ZZbSnxWD9EVBiMerevD5bJ0we [...]
+uupid: dfisher
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/getSchemaResults.ldif b/src/test/resources/edu/vt/middleware/ldap/getSchemaResults.ldif
new file mode 100644
index 0000000..f9bae17
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/getSchemaResults.ldif
@@ -0,0 +1,12 @@
+dn: AttributeDefinition
+objectclass: AttributeDefinition
+
+dn: ClassDefinition
+objectclass: ClassDefinition
+
+dn: MatchingRule
+objectclass: MatchingRule
+
+dn: SyntaxDefinition
+objectclass: SyntaxDefinition
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/image.jpg b/src/test/resources/edu/vt/middleware/ldap/image.jpg
new file mode 100644
index 0000000..08404f1
Binary files /dev/null and b/src/test/resources/edu/vt/middleware/ldap/image.jpg differ
diff --git a/src/test/resources/edu/vt/middleware/ldap/mergeDuplicateResults.ldif b/src/test/resources/edu/vt/middleware/ldap/mergeDuplicateResults.ldif
new file mode 100644
index 0000000..46d36fb
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/mergeDuplicateResults.ldif
@@ -0,0 +1,19 @@
+dn: uugid=group3,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+creationDate: 2010-06-06T12:00:00
+creationDate: 2010-06-06T12:00:00
+member: uugid=group3,ou=test,dc=vt,dc=edu
+member: uugid=group4,ou=test,dc=vt,dc=edu
+member: uugid=group5,ou=test,dc=vt,dc=edu
+objectClass: virginiaTechGroup
+objectClass: virginiaTechGroup
+objectClass: virginiaTechGroup
+uid: 2003
+uid: 2004
+uid: 2005
+uugid: group3
+uugid: group4
+uugid: group5
diff --git a/src/test/resources/edu/vt/middleware/ldap/mergeResults.ldif b/src/test/resources/edu/vt/middleware/ldap/mergeResults.ldif
new file mode 100644
index 0000000..f02c509
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/mergeResults.ldif
@@ -0,0 +1,13 @@
+dn: uugid=group3,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+member: uugid=group3,ou=test,dc=vt,dc=edu
+member: uugid=group4,ou=test,dc=vt,dc=edu
+member: uugid=group5,ou=test,dc=vt,dc=edu
+objectClass: virginiaTechGroup
+uid: 2003
+uid: 2004
+uid: 2005
+uugid: group3
+uugid: group4
+uugid: group5
diff --git a/src/test/resources/edu/vt/middleware/ldap/multipleEntriesIn.ldif b/src/test/resources/edu/vt/middleware/ldap/multipleEntriesIn.ldif
new file mode 100644
index 0000000..2785b43
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/multipleEntriesIn.ldif
@@ -0,0 +1,102 @@
+version: 1
+dn: uid=818037,ou=People,dc=vt,dc=edu
+middleName: W
+uid: 818037
+mail: dfisher at vt.edu
+sn: Fisher
+instantMessagingID: Yahoo!:dfish3r
+instantMessagingID: Google:dfisher at gmail.com
+instantMessagingID: Virginia Tech:dfisher at im.vt.edu
+instantMessagingID: ICQ:8168282
+department: MiddleWare
+objectClass: virginiaTechPerson
+# random comment
+suppressedAttribute: localPostalAddress
+suppressedAttribute: homePostalAddress
+# random comment
+suppressedAttribute: homePhone
+suppressedAttribute: localPhone
+givenName: Daniel
+title: Project Manager, Middleware
+mailStop: 0479
+departmentNumber: 066103
+cn: Daniel W Fisher
+authId: dfisher
+telephoneNumber: 5402312096
+userCertificate;binary:: MIIC7zCCAligAwIBAgIQCBdZgzdx+yX5+jSRD50SJzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc3VpbmcgQ0EwHhcNMDkwNjI5MTg0NTQ2WhcNMTAwNjI5MTg0NTQ2WjBZMQ8wDQYDVQQEEwZGaXNoZXIxDzANBgNVBCoTBkRhbmllbDEWMBQGA1UEAxMNRGFuaWVsIEZpc2hlcjEdMBsGCSqGSIb3DQEJARYOZGZpc2hlckB2dC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbsC0qvAkU3oWwz39oq/ur/mhHZ+KNODug+pAsg0ZZbSnxWD9EVBiMerevD5bJ0we [...]
+postalAddress: SETI-Middleware
+ 1700 Pratt Drive
+ Blacksburg, VA 24060
+displayName: Daniel W Fisher
+suppressDisplay: false
+jpegPhoto:< file:///${basedir}/src/test/resources/edu/vt/middleware/ldap/image.jpg
+uupid: dfisher
+
+# random comment
+
+dn:uid=1152120,ou=People,dc=vt,dc=edu
+middleName:H
+uid:1152120
+mail:dhawes at vt.edu
+sn:Hawes
+instantMessagingID:Google:dhawes at gmail.com
+instantMessagingID:Virginia Tech:dhawes at im.iad.vt.edu
+department:MiddleWare
+objectClass:virginiaTechPerson
+suppressedAttribute:localPostalAddress
+# random comment
+givenName:David
+labeledURI:Homepage:http://filebox.vt.edu/users/dhawes
+title:Middleware Application Developer
+mailStop:0479
+localPhone:5405529055
+departmentNumber:066103
+cn:David H Hawes
+authId:dhawes
+telephoneNumber:5402316978
+# random comment
+postalAddress:SETI-Middleware$1700 Pratt Dr.$Blacksburg, VA 24061
+displayName:David H Hawes
+suppressDisplay:false
+uupid:dhawes
+
+dn: uid=1145718,ou=People,dc=vt,dc=edu
+middleName: S
+uid: 1145718
+mail: serac at vt.edu
+sn: Addison
+instantMessagingID: Virginia Tech:serac at im.vt.edu
+department: MiddleWare
+objectClass: virginiaTechPerson
+suppressedAttribute: localPostalAddress
+givenName: Marvin
+labeledURI: Homepage:http://www.middleware.vt.edu/doku.php?id=middleware:serac
+title: IT Specialist
+mailStop: 0479
+departmentNumber: 066103
+cn: Marvin S Addison
+authId: serac
+telephoneNumber: 5402317050
+# random comment
+userCertificate;binary:: MIIC/TCCAmagAwIBAgIQKQxJ2CQ0bujrfVdU9505nzANB
+ gkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHR
+ pbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc
+ 3VpbmcgQ0EwHhcNMDkwODI3MjAwNzExWhcNMTAwODI3MjAwNzExWjBZMRAwDgYDVQQEEwd
+ BZGRpc29uMQ8wDQYDVQQqEwZNYXJ2aW4xFzAVBgNVBAMTDk1hcnZpbiBBZGRpc29uMRswG
+ QYJKoZIhvcNAQkBFgxzZXJhY0B2dC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggE
+ KAoIBAQCYU7i0HPyrjhLdm09uprGeIJQnkdxU+HCu1oT0SPVg6I0/KYw64bbKSc36cHE5d
+ 03RpqkU5HCSJ5m4OaluDYezaOz6TcSUhAAmYnkF8lCXI7zBS1VGlPe2w3ronZoBb6bZ0Po
+ bBpD+1AdRRfV+6FNqhnqG+ZAOlEsAwyUkEd/lTPjYsS7Te0GXOyhPTDUiKixBdPSuMSuAJ
+ JZKrAIBTRe62dpDSOvBYJSVVpuGdiCSaG1YQWI3DkvjzLT4+0zvDPjb7dvePnbkbpwyFfK
+ +e1wYpK2yShHiJeOPAdVUZ+PjeBFnD2TkDgk/Tf8j6QJLMw0Apt7rATxW9NAUEPLoPeCFA
+ gMBAAGjOTA3MA4GA1UdDwEB/wQEAwID+DAXBgNVHREEEDAOgQxzZXJhY0B2dC5lZHUwDAY
+ DVR0TAQH/BAIwADANBgkqhkiG9w0BAQUFAAOBgQC5V1Kb0q7ajm7OZjbjv8xosxblvt9Qh
+ l5yBhnX4xL9QomntH60LZc2pf20hnJ6Pl6Onxk4LejTtemyL0Bm0i3DJbqCCBFaOsEdX2G
+ 3D3HPNhdphYckn7fjzuvUudMEkfvrPDj0evdJIgwOXDEvsh8y8LggO1Bp6xj63H0oPePHB
+ w==
+postalAddress: SETI-Middleware 1700 Pratt Drive
+ Room 111A Blacksburg, VA 24060
+displayName: Marvin S Addison
+suppressDisplay: false
+uupid: serac
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/multipleEntriesOut.ldif b/src/test/resources/edu/vt/middleware/ldap/multipleEntriesOut.ldif
new file mode 100644
index 0000000..da35895
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/multipleEntriesOut.ldif
@@ -0,0 +1,76 @@
+dn: uid=818037,ou=People,dc=vt,dc=edu
+middleName: W
+uid: 818037
+mail: dfisher at vt.edu
+sn: Fisher
+instantMessagingID: Yahoo!:dfish3r
+instantMessagingID: Google:dfisher at gmail.com
+instantMessagingID: Virginia Tech:dfisher at im.vt.edu
+instantMessagingID: ICQ:8168282
+department: MiddleWare
+objectClass: virginiaTechPerson
+suppressedAttribute: localPostalAddress
+suppressedAttribute: homePostalAddress
+suppressedAttribute: homePhone
+suppressedAttribute: localPhone
+givenName: Daniel
+title: Project Manager, Middleware
+mailStop: 0479
+departmentNumber: 066103
+cn: Daniel W Fisher
+authId: dfisher
+telephoneNumber: 5402312096
+userCertificate;binary:: MIIC7zCCAligAwIBAgIQCBdZgzdx+yX5+jSRD50SJzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc3VpbmcgQ0EwHhcNMDkwNjI5MTg0NTQ2WhcNMTAwNjI5MTg0NTQ2WjBZMQ8wDQYDVQQEEwZGaXNoZXIxDzANBgNVBCoTBkRhbmllbDEWMBQGA1UEAxMNRGFuaWVsIEZpc2hlcjEdMBsGCSqGSIb3DQEJARYOZGZpc2hlckB2dC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbsC0qvAkU3oWwz39oq/ur/mhHZ+KNODug+pAsg0ZZbSnxWD9EVBiMerevD5bJ0we [...]
+postalAddress: SETI-Middleware 1700 Pratt Drive Blacksburg, VA 24060
+displayName: Daniel W Fisher
+suppressDisplay: false
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+uupid: dfisher
+
+dn: uid=1152120,ou=People,dc=vt,dc=edu
+middleName: H
+uid: 1152120
+mail: dhawes at vt.edu
+sn: Hawes
+instantMessagingID: Google:dhawes at gmail.com
+instantMessagingID: Virginia Tech:dhawes at im.iad.vt.edu
+department: MiddleWare
+objectClass: virginiaTechPerson
+suppressedAttribute: localPostalAddress
+givenName: David
+labeledURI: Homepage:http://filebox.vt.edu/users/dhawes
+title: Middleware Application Developer
+mailStop: 0479
+localPhone: 5405529055
+departmentNumber: 066103
+cn: David H Hawes
+authId: dhawes
+telephoneNumber: 5402316978
+postalAddress: SETI-Middleware$1700 Pratt Dr.$Blacksburg, VA 24061
+displayName: David H Hawes
+suppressDisplay: false
+uupid: dhawes
+
+dn: uid=1145718,ou=People,dc=vt,dc=edu
+middleName: S
+uid: 1145718
+mail: serac at vt.edu
+sn: Addison
+instantMessagingID: Virginia Tech:serac at im.vt.edu
+department: MiddleWare
+objectClass: virginiaTechPerson
+suppressedAttribute: localPostalAddress
+givenName: Marvin
+labeledURI: Homepage:http://www.middleware.vt.edu/doku.php?id=middleware:serac
+title: IT Specialist
+mailStop: 0479
+departmentNumber: 066103
+cn: Marvin S Addison
+authId: serac
+telephoneNumber: 5402317050
+userCertificate;binary:: MIIC/TCCAmagAwIBAgIQKQxJ2CQ0bujrfVdU9505nzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc3VpbmcgQ0EwHhcNMDkwODI3MjAwNzExWhcNMTAwODI3MjAwNzExWjBZMRAwDgYDVQQEEwdBZGRpc29uMQ8wDQYDVQQqEwZNYXJ2aW4xFzAVBgNVBAMTDk1hcnZpbiBBZGRpc29uMRswGQYJKoZIhvcNAQkBFgxzZXJhY0B2dC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYU7i0HPyrjhLdm09uprGeIJQnkdxU+HCu1oT0SPVg6I0/KYw64bbKSc36cHE5d03 [...]
+postalAddress: SETI-Middleware 1700 Pratt Drive Room 111A Blacksburg, VA 24060
+displayName: Marvin S Addison
+suppressDisplay: false
+uupid: serac
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/pagedResults.ldif b/src/test/resources/edu/vt/middleware/ldap/pagedResults.ldif
new file mode 100644
index 0000000..011fb01
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/pagedResults.ldif
@@ -0,0 +1,32 @@
+dn: uugid=group2,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+member: uugid=group3,ou=test,dc=vt,dc=edu
+uid: 2002
+uugid: group2
+
+dn: uugid=group3,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+member: uugid=group4,ou=test,dc=vt,dc=edu
+member: uugid=group5,ou=test,dc=vt,dc=edu
+uid: 2003
+uugid: group3
+
+dn: uugid=group4,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+member: uugid=group3,ou=test,dc=vt,dc=edu
+uid: 2004
+uugid: group4
+
+dn: uugid=group5,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+objectClass: virginiaTechGroup
+uid: 2005
+uugid: group5
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/recursiveAttributeHandlerResults.ldif b/src/test/resources/edu/vt/middleware/ldap/recursiveAttributeHandlerResults.ldif
new file mode 100644
index 0000000..c4bb5a2
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/recursiveAttributeHandlerResults.ldif
@@ -0,0 +1,9 @@
+dn: uugid=group2,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+member: uugid=group3,ou=test,dc=vt,dc=edu
+member: uugid=group4,ou=test,dc=vt,dc=edu
+member: uugid=group5,ou=test,dc=vt,dc=edu
+objectClass: virginiaTechGroup
+uid: 2002
+uugid: group2
diff --git a/src/test/resources/edu/vt/middleware/ldap/recursiveSearchResultHandlerResults.ldif b/src/test/resources/edu/vt/middleware/ldap/recursiveSearchResultHandlerResults.ldif
new file mode 100644
index 0000000..d387d7b
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/recursiveSearchResultHandlerResults.ldif
@@ -0,0 +1,13 @@
+dn: uugid=group2,ou=test,dc=vt,dc=edu
+contactPerson: uid=2,ou=test,dc=vt,dc=edu
+creationDate: 2010-06-06T12:00:00
+member: uugid=group3,ou=test,dc=vt,dc=edu
+objectClass: virginiaTechGroup
+uid: 2002
+uid: 2003
+uid: 2004
+uid: 2005
+uugid: group2
+uugid: group3
+uugid: group4
+uugid: group5
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchAttributesResults-2.ldif b/src/test/resources/edu/vt/middleware/ldap/searchAttributesResults-2.ldif
new file mode 100644
index 0000000..739cba5
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchAttributesResults-2.ldif
@@ -0,0 +1,5 @@
+dn: uid=2,ou=test,dc=vt,dc=edu
+departmentNumber: 0822
+givenName: John
+sn: Doe
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchResults-10.ldif b/src/test/resources/edu/vt/middleware/ldap/searchResults-10.ldif
new file mode 100644
index 0000000..7f400ea
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchResults-10.ldif
@@ -0,0 +1,5 @@
+dn: uid=10,ou=test,dc=vt,dc=edu
+departmentNumber: 0830
+givenName: Johv
+sn: Dom
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchResults-12.ldif b/src/test/resources/edu/vt/middleware/ldap/searchResults-12.ldif
new file mode 100644
index 0000000..e137f35
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchResults-12.ldif
@@ -0,0 +1,15 @@
+dn: uid=12,ou=test,dc=vt,dc=edu
+cn: Johx H Doo
+departmentNumber: 0830
+displayName: Johx H Doo
+givenName: Johx
+initials: JHD
+mail: jdoe12 at vt.edu
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: Doo
+uid: 12
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchResults-2.ldif b/src/test/resources/edu/vt/middleware/ldap/searchResults-2.ldif
new file mode 100644
index 0000000..739cba5
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchResults-2.ldif
@@ -0,0 +1,5 @@
+dn: uid=2,ou=test,dc=vt,dc=edu
+departmentNumber: 0822
+givenName: John
+sn: Doe
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchResults-3.ldif b/src/test/resources/edu/vt/middleware/ldap/searchResults-3.ldif
new file mode 100644
index 0000000..d4d594c
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchResults-3.ldif
@@ -0,0 +1,5 @@
+dn: uid=3,ou=test,dc=vt,dc=edu
+departmentNumber: 0823
+givenName: Joho
+sn: Dof
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchResults-4.ldif b/src/test/resources/edu/vt/middleware/ldap/searchResults-4.ldif
new file mode 100644
index 0000000..118d97f
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchResults-4.ldif
@@ -0,0 +1,5 @@
+dn: uid=4,ou=test,dc=vt,dc=edu
+departmentNumber: 0824
+givenName: Johp
+sn: Dog
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchResults-5.ldif b/src/test/resources/edu/vt/middleware/ldap/searchResults-5.ldif
new file mode 100644
index 0000000..d69d450
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchResults-5.ldif
@@ -0,0 +1,5 @@
+dn: uid=5,ou=test,dc=vt,dc=edu
+departmentNumber: 0825
+givenName: Johq
+sn: Doh
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchResults-6.ldif b/src/test/resources/edu/vt/middleware/ldap/searchResults-6.ldif
new file mode 100644
index 0000000..ee4c127
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchResults-6.ldif
@@ -0,0 +1,5 @@
+dn: uid=6,ou=test,dc=vt,dc=edu
+departmentNumber: 0826
+givenName: Johr
+sn: Doi
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchResults-7.ldif b/src/test/resources/edu/vt/middleware/ldap/searchResults-7.ldif
new file mode 100644
index 0000000..46a3321
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchResults-7.ldif
@@ -0,0 +1,5 @@
+dn: uid=7,ou=test,dc=vt,dc=edu
+departmentNumber: 0827
+givenName: Johs
+sn: Doj
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchResults-8.ldif b/src/test/resources/edu/vt/middleware/ldap/searchResults-8.ldif
new file mode 100644
index 0000000..d737f47
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchResults-8.ldif
@@ -0,0 +1,6 @@
+dn: uid=8,ou=test,dc=vt,dc=edu
+departmentNumber: 0828
+givenName: Joht
+sn: Dok
+jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPRTXkZ7wHW [...]
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/searchResults-9.ldif b/src/test/resources/edu/vt/middleware/ldap/searchResults-9.ldif
new file mode 100644
index 0000000..739240f
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/searchResults-9.ldif
@@ -0,0 +1,5 @@
+dn: uid=9,ou=test,dc=vt,dc=edu
+departmentNumber: 0829
+givenName: Johu
+sn: Dol
+
diff --git a/src/test/resources/edu/vt/middleware/ldap/specialChars-2.ldif b/src/test/resources/edu/vt/middleware/ldap/specialChars-2.ldif
new file mode 100644
index 0000000..8ccfaa3
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/specialChars-2.ldif
@@ -0,0 +1,10 @@
+dn: cn=Test\/User\/ \#2\,3\=4\/\+5\<6\>7\;8,ou=test,dc=vt,dc=edu
+cn: Test/User/ #2,3=4/+5<6>7;8
+givenName: Test
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: User
+uid: 17894
+userPassword: {SHA}KqYKj/f81HPTIeAUav2eJt85UUc=
diff --git a/src/test/resources/edu/vt/middleware/ldap/specialChars.ldif b/src/test/resources/edu/vt/middleware/ldap/specialChars.ldif
new file mode 100644
index 0000000..7ee2123
--- /dev/null
+++ b/src/test/resources/edu/vt/middleware/ldap/specialChars.ldif
@@ -0,0 +1,9 @@
+dn: cn=Test\/User\/ \#1,ou=test,dc=vt,dc=edu
+cn: Test/User/ #1
+givenName: Test
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+sn: User
+uid: 17893
diff --git a/src/test/resources/krb5.keytab b/src/test/resources/krb5.keytab
new file mode 100644
index 0000000..c800daa
Binary files /dev/null and b/src/test/resources/krb5.keytab differ
diff --git a/src/test/resources/ldap.conn.properties b/src/test/resources/ldap.conn.properties
new file mode 100644
index 0000000..1e52a6a
--- /dev/null
+++ b/src/test/resources/ldap.conn.properties
@@ -0,0 +1,30 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://dne.middleware.vt.edu ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# bind DN if one is required to bind before searching
+edu.vt.middleware.ldap.bindDn=uid=1,ou=test,dc=vt,dc=edu
+
+# credential for the bind DN
+edu.vt.middleware.ldap.bindCredential=VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2
+
+# LDAP authentication mechanism
+# default value is 'simple'
+edu.vt.middleware.ldap.authtype=simple
+
+# sets the search scope
+# default value is 'SUBTREE'
+edu.vt.middleware.ldap.searchScope=SUBTREE
+
+# set socket timeout low for testing
+edu.vt.middleware.ldap.timeout=2000
+
+edu.vt.middleware.ldap.connectionHandler=edu.vt.middleware.ldap.handler.DefaultConnectionHandler{{connectionStrategy=ROUND_ROBIN}}
diff --git a/src/test/resources/ldap.cram-md5.properties b/src/test/resources/ldap.cram-md5.properties
new file mode 100644
index 0000000..214ec00
--- /dev/null
+++ b/src/test/resources/ldap.cram-md5.properties
@@ -0,0 +1,18 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:389 ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# set socket timeout low for testing
+edu.vt.middleware.ldap.timeout=2000
+
+# LDAP authentication mechanism
+# default value is 'simple'
+edu.vt.middleware.ldap.authtype=CRAM-MD5
diff --git a/src/test/resources/ldap.digest-md5.properties b/src/test/resources/ldap.digest-md5.properties
new file mode 100644
index 0000000..34a2898
--- /dev/null
+++ b/src/test/resources/ldap.digest-md5.properties
@@ -0,0 +1,18 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:389 ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# set socket timeout low for testing
+edu.vt.middleware.ldap.timeout=2000
+
+# LDAP authentication mechanism
+# default value is 'simple'
+edu.vt.middleware.ldap.authtype=DIGEST-MD5
diff --git a/src/test/resources/ldap.gssapi.properties b/src/test/resources/ldap.gssapi.properties
new file mode 100644
index 0000000..bd56713
--- /dev/null
+++ b/src/test/resources/ldap.gssapi.properties
@@ -0,0 +1,19 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# LDAP authentication mechanism
+# default value is 'simple'
+edu.vt.middleware.ldap.authtype=GSSAPI
+
+# whether TLS should be used for LDAP connections
+# default value is 'false'
+edu.vt.middleware.ldap.tls=false
diff --git a/src/test/resources/ldap.null.properties b/src/test/resources/ldap.null.properties
new file mode 100644
index 0000000..2fb90e1
--- /dev/null
+++ b/src/test/resources/ldap.null.properties
@@ -0,0 +1,52 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# bind DN if one is required to bind before searching
+edu.vt.middleware.ldap.bindDn=uid=1,ou=test,dc=vt,dc=edu
+
+# credential for the bind DN
+edu.vt.middleware.ldap.bindCredential=VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2
+
+# LDAP authentication mechanism
+# default value is 'simple'
+edu.vt.middleware.ldap.authtype=simple
+
+# sets the batch size to use when returning results
+# default value is '-1'
+edu.vt.middleware.ldap.batchSize=-1
+
+# sets the search scope
+# default value is 'SUBTREE'
+edu.vt.middleware.ldap.searchScope=SUBTREE
+
+# sets the length of time that search operations will block
+# default value is 0, block forever
+edu.vt.middleware.ldap.timeLimit=5000
+
+# set socket timeout low for testing
+edu.vt.middleware.ldap.timeout=8000
+
+# specifies additional attributes which should be treated as binary
+# attribute names should be space delimited
+edu.vt.middleware.ldap.binaryAttributes=jpegPhoto
+
+# whether TLS should be used for LDAP connections
+# default value is 'false'
+edu.vt.middleware.ldap.tls=false
+
+# nullable properties
+edu.vt.middleware.ldap.sslSocketFactory=null
+edu.vt.middleware.ldap.hostnameVerifier=null
+edu.vt.middleware.ldap.operationRetryExceptions=null
+edu.vt.middleware.ldap.searchResultHandlers=null
+edu.vt.middleware.ldap.handlerIgnoreExceptions=null
+
diff --git a/src/test/resources/ldap.parser.properties b/src/test/resources/ldap.parser.properties
new file mode 100644
index 0000000..290af0d
--- /dev/null
+++ b/src/test/resources/ldap.parser.properties
@@ -0,0 +1,50 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# bind DN if one is required to bind before searching
+edu.vt.middleware.ldap.bindDn=uid=1,ou=test,dc=vt,dc=edu
+
+# credential for the bind DN
+edu.vt.middleware.ldap.bindCredential=VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2
+
+# LDAP authentication mechanism
+# default value is 'simple'
+edu.vt.middleware.ldap.authtype=simple
+
+# sets the batch size to use when returning results
+# default value is '-1'
+edu.vt.middleware.ldap.batchSize=10
+
+# sets the search scope
+# default value is 'SUBTREE'
+edu.vt.middleware.ldap.searchScope=OBJECT
+
+# sets the length of time that search operations will block
+# default value is 0, block forever
+edu.vt.middleware.ldap.timeLimit=5000
+
+# set socket timeout low for testing
+edu.vt.middleware.ldap.timeout=8000
+
+# specifies additional attributes which should be treated as binary
+# attribute names should be space delimited
+edu.vt.middleware.ldap.binaryAttributes=jpegPhoto
+
+# whether TLS should be used for LDAP connections
+# default value is 'false'
+edu.vt.middleware.ldap.tls=false
+
+# search result handlers
+edu.vt.middleware.ldap.searchResultHandlers=edu.vt.middleware.ldap.handler.RecursiveSearchResultHandler{{searchAttribute=member}{mergeAttributes=mail,department}},edu.vt.middleware.ldap.handler.MergeSearchResultHandler{{allowDuplicates=true}},edu.vt.middleware.ldap.handler.BinarySearchResultHandler{ },edu.vt.middleware.ldap.handler.EntryDnSearchResultHandler{{dnAttributeName=myDN}}
+
+# exceptions to ignore when searching
+edu.vt.middleware.ldap.handlerIgnoreExceptions=javax.naming.SizeLimitExceededException,javax.naming.PartialResultException
diff --git a/src/test/resources/ldap.pool.properties b/src/test/resources/ldap.pool.properties
new file mode 100644
index 0000000..6ad8178
--- /dev/null
+++ b/src/test/resources/ldap.pool.properties
@@ -0,0 +1,10 @@
+# Configuration variables for ldap pool operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+# period that the prune timer task runs
+edu.vt.middleware.ldap.pool.pruneTimerPeriod=5000
+
+# expiration time for objects in the ldap
+edu.vt.middleware.ldap.pool.expirationTime=1000
+
diff --git a/src/test/resources/ldap.properties b/src/test/resources/ldap.properties
new file mode 100644
index 0000000..eadea32
--- /dev/null
+++ b/src/test/resources/ldap.properties
@@ -0,0 +1,47 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# bind DN if one is required to bind before searching
+edu.vt.middleware.ldap.bindDn=uid=1,ou=test,dc=vt,dc=edu
+
+# credential for the bind DN
+edu.vt.middleware.ldap.bindCredential=VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2
+
+# LDAP authentication mechanism
+# default value is 'simple'
+edu.vt.middleware.ldap.authtype=simple
+
+# sets the batch size to use when returning results
+# default value is '-1'
+edu.vt.middleware.ldap.batchSize=-1
+
+# sets the search scope
+# default value is 'SUBTREE'
+edu.vt.middleware.ldap.searchScope=SUBTREE
+
+# sets the length of time that search operations will block
+# default value is 0, block forever
+edu.vt.middleware.ldap.timeLimit=5000
+
+# set socket timeout low for testing
+edu.vt.middleware.ldap.timeout=8000
+
+# specifies additional attributes which should be treated as binary
+# attribute names should be space delimited
+edu.vt.middleware.ldap.binaryAttributes=jpegPhoto
+
+# whether TLS should be used for LDAP connections
+# default value is 'false'
+edu.vt.middleware.ldap.tls=false
+
+# exceptions to ignore when searching
+edu.vt.middleware.ldap.handlerIgnoreExceptions=javax.naming.SizeLimitExceededException,javax.naming.PartialResultException
diff --git a/src/test/resources/ldap.sasl.properties b/src/test/resources/ldap.sasl.properties
new file mode 100644
index 0000000..fa78a03
--- /dev/null
+++ b/src/test/resources/ldap.sasl.properties
@@ -0,0 +1,31 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# fully qualified class name which implements javax.net.ssl.SSLSocketFactory
+edu.vt.middleware.ldap.sslSocketFactory=edu.vt.middleware.ldap.ssl.TLSSocketFactory{ edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{ {trustStore=classpath:/ed.truststore} {trustStoreType=BKS} {keyStore=classpath:/ed.keystore} {keyStoreType=BKS} {keyStorePassword=changeit} }}
+
+# fully qualified class name which implements javax.net.ssl.HostnameVerifier
+edu.vt.middleware.ldap.hostnameVerifier=edu.vt.middleware.ldap.AnyHostnameVerifier
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:389 ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# LDAP authentication mechanism
+# default value is 'simple'
+edu.vt.middleware.ldap.authtype=EXTERNAL
+
+# whether TLS should be used for LDAP connections
+# default value is 'false'
+edu.vt.middleware.ldap.tls=true
+
+# set socket timeout low for testing
+edu.vt.middleware.ldap.timeout=2000
+
+# LDAP field which contains user identifier
+edu.vt.middleware.ldap.auth.userField=uid,mail
diff --git a/src/test/resources/ldap.setup.properties b/src/test/resources/ldap.setup.properties
new file mode 100644
index 0000000..55b8b27
--- /dev/null
+++ b/src/test/resources/ldap.setup.properties
@@ -0,0 +1,17 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# bind DN if one is required to bind before searching
+edu.vt.middleware.ldap.bindDn=uid=1,ou=test,dc=vt,dc=edu
+
+# credential for the bind DN
+edu.vt.middleware.ldap.bindCredential=VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2
diff --git a/src/test/resources/ldap.ssl.properties b/src/test/resources/ldap.ssl.properties
new file mode 100644
index 0000000..b7da9b6
--- /dev/null
+++ b/src/test/resources/ldap.ssl.properties
@@ -0,0 +1,27 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# fully qualified class name which implements javax.net.ssl.SSLSocketFactory
+edu.vt.middleware.ldap.sslSocketFactory=edu.vt.middleware.ldap.ssl.SingletonTLSSocketFactory{edu.vt.middleware.ldap.ssl.X509CredentialConfig{{trustCertificates=file:src/test/resources/ed.trust.crt}}}
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:10636
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# bind DN if one is required to bind before searching
+edu.vt.middleware.ldap.bindDn=uid=1,ou=test,dc=vt,dc=edu
+
+# credential for the bind DN
+edu.vt.middleware.ldap.bindCredential=VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2
+
+# whether SSL should be used for LDAP connections
+# default value is 'false'
+edu.vt.middleware.ldap.ssl=true
+
+# LDAP field which contains user identifier
+edu.vt.middleware.ldap.auth.userField=uid,mail
diff --git a/src/test/resources/ldap.tls.load.properties b/src/test/resources/ldap.tls.load.properties
new file mode 100644
index 0000000..75a0455
--- /dev/null
+++ b/src/test/resources/ldap.tls.load.properties
@@ -0,0 +1,30 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# fully qualified class name which implements javax.net.ssl.SSLSocketFactory
+edu.vt.middleware.ldap.sslSocketFactory={ edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{ {trustStore=classpath:/ed.truststore} {trustStoreType=BKS} }}
+
+# fully qualified class name which implements javax.net.ssl.HostnameVerifier
+edu.vt.middleware.ldap.hostnameVerifier=edu.vt.middleware.ldap.AnyHostnameVerifier
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=ou=test,dc=vt,dc=edu
+
+# bind DN if one is required to bind before searching
+edu.vt.middleware.ldap.bindDn=uid=1,ou=test,dc=vt,dc=edu
+
+# credential for the bind DN
+edu.vt.middleware.ldap.bindCredential=VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2
+
+# whether TLS should be used for LDAP connections
+# default value is 'false'
+edu.vt.middleware.ldap.tls=true
+
+# LDAP field which contains user identifier
+edu.vt.middleware.ldap.auth.userField=uid,mail
diff --git a/src/test/resources/ldap.tls.properties b/src/test/resources/ldap.tls.properties
new file mode 100644
index 0000000..6189b2e
--- /dev/null
+++ b/src/test/resources/ldap.tls.properties
@@ -0,0 +1,43 @@
+# Configuration variables for ldap operation
+# Comments must be on separate lines
+# Format is 'name=value'
+
+## LDAP CONFIG ##
+
+# fully qualified class name which implements javax.net.ssl.SSLSocketFactory
+edu.vt.middleware.ldap.sslSocketFactory={ trustCertificates=file:src/test/resources/ed.trust.crt }
+
+# fully qualified class name which implements javax.net.ssl.SSLSocketFactory
+#edu.vt.middleware.ldap.auth.sslSocketFactory=edu.vt.middleware.ldap.ssl.TLSSocketFactory{enabledCipherSuites=TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA}
+
+# fully qualified class name which implements javax.net.ssl.HostnameVerifier
+edu.vt.middleware.ldap.hostnameVerifier=edu.vt.middleware.ldap.AnyHostnameVerifier
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:389
+
+# hostname of the LDAP
+edu.vt.middleware.ldap.auth.ldapUrl=ldap://ldap-test-1.middleware.vt.edu:389 ldap://ldap-test-1.middleware.vt.edu:10389
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.auth.baseDn=ou=test,dc=vt,dc=edu
+
+# base dn for performing user lookups
+edu.vt.middleware.ldap.baseDn=dc=vt,dc=edu
+
+# bind DN if one is required to bind before searching
+edu.vt.middleware.ldap.bindDn=uid=1,ou=test,dc=vt,dc=edu
+
+# credential for the bind DN
+edu.vt.middleware.ldap.bindCredential=VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2
+
+# whether TLS should be used for LDAP connections
+# default value is 'false'
+edu.vt.middleware.ldap.tls=true
+
+# set socket timeout low for testing
+edu.vt.middleware.ldap.timeout=2000
+
+# LDAP field which contains user identifier
+edu.vt.middleware.ldap.auth.userField=uid,mail
+
diff --git a/src/test/resources/ldap_jaas.config b/src/test/resources/ldap_jaas.config
new file mode 100644
index 0000000..f1b764e
--- /dev/null
+++ b/src/test/resources/ldap_jaas.config
@@ -0,0 +1,279 @@
+vt-ldap {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ baseDn="ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier{foo=test}"
+ operationRetryException="javax.naming.CommunicationException,javax.naming.ServiceUnavailableException"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ sslSocketFactory="{trustCertificates=classpath:/ed.trust.crt}";
+};
+
+vt-ldap-ssl {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ ldapUrl="ldaps://ldap-test-1.middleware.vt.edu:10636"
+ baseDn="ou=test,dc=vt,dc=edu"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber";
+};
+
+vt-ldap-ssl-2 {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10636"
+ ssl="true"
+ baseDn="ou=test,dc=vt,dc=edu"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ sslSocketFactory="edu.vt.middleware.ldap.ssl.SingletonTLSSocketFactory{edu.vt.middleware.ldap.ssl.X509CredentialConfig{{trustCertificates=classpath:/ed.trust.crt}}}";
+};
+
+vt-ldap-authz {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ baseDn="ou=test,dc=vt,dc=edu"
+ connectionHandler="edu.vt.middleware.ldap.handler.TlsConnectionHandler"
+ authenticationHandler="edu.vt.middleware.ldap.auth.handler.BindAuthenticationHandler"
+ dnResolver="edu.vt.middleware.ldap.auth.SearchDnResolver"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ setLdapDnPrincipal="true"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ authorizationFilter="uid={1}"
+ authorizationFilterArgs="7"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ sslSocketFactory="{trustCertificates=file:src/test/resources/ed.trust.crt}";
+};
+
+vt-ldap-random {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389 ldap://ed-dne.middleware.vt.edu"
+ baseDn="ou=test,dc=vt,dc=edu"
+ connectionHandler="edu.vt.middleware.ldap.handler.TlsConnectionHandler{{connectionStrategy=RANDOM}}"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ setLdapDnPrincipal="true"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ sslSocketFactory="{trustCertificates=file:src/test/resources/ed.trust.crt}";
+};
+
+vt-ldap-filter {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389 ldap://ed-dne.middleware.vt.edu"
+ baseDn="ou=test,dc=vt,dc=edu"
+ connectionHandler="edu.vt.middleware.ldap.handler.TlsConnectionHandler{{connectionStrategy=ACTIVE_PASSIVE}{connectionRetryExceptions=javax.naming.CommunicationException}}"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier{foo=test,bar=false}"
+ handlerIgnoreExceptions="javax.naming.NamingException"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ allowMultipleDns="true"
+ authorizationFilter="uid={1}"
+ authorizationFilterArgs="7"
+ userFilter="(&(mail={0})(objectClass={1}))"
+ userFilterArgs="person"
+ sslSocketFactory="{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+};
+
+vt-ldap-handler {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ baseDn="ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ handlerIgnoreExceptions="javax.naming.NamingException"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ authorizationHandlers="edu.vt.middleware.ldap.auth.handler.TestAuthorizationHandler"
+ userFilter="(&(mail={0})(objectClass={1}))"
+ userFilterArgs="person"
+ userRoleAttribute="departmentNumber"
+ sslSocketFactory="{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+};
+
+vt-ldap-roles {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ storePass="true"
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ baseDn="ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ sslSocketFactory="{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+ edu.vt.middleware.ldap.jaas.LdapRoleAuthorizationModule optional
+ useFirstPass="true"
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389/ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ roleFilter="(mail={1})"
+ roleAttribute="objectClass"
+ sslSocketFactory="{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+};
+
+vt-ldap-roles-recursive {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ storePass="true"
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ baseDn="ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ sslSocketFactory="{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+ edu.vt.middleware.ldap.jaas.LdapRoleAuthorizationModule required
+ useFirstPass="true"
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ baseDn="ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ roleFilter="(member={0})"
+ roleAttribute="uugid"
+ searchResultHandlers="edu.vt.middleware.ldap.handler.FqdnSearchResultHandler,edu.vt.middleware.ldap.handler.RecursiveSearchResultHandler{{searchAttribute=member}{mergeAttributes=uugid}}"
+ sslSocketFactory="{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+};
+
+vt-ldap-use-first {
+ edu.vt.middleware.ldap.jaas.TestLoginModule required;
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ useFirstPass="true"
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ baseDn="ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ defaultRole="test-role1,test-role2"
+ sslSocketFactory="edu.vt.middleware.ldap.ssl.TLSSocketFactory{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+};
+
+vt-ldap-try-first {
+ edu.vt.middleware.ldap.jaas.TestLoginModule required;
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ tryFirstPass="true"
+ storePass="true"
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ baseDn="ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ sslSocketFactory="edu.vt.middleware.ldap.ssl.TLSSocketFactory{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+ edu.vt.middleware.ldap.jaas.LdapRoleAuthorizationModule optional
+ useFirstPass="true"
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389/ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ roleFilter="(mail={1})"
+ roleAttribute="objectClass"
+ sslSocketFactory="edu.vt.middleware.ldap.ssl.TLSSocketFactory{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+};
+
+vt-ldap-sufficient {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule sufficient
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ baseDn="ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ authorizationFilter="departmentNumber=0000"
+ sslSocketFactory="edu.vt.middleware.ldap.ssl.TLSSocketFactory{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+ edu.vt.middleware.ldap.jaas.LdapLoginModule sufficient
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ baseDn="ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ authorizationFilter="departmentNumber=0827"
+ sslSocketFactory="edu.vt.middleware.ldap.ssl.TLSSocketFactory{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+};
+
+vt-ldap-roles-only {
+ edu.vt.middleware.ldap.jaas.LdapRoleAuthorizationModule required
+ useFirstPass="true"
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389/ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ roleFilter="(uid=7)"
+ roleAttribute="departmentNumber,objectClass"
+ principalGroupName="Principals"
+ roleGroupName="Roles"
+ sslSocketFactory="edu.vt.middleware.ldap.ssl.TLSSocketFactory{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+};
+
+vt-ldap-dn-roles-only {
+ edu.vt.middleware.ldap.jaas.LdapDnAuthorizationModule required
+ storePass="true"
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389/ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ sslSocketFactory="edu.vt.middleware.ldap.ssl.TLSSocketFactory{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+ edu.vt.middleware.ldap.jaas.LdapRoleAuthorizationModule required
+ useFirstPass="true"
+ ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389/ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ bindDn="uid=1,ou=test,dc=vt,dc=edu"
+ bindCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ roleFilter="(mail={1})"
+ roleAttribute="departmentNumber,objectClass"
+ principalGroupName="Principals"
+ roleGroupName="Roles"
+ sslSocketFactory="edu.vt.middleware.ldap.ssl.TLSSocketFactory{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+};
+
+vt-ldap-deprecated {
+ edu.vt.middleware.ldap.jaas.LdapLoginModule required
+ host="ldap-test-1.middleware.vt.edu"
+ port="10389"
+ base="ou=test,dc=vt,dc=edu"
+ tls="true"
+ hostnameVerifier="edu.vt.middleware.ldap.AnyHostnameVerifier"
+ serviceUser="uid=1,ou=test,dc=vt,dc=edu"
+ serviceCredential="VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2"
+ userField="mail"
+ userRoleAttribute="departmentNumber"
+ sslSocketFactory="edu.vt.middleware.ldap.ssl.TLSSocketFactory{edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig{{trustStore=classpath:/ed.truststore} {trustStoreType=BKS}}}";
+};
+
+com.sun.security.jgss.initiate {
+ com.sun.security.auth.module.Krb5LoginModule required
+ doNotPrompt="true"
+ debug="true"
+ principal="test3"
+ useKeyTab="true"
+ keyTab="src/test/resources/krb5.keytab";
+};
diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml
new file mode 100644
index 0000000..c562a12
--- /dev/null
+++ b/src/test/resources/log4j.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"
+ debug="false">
+
+ <appender name="CONSOLE"
+ class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%X{host} %d %-5p [%c] %m%n"/>
+ </layout>
+ </appender>
+
+ <category name="edu.vt.middleware.ldap"
+ additivity="false">
+ <priority value="INFO"/>
+ <appender-ref ref="CONSOLE"/>
+ </category>
+
+ <category name="org.springframework"
+ additivity="false">
+ <priority value="ERROR"/>
+ <appender-ref ref="CONSOLE"/>
+ </category>
+
+ <root>
+ <priority value="INFO"/>
+ <appender-ref ref="CONSOLE"/>
+ </root>
+
+</log4j:configuration>
diff --git a/src/test/resources/spring-context.xml b/src/test/resources/spring-context.xml
new file mode 100644
index 0000000..45a8746
--- /dev/null
+++ b/src/test/resources/spring-context.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xsi:schemaLocation="
+http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="ldap"
+ class="edu.vt.middleware.ldap.Ldap"
+ p:ldapConfig-ref="ldapConfig"
+ />
+
+ <bean id="ldapConfig"
+ class="edu.vt.middleware.ldap.LdapConfig"
+ p:ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ p:tls="true"
+ p:authtype="EXTERNAL"
+ p:searchScope="SUBTREE">
+ <property name="sslSocketFactory">
+ <bean class="edu.vt.middleware.ldap.ssl.TLSSocketFactory"
+ init-method="initialize">
+ <property name="SSLContextInitializer">
+ <bean
+ factory-bean="sslContextInitializerFactory"
+ factory-method="createSSLContextInitializer" />
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="sslContextInitializerFactory"
+ class="edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig"
+ p:keyStore="classpath:/ed.keystore"
+ p:keyStoreType="BKS"
+ p:keyStorePassword="changeit"
+ p:trustStore="classpath:/ed.truststore"
+ p:trustStoreType="BKS"
+ p:trustStorePassword="changeit"
+ />
+
+</beans>
diff --git a/src/test/resources/spring-pool-context.xml b/src/test/resources/spring-pool-context.xml
new file mode 100644
index 0000000..d3e223e
--- /dev/null
+++ b/src/test/resources/spring-pool-context.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xsi:schemaLocation="
+http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">
+
+ <bean id="ldapFactory"
+ class="edu.vt.middleware.ldap.pool.DefaultLdapFactory">
+ <constructor-arg index="0" ref="ldapConfig"/>
+ </bean>
+
+ <bean id="ldapPool"
+ class="edu.vt.middleware.ldap.pool.BlockingLdapPool"
+ init-method="initialize"
+ p:blockWaitTime="5000">
+ <constructor-arg index="0">
+ <bean class="edu.vt.middleware.ldap.pool.LdapPoolConfig"
+ p:minPoolSize="5"
+ p:maxPoolSize="20"
+ p:validatePeriodically="true"
+ p:validateTimerPeriod="30000"
+ p:expirationTime="600000"
+ p:pruneTimerPeriod="60000"
+ />
+ </constructor-arg>
+ <constructor-arg index="1" ref="ldapFactory"/>
+ </bean>
+
+ <bean id="ldapConfig"
+ class="edu.vt.middleware.ldap.LdapConfig"
+ p:ldapUrl="ldap://ldap-test-1.middleware.vt.edu:10389"
+ p:tls="true"
+ p:authtype="EXTERNAL"
+ p:searchScope="SUBTREE">
+ <property name="sslSocketFactory">
+ <bean class="edu.vt.middleware.ldap.ssl.TLSSocketFactory"
+ init-method="initialize">
+ <property name="SSLContextInitializer">
+ <bean
+ factory-bean="sslContextInitializerFactory"
+ factory-method="createSSLContextInitializer" />
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="sslContextInitializerFactory"
+ class="edu.vt.middleware.ldap.ssl.KeyStoreCredentialConfig"
+ p:keyStore="classpath:/ed.keystore"
+ p:keyStoreType="BKS"
+ p:keyStorePassword="changeit"
+ p:trustStore="classpath:/ed.truststore"
+ p:trustStoreType="BKS"
+ p:trustStorePassword="changeit"
+ />
+
+</beans>
diff --git a/src/test/resources/vt-ldap.truststore b/src/test/resources/vt-ldap.truststore
new file mode 100644
index 0000000..98ed077
Binary files /dev/null and b/src/test/resources/vt-ldap.truststore differ
diff --git a/src/test/resources/web.xml b/src/test/resources/web.xml
new file mode 100644
index 0000000..271e30f
--- /dev/null
+++ b/src/test/resources/web.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE web-app
+ PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+ "http://java.sun.com/dtd/web-app_2_3.dtd">
+
+<web-app>
+ <servlet>
+ <servlet-name>LdifSearch</servlet-name>
+ <servlet-class>edu.vt.middleware.ldap.servlets.SearchServlet</servlet-class>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.propertiesFile</param-name>
+ <param-value>/ldap.properties</param-value>
+ </init-param>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.poolPropertiesFile</param-name>
+ <param-value>/ldap.pool.properties</param-value>
+ </init-param>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.poolType</param-name>
+ <param-value>BLOCKING</param-value>
+ </init-param>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.outputFormat</param-name>
+ <param-value>LDIF</param-value>
+ </init-param>
+ </servlet>
+ <servlet>
+ <servlet-name>DsmlSearch</servlet-name>
+ <servlet-class>edu.vt.middleware.ldap.servlets.SearchServlet</servlet-class>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.propertiesFile</param-name>
+ <param-value>/ldap.properties</param-value>
+ </init-param>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.poolPropertiesFile</param-name>
+ <param-value>/ldap.pool.properties</param-value>
+ </init-param>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.poolType</param-name>
+ <param-value>SOFTLIMIT</param-value>
+ </init-param>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.outputFormat</param-name>
+ <param-value>DSML</param-value>
+ </init-param>
+ </servlet>
+ <servlet>
+ <servlet-name>AttributeSearch</servlet-name>
+ <servlet-class>edu.vt.middleware.ldap.servlets.AttributeServlet</servlet-class>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.propertiesFile</param-name>
+ <param-value>/ldap.properties</param-value>
+ </init-param>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.poolPropertiesFile</param-name>
+ <param-value>/ldap.pool.properties</param-value>
+ </init-param>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.poolType</param-name>
+ <param-value>SHARED</param-value>
+ </init-param>
+ </servlet>
+ <servlet>
+ <servlet-name>Login</servlet-name>
+ <servlet-class>edu.vt.middleware.ldap.servlets.LoginServlet</servlet-class>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.propertiesFile</param-name>
+ <param-value>/ldap.tls.properties</param-value>
+ </init-param>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.sessionId</param-name>
+ <param-value>vt-ldap.User</param-value>
+ </init-param>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.loginUrl</param-name>
+ <param-value>SessionCheck</param-value>
+ </init-param>
+ </servlet>
+ <servlet>
+ <servlet-name>Logout</servlet-name>
+ <servlet-class>edu.vt.middleware.ldap.servlets.LogoutServlet</servlet-class>
+ <init-param>
+ <param-name>edu.vt.middleware.ldap.servlets.sessionId</param-name>
+ <param-value>vt-ldap.User</param-value>
+ </init-param>
+ </servlet>
+ <servlet>
+ <servlet-name>SessionCheck</servlet-name>
+ <servlet-class>edu.vt.middleware.ldap.servlets.SessionCheck</servlet-class>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>LdifSearch</servlet-name>
+ <url-pattern>/LdifSearch</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>DsmlSearch</servlet-name>
+ <url-pattern>/DsmlSearch</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>AttributeSearch</servlet-name>
+ <url-pattern>/AttributeSearch</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>Login</servlet-name>
+ <url-pattern>/Login</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>Logout</servlet-name>
+ <url-pattern>/Logout</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>SessionCheck</servlet-name>
+ <url-pattern>/SessionCheck</url-pattern>
+ </servlet-mapping>
+</web-app>
diff --git a/src/test/testng/testng.xml b/src/test/testng/testng.xml
new file mode 100644
index 0000000..48107d4
--- /dev/null
+++ b/src/test/testng/testng.xml
@@ -0,0 +1,355 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="vt-ldap" verbose="1" parallel="tests" thread-count="1">
+
+ <!-- ldap test parameters -->
+ <parameter name="createEntry2"
+ value="/edu/vt/middleware/ldap/createLdapEntry-2.ldif"/>
+ <parameter name="createEntry3"
+ value="/edu/vt/middleware/ldap/createLdapEntry-3.ldif"/>
+ <parameter name="createEntry4"
+ value="/edu/vt/middleware/ldap/createLdapEntry-4.ldif"/>
+ <parameter name="createEntry5"
+ value="/edu/vt/middleware/ldap/createLdapEntry-5.ldif"/>
+ <parameter name="createEntry6"
+ value="/edu/vt/middleware/ldap/createLdapEntry-6.ldif"/>
+ <parameter name="createEntry7"
+ value="/edu/vt/middleware/ldap/createLdapEntry-7.ldif"/>
+ <parameter name="createEntry8"
+ value="/edu/vt/middleware/ldap/createLdapEntry-8.ldif"/>
+ <parameter name="createEntry9"
+ value="/edu/vt/middleware/ldap/createLdapEntry-9.ldif"/>
+ <parameter name="createEntry10"
+ value="/edu/vt/middleware/ldap/createLdapEntry-10.ldif"/>
+ <parameter name="createEntry11"
+ value="/edu/vt/middleware/ldap/createLdapEntry-11.ldif"/>
+ <parameter name="createEntry12"
+ value="/edu/vt/middleware/ldap/createLdapEntry-12.ldif"/>
+
+ <parameter name="createGroup2"
+ value="/edu/vt/middleware/ldap/createGroupEntry-2.ldif"/>
+ <parameter name="createGroup3"
+ value="/edu/vt/middleware/ldap/createGroupEntry-3.ldif"/>
+ <parameter name="createGroup4"
+ value="/edu/vt/middleware/ldap/createGroupEntry-4.ldif"/>
+ <parameter name="createGroup5"
+ value="/edu/vt/middleware/ldap/createGroupEntry-5.ldif"/>
+ <parameter name="createGroup6"
+ value="/edu/vt/middleware/ldap/createGroupEntry-6.ldif"/>
+ <parameter name="createGroup7"
+ value="/edu/vt/middleware/ldap/createGroupEntry-7.ldif"/>
+ <parameter name="createGroup8"
+ value="/edu/vt/middleware/ldap/createGroupEntry-8.ldif"/>
+ <parameter name="createGroup9"
+ value="/edu/vt/middleware/ldap/createGroupEntry-9.ldif"/>
+
+ <parameter name="createSpecialCharsEntry"
+ value="/edu/vt/middleware/ldap/specialChars.ldif"/>
+ <parameter name="createSpecialCharsEntry2"
+ value="/edu/vt/middleware/ldap/specialChars-2.ldif"/>
+
+ <parameter name="multipleLdifResultsIn"
+ value="/edu/vt/middleware/ldap/multipleEntriesIn.ldif"/>
+ <parameter name="multipleLdifResultsOut"
+ value="/edu/vt/middleware/ldap/multipleEntriesOut.ldif"/>
+ <parameter name="ldifEntry"
+ value="/edu/vt/middleware/ldap/dfisher.ldif"/>
+ <parameter name="ldifSortedEntry"
+ value="/edu/vt/middleware/ldap/dfisher.sorted.ldif"/>
+
+ <parameter name="dsmlv1Entry"
+ value="/edu/vt/middleware/ldap/dfisher.dsmlv1"/>
+ <parameter name="dsmlv1SortedEntry"
+ value="/edu/vt/middleware/ldap/dfisher.sorted.dsmlv1"/>
+ <parameter name="dsmlv2Entry"
+ value="/edu/vt/middleware/ldap/dfisher.dsmlv2"/>
+ <parameter name="dsmlv2SortedEntry"
+ value="/edu/vt/middleware/ldap/dfisher.sorted.dsmlv2"/>
+
+ <parameter name="searchResults2"
+ value="/edu/vt/middleware/ldap/searchResults-2.ldif"/>
+ <parameter name="searchResults3"
+ value="/edu/vt/middleware/ldap/searchResults-3.ldif"/>
+ <parameter name="searchResults4"
+ value="/edu/vt/middleware/ldap/searchResults-4.ldif"/>
+ <parameter name="searchResults5"
+ value="/edu/vt/middleware/ldap/searchResults-5.ldif"/>
+ <parameter name="searchResults6"
+ value="/edu/vt/middleware/ldap/searchResults-6.ldif"/>
+ <parameter name="searchResults7"
+ value="/edu/vt/middleware/ldap/searchResults-7.ldif"/>
+ <parameter name="searchResults8"
+ value="/edu/vt/middleware/ldap/searchResults-8.ldif"/>
+ <parameter name="searchResults9"
+ value="/edu/vt/middleware/ldap/searchResults-9.ldif"/>
+ <parameter name="searchResults10"
+ value="/edu/vt/middleware/ldap/searchResults-10.ldif"/>
+ <parameter name="searchResults11"
+ value="/edu/vt/middleware/ldap/searchResults-11.ldif"/>
+ <parameter name="searchResults12"
+ value="/edu/vt/middleware/ldap/searchResults-12.ldif"/>
+
+ <parameter name="renameOldDn" value="uid=2,ou=test,dc=vt,dc=edu"/>
+ <parameter name="renameNewDn" value="uid=1002,ou=test,dc=vt,dc=edu"/>
+
+ <parameter name="compareDn" value="uid=2,ou=test,dc=vt,dc=edu"/>
+ <parameter name="compareFilter" value="(departmentNumber={0})"/>
+ <parameter name="compareFilterArgs" value="0822"/>
+
+ <parameter name="searchDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="searchFilter" value="(uid={0})"/>
+ <parameter name="searchFilterArgs" value="2"/>
+ <parameter name="searchReturnAttrs" value="departmentNumber|givenName|sn"/>
+ <parameter name="searchResults"
+ value="/edu/vt/middleware/ldap/searchResults-2.ldif"/>
+
+ <parameter name="searchAttributesDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="searchAttributesFilter" value="mail=jdoe2 at vt.edu"/>
+ <parameter name="searchAttributesReturnAttrs" value="departmentNumber|givenName|sn"/>
+ <parameter name="searchAttributesResults"
+ value="/edu/vt/middleware/ldap/searchAttributesResults-2.ldif"/>
+
+ <parameter name="pagedSearchDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="pagedSearchFilter" value="uugid=*"/>
+ <parameter name="pagedSearchResults"
+ value="/edu/vt/middleware/ldap/pagedResults.ldif"/>
+
+ <parameter name="recursiveSearchDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="recursiveSearchFilter" value="(uugid={0})"/>
+ <parameter name="recursiveSearchFilterArgs" value="group2"/>
+ <parameter name="recursiveAttributeHandlerResults"
+ value="/edu/vt/middleware/ldap/recursiveAttributeHandlerResults.ldif"/>
+ <parameter name="recursiveSearchResultHandlerResults"
+ value="/edu/vt/middleware/ldap/recursiveSearchResultHandlerResults.ldif"/>
+
+ <parameter name="mergeSearchDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="mergeSearchFilter" value="(|(uugid=group3)(uugid=group4)(uugid=group5))"/>
+ <parameter name="mergeSearchResults"
+ value="/edu/vt/middleware/ldap/mergeResults.ldif"/>
+
+ <parameter name="mergeDuplicateSearchDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="mergeDuplicateSearchFilter" value="(|(uugid=group3)(uugid=group4)(uugid=group5))"/>
+ <parameter name="mergeDuplicateSearchResults"
+ value="/edu/vt/middleware/ldap/mergeDuplicateResults.ldif"/>
+
+ <parameter name="binarySearchDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="binarySearchFilter" value="uid=2"/>
+ <parameter name="binarySearchReturnAttr" value="jpegPhoto"/>
+ <parameter name="binarySearchResult"
+ value="/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnGH0R0ryvhlAAAdCBPR [...]
+
+ <parameter name="searchExceptionDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="searchExceptionFilter" value="(|(uugid=group2)(uugid=group3)(uugid=group4))"/>
+ <parameter name="searchExceptionResultsSize" value="3"/>
+
+ <parameter name="specialCharSearchDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="specialCharSearchFilter" value="(uid=17893)"/>
+ <parameter name="specialCharSearchResults"
+ value="/edu/vt/middleware/ldap/specialChars.ldif"/>
+
+ <parameter name="rewriteSearchDn" value="dc=blah"/>
+ <parameter name="rewriteSearchFilter" value="(uid=17893)"/>
+ <parameter name="rewriteSearchResults"
+ value="/edu/vt/middleware/ldap/specialChars.ldif"/>
+
+ <parameter name="listDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="listResults" value="uid=1|uid=2"/>
+
+ <parameter name="listBindingsDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="listBindingsResults" value="uid=1|uid=2"/>
+
+ <parameter name="getAttributesDn" value="uid=2,ou=test,dc=vt,dc=edu"/>
+ <parameter name="getAttributesReturnAttrs" value="departmentNumber|givenName|sn"/>
+ <parameter name="getAttributesResults"
+ value="departmentNumber=0822|givenName=John|sn=Doe"/>
+
+ <parameter name="getAttributesBase64Dn" value="uid=2,ou=test,dc=vt,dc=edu"/>
+ <parameter name="getAttributesBase64ReturnAttrs" value="sn|jpegPhoto"/>
+ <parameter name="getAttributesBase64Results"
+ value="sn=Doe|jpegPhoto=/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0bLMRcnZvsdpnG [...]
+
+ <parameter name="getSchemaDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="getSchemaResults"
+ value="/edu/vt/middleware/ldap/getSchemaResults.ldif"/>
+
+ <parameter name="addAttributeDn" value="uid=2,ou=test,dc=vt,dc=edu"/>
+ <parameter name="addAttributeAttribute"
+ value="title=Test User|title=Best User"/>
+
+ <parameter name="addAttributesDn" value="uid=2,ou=test,dc=vt,dc=edu"/>
+ <parameter name="addAttributesAttributes"
+ value="telephoneNumber=15408675309|homePhone=15555555555"/>
+
+ <parameter name="replaceAttributeDn" value="uid=2,ou=test,dc=vt,dc=edu"/>
+ <parameter name="replaceAttributeAttribute"
+ value="title=Unit Test User|title=Best Test User"/>
+
+ <parameter name="replaceAttributesDn" value="uid=2,ou=test,dc=vt,dc=edu"/>
+ <parameter name="replaceAttributesAttributes"
+ value="telephoneNumber=12223334444|homePhone=155566677777"/>
+
+ <parameter name="removeAttributeDn" value="uid=2,ou=test,dc=vt,dc=edu"/>
+ <parameter name="removeAttributeAttribute"
+ value="title=Unit Test User|title=Best Test User"/>
+
+ <parameter name="removeAttributesDn" value="uid=2,ou=test,dc=vt,dc=edu"/>
+ <parameter name="removeAttributesAttributes"
+ value="telephoneNumber=12223334444|homePhone=155566677777"/>
+
+ <parameter name="krb5Realm" value="VT.EDU"/>
+ <parameter name="krb5Kdc" value="ldap-test-1.middleware.vt.edu"/>
+ <parameter name="gssApiSearchDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="gssApiSearchFilter" value="(uid={0})"/>
+ <parameter name="gssApiSearchFilterArgs" value="2"/>
+ <parameter name="gssApiSearchReturnAttrs" value="departmentNumber|givenName|sn"/>
+ <parameter name="gssApiSearchResults"
+ value="/edu/vt/middleware/ldap/searchResults-2.ldif"/>
+
+ <parameter name="loadPropertiesUrl" value="ldap://ldap-test-1.middleware.vt.edu:389 ldap://ldap-test-1.middleware.vt.edu:10389"/>
+ <parameter name="loadPropertiesBaseDn" value="ou=test,dc=vt,dc=edu"/>
+
+ <parameter name="getDnUid" value="3"/>
+ <parameter name="getDnUser" value="jdoe3 at vt.edu"/>
+ <parameter name="getDnDuplicateFilter" value="uid=*"/>
+
+ <parameter name="authenticateDn" value="uid=3,ou=test,dc=vt,dc=edu"/>
+ <parameter name="authenticateDnCredential" value="password3"/>
+ <parameter name="authenticateDnFilter" value="departmentNumber={1}"/>
+ <parameter name="authenticateDnFilterArgs" value="0823"/>
+ <parameter name="authenticateDnReturnAttrs" value="givenName|sn"/>
+ <parameter name="authenticateDnResults" value="givenName=Joho|sn=Dof"/>
+
+ <parameter name="authenticateUser" value="jdoe3 at vt.edu"/>
+ <parameter name="authenticateCredential" value="password3"/>
+ <parameter name="authenticateFilter" value="departmentNumber=0823"/>
+ <parameter name="authenticateReturnAttrs" value="givenName|sn"/>
+ <parameter name="authenticateResults" value="givenName=Joho|sn=Dof"/>
+
+ <parameter name="authenticateSpecialCharsUser" value="17894"/>
+ <parameter name="authenticateSpecialCharsCredential" value="password2"/>
+
+ <parameter name="digestMd5User" value="test3"/>
+ <parameter name="digestMd5Credential" value="password"/>
+
+ <parameter name="cramMd5User" value="test3"/>
+ <parameter name="cramMd5Credential" value="password"/>
+
+ <parameter name="toSearchResultsDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="toSearchResultsFilter" value="uid=4"/>
+ <parameter name="toSearchResultsAttrs" value="departmentNumber|givenName|sn"/>
+ <parameter name="toSearchResultsResults"
+ value="/edu/vt/middleware/ldap/searchResults-4.ldif"/>
+
+ <parameter name="cliSearchArgs"
+ value="-ldapUrl|ldap://ldap-test-1.middleware.vt.edu:10389|-baseDn|ou=test,dc=vt,dc=edu|-bindDn|uid=1,ou=test,dc=vt,dc=edu|-bindCredential|VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2|-query|(mail=jdoe5@vt.edu)|departmentNumber|givenName|sn"/>
+ <parameter name="cliSearchResults"
+ value="/edu/vt/middleware/ldap/searchResults-5.ldif"/>
+
+ <parameter name="cliAuthArgs"
+ value="-ldapUrl|ldap://ldap-test-1.middleware.vt.edu:10389|-baseDn|ou=test,dc=vt,dc=edu|-tls|true|-hostnameVerifier|edu.vt.middleware.ldap.AnyHostnameVerifier|-bindDn|uid=1,ou=test,dc=vt,dc=edu|-bindCredential|VKSxXwlU7YssGl1foLMH2mGMWkifbODb1djfJ4t2|-userField|mail|-user|jdoe6@vt.edu|-credential|password6|-authorizationFilter|(departmentNumber=0826)|departmentNumber|givenName|sn"/>
+ <parameter name="cliAuthResults"
+ value="/edu/vt/middleware/ldap/searchResults-6.ldif"/>
+
+ <parameter name="jaasDn" value="uid=7,ou=test,dc=vt,dc=edu"/>
+ <parameter name="jaasUser" value="jdoe7 at vt.edu"/>
+ <parameter name="jaasUserRole" value="0827"/>
+ <parameter name="jaasUserRoleDefault" value="0827|test-role1|test-role2"/>
+ <parameter name="jaasRole" value="inetOrgPerson|organizationalPerson|person|top|virginiaTechPerson"/>
+ <parameter name="jaasRoleCombined" value="0827|inetOrgPerson|organizationalPerson|person|top|virginiaTechPerson"/>
+ <parameter name="jaasRoleCombinedRecursive" value="0827|group6|group7|group8|group9"/>
+ <parameter name="jaasCredential" value="password7"/>
+
+ <parameter name="webXml" value="src/test/resources/web.xml"/>
+
+ <parameter name="ldifSearchServletQuery" value="mail=jdoe8 at vt.edu"/>
+ <parameter name="ldifSearchServletAttrs" value="departmentNumber|givenName|sn|jpegPhoto"/>
+ <parameter name="ldifSearchServletLdif" value="/edu/vt/middleware/ldap/searchResults-8.ldif"/>
+
+ <parameter name="dsmlSearchServletQuery" value="uid=8"/>
+ <parameter name="dsmlSearchServletAttrs" value="departmentNumber|givenName|sn|jpegPhoto"/>
+ <parameter name="dsmlSearchServletLdif" value="/edu/vt/middleware/ldap/searchResults-8.ldif"/>
+
+ <parameter name="attributeServletQuery" value="(&(givenName=johu)(sn=dol))"/>
+ <parameter name="attributeServletAttr" value="jpegPhoto"/>
+ <parameter name="attributeServletValue" value="/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAANABcDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAiEAEAAgIABQUAAAAAAAAAAAABAAMCEQQSMUFxISIjMnP/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A71fHZZW+ge48MWqvG0b [...]
+
+ <parameter name="sessionManagerUser" value="jdoe10 at vt.edu"/>
+ <parameter name="sessionManagerPassword" value="password10"/>
+
+ <parameter name="dsmlSearchDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="dsmlSearchFilter" value="(uid=11)"/>
+
+ <parameter name="ldifSearchDn" value="ou=test,dc=vt,dc=edu"/>
+ <parameter name="ldifSearchFilter" value="(uid=12)"/>
+
+ <parameter name="ldapHost" value="ed-dev"/>
+ <parameter name="sleepTime" value="10000"/>
+
+ <test name="coretests" parallel="methods" thread-count="12">
+ <groups>
+ <run>
+ <include name="ldaptest" />
+ <include name="authtest" />
+ <include name="beantest" />
+ <include name="jaastest" />
+ <include name="servlettest" />
+ <include name="dsmltest" />
+ <include name="ldiftest" />
+ <include name="ssltest" />
+ <include name="wikitest" />
+ </run>
+ </groups>
+ <packages>
+ <package name="edu.vt.middleware.ldap.*" />
+ </packages>
+ </test>
+ <test name="clitests">
+ <groups>
+ <run>
+ <include name="ldapclitest" />
+ <include name="authclitest" />
+ </run>
+ </groups>
+ <packages>
+ <package name="edu.vt.middleware.ldap.*" />
+ </packages>
+ </test>
+ <test name="conntest">
+ <groups>
+ <run>
+ <include name="conntest" />
+ </run>
+ </groups>
+ <packages>
+ <package name="edu.vt.middleware.ldap.*" />
+ </packages>
+ </test>
+<!-- must run separate from other tests
+ <test name="loadtests" parallel="methods" thread-count="12">
+ <groups>
+ <run>
+ <include name="authloadtest" />
+ </run>
+ </groups>
+ <packages>
+ <package name="edu.vt.middleware.ldap.*" />
+ </packages>
+ </test>
+ <test name="pooltests" parallel="methods" thread-count="12">
+ <groups>
+ <run>
+ <include name="softlimitpooltest" />
+ <include name="blockingpooltest" />
+ <include name="blockingtimeoutpooltest" />
+ <include name="sharedpooltest" />
+ <include name="connstrategypooltest" />
+ <include name="comparisonpooltest" />
+ </run>
+ </groups>
+ <packages>
+ <package name="edu.vt.middleware.ldap.pool.*" />
+ </packages>
+ </test>
+-->
+</suite>
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/libvt-ldap-java.git
More information about the pkg-java-commits
mailing list