├── .gitignore
├── LICENSE
├── NOTICE.txt
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── org
│ │ └── slc
│ │ └── sli
│ │ └── ldap
│ │ └── inmemory
│ │ ├── LdapInMemoryContextListener.java
│ │ ├── LdapServer.java
│ │ ├── LdapServerImpl.java
│ │ ├── domain
│ │ ├── Entry.java
│ │ ├── Ldif.java
│ │ ├── Listener.java
│ │ ├── PasswordRewriteStrategy.java
│ │ ├── Root.java
│ │ ├── Schema.java
│ │ └── Server.java
│ │ ├── loghandlers
│ │ ├── AccessLogHandler.java
│ │ └── LDAPDebugLogHandler.java
│ │ └── utils
│ │ └── ConfigurationLoader.java
├── resources
│ ├── ldap-inmemory-config.xsd
│ └── log4j.xml
└── webapp
│ └── WEB-INF
│ └── web.xml
└── test
├── java
└── org
│ └── slc
│ └── sli
│ └── ldap
│ └── inmemory
│ ├── LdapServerSingletonHarness.java
│ ├── LdapServerTest.java
│ ├── domain
│ └── ServerTest.java
│ └── utils
│ ├── ConfigurationLoaderTest.java
│ └── LdifAdjustor.java
└── resources
├── ldap-inmemory-config.xml
├── ldif
├── 2014_01_16_ldap_export.ldif.modified
├── ldap-schema.ldif
└── original
│ ├── 2014_01_16_ldap_export.ldif
│ └── include_users.txt
└── log4j.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | lib/
2 |
3 | # Compiled source #
4 | ###################
5 | *.com
6 | *.class
7 | *.dll
8 | *.exe
9 | *.o
10 | *.so
11 |
12 | # Packages #
13 | ############
14 | # it's better to unpack these files and commit the raw source
15 | # git has its own built in compression methods
16 | *.7z
17 | *.dmg
18 | *.gz
19 | *.iso
20 | *.jar
21 | *.rar
22 | *.tar
23 | *.zip
24 | *.war
25 | *.ear
26 |
27 | # Logs and databases #
28 | ######################
29 | *.log
30 | *.out
31 | *.sql
32 | *.sqlite
33 |
34 | # OS generated files #
35 | ######################
36 | .DS_Store
37 | .DS_Store?
38 | ._*
39 | .Spotlight-V100
40 | .Trashes
41 | ehthumbs.db
42 | Thumbs.db
43 |
44 | # Eclipse
45 | .classpath
46 | .project
47 | .settings/
48 |
49 | # Intellij
50 | *.idea/
51 | *.iml
52 | *.iws
53 |
54 | # Maven
55 | log/
56 | target/
57 |
58 |
59 | .idea
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/NOTICE.txt:
--------------------------------------------------------------------------------
1 | ldap-in-memory
2 |
3 |
4 |
5 | Copyright 2012 inBloom, Inc.
6 |
7 | This product includes software developed by inBloom (http://www.inbloom.org/).
8 |
9 |
10 | Licensed under the Apache License, Version 2.0 (the "License");
11 | you may not use this software except in compliance with the License.
12 | You may obtain a copy of the License at
13 |
14 | http://www.apache.org/licenses/LICENSE-2.0
15 |
16 | Unless required by applicable law or agreed to in writing, software
17 | distributed under the License is distributed on an "AS IS" BASIS,
18 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | See the License for the specific language governing permissions and
20 | limitations under the License.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | .___ _____ .____ ________ _____ __________
2 | | | / \ | | \______ \ / _ \\______ \
3 | | |/ \ / \ ______ | | | | \ / /_\ \| ___/
4 | | / Y \ /_____/ | |___ | ` \/ | \ |
5 | |___\____|__ / |_______ \/_______ /\____|__ /____|
6 | \/ \/ \/ \/
7 |
8 | # In-Memory LDAP Server
9 |
10 | ## Overview
11 | The in-memory LDAP server (IM-LDAP) simplifies the task of standing up an LDAP server for non-production environments. This document explains how to easily configure an IM-LDAP directory server for testing, demonstration purposes, or simple LDAP data processing tasks. IM-LDAP allows for easy, cross-platform development of applications, such as the [Secure Data Service](https://github.com/inbloom/secure-data-service), that rely on an LDAP server.
12 |
13 | This implementation leverages the [UnboundedID Java SDK](https://www.unboundid.com/products/ldapsdk/). This library includes a fast, powerful, and user-friendly Java API for communicating with LDAP directory servers. The UnboundID Identity Data Platform provides a directory services solution designed to scale for extremely large identity datasets and also provides the data synchronization services necessary to support a low-risk migration from existing directory services solutions.
14 |
15 | The UnboundID LDAP SDK for Java is free to use and redistribute in open source or proprietary applications under the GPLv2, LGPLv2.1 and the UnboundID Free Use License. It does not have any third-party dependencies and commercial support is available from UnboundID.
16 |
17 | The UnboundedID Java SDK has capability restrictions that require LDAP schema changes to be performed offline. For more information on LDAP SDK features refer to:
18 |
19 | https://www.unboundid.com/products/ldapsdk/docs/getting-started/features.php
20 |
21 | ### IMPORTANT
22 | The UnboundedID Java SDK only provides an implementation to support plain text passwords (an interface is provided to create other extensions). When LDIF files are imported, the IM-LDAP server will automatically reset the password to "username" + "1234".
23 |
24 | ## Configuration
25 | The IM-LDAP self-configures by using the contents of the configuration file ldap-inmemory-config.xml, which is discussed below. The configuration file allows for specification of the root node, binding details (including password), the ability to add individual LDAP entries (without attributes), and the import of specified LDIF export files.
26 |
27 | ## LDIF Adjustor
28 | The LDAP schema is loaded from the LDIF file that is defined in the configuration file. A utility class has been added as a tool to simplify the configuration process. LDIF exports must conform to the schema and include the changetype attribute. A helper class has been added which can be run to generate modified LDIF export files to add the missing attribute.
29 |
30 | Please refer to the class `com.slc.sli.ldap.inmemory.utils.LdifAdjustor` for more information. This class supports both IDE and command line execution.
31 |
32 | ## Deployment
33 | Deployment occurs by adding a WAR file and resources. To accomplish this, you will copy and modify the resources from test into your classpath to a servlet container such as Jetty or Tomcat. A class is provided within the test package to allow the server to be easily run within an IDE. By default, the application is configured to run on localhost `127.0.0.1` and on port `10389`.
34 |
35 | ## Logging
36 |
37 | Logging occurs through the SLF4J logging facade which is backed by Log4J. Log levels can be configured from within the `log4j.xml`. Two handlers have been implemented to allow IM-LDAP events to be exposed by log statements, which could also be captured by specific log file appenders (not included at this time).
38 |
39 | ## Listener
40 |
41 | The IM-LDAP allows for multiple listeners to be configured against the same internal server repository distinguished by port and authentication mechanism. However, currently, this implementation only supports use of one listener via the configuration file. Refer to the UnboundedID documentation [link] for more information.
42 |
43 | ## Supported Platforms
44 |
45 | The IM-LDAP runs within a Java Virtual Machine.
46 |
47 | ## Recommended Skill Sets
48 |
49 | ### Skill set for IM-LDAP out-of-box use:
50 |
51 | - Maven
52 | - Jetty
53 |
54 | ### Skill set to change or modify how IM-LDAP works:
55 |
56 | - Familiarity with LDAP (entries, Apache Directory Studio, LDIF export format, LDAP schemas, keystones)
57 | - Maven
58 | - Jetty/Tomcat
59 | - XML
60 | - Java (classpath)
61 |
62 | ### LDAP Best Practices
63 |
64 | http://www.ldapguru.info/ldap/ldap-programming-practices.html
65 |
66 | ## Integrating with the [Secure Data Service](http://github.com/inbloom/secure-data-service)
67 |
68 | - SDS installation/configuration is recommended prior to setting up LDAP. Then, once the LDAP is set up, you can deploy and run it on the configured SDS platform. The links below provide information on SDS installation/configuration:
69 |
70 | - inBloom platform with the following components running:
71 |
72 | - API
73 | - Simple IDP
74 | - Ingest
75 | - Search Indexer
76 |
77 | - Small dataset successfully loaded.
78 |
79 | ## Configuration
80 |
81 | ### Platform
82 |
83 | After SDS artifacts have been built and deployed, you must modify the sli.properties entries. Alternatively, the YML used to generate sli.properties could be modified. However, this procedure is more complex and changes the default "local" behavior.
84 |
85 | To generate a value for the property sli.api.ldap.pass that matches the IM-LDAP root password, run the org.slc.sli.encryption.tool.Encryptor class via your IDE. The class contains a main method that can be executed from within the IDE (or modified locally as required). The example below uses a value based on the password “test1234”.
86 |
87 | **NOTE:** `sli.simple-idp.ldap.urls` should use the appropriate IP address. This implementation will attempt to bind to all interfaces available on the provided address.
88 |
89 | Within `sli.properties` change the following values:
90 |
91 | ```
92 | sli.simple-idp.ldap.urls = ldap://127.0.0.1:10389
93 | sli.api.ldap.pass = FEEBEC071D0C8CA1886224930EEE3F6D
94 | ```
95 | For reference: the original value from git:
96 | ```
97 | sli.api.ldap.pass = F8EEEB5397084B5DD8EA44604D8CC39A
98 | sli.api.ldap.user = cn=Admin,dc=slidev,dc=org
99 | ```
100 |
101 | ### ldap-inmemory-config.xml
102 | - Refer to the ldap-inmemory-config.xml file found within the test/resources. This config file is validated against the ldap-inmemory-config.xsd.
103 | - A root element must be specified within the configuration. All LDIFs are loaded beneath the root. If an LDIF contains the root node, remove it from the LDIF and add it to the config file. Note that only one root element may be specified.
104 | - LDIF files are loaded, in order, as defined in the configuration file. One to many LDIF files may be loaded. Optionally, zero to many LDAP entries may be specified (without attributes).
105 | - At this time, only one listener is supported. If the address is not specified for a listener, the server will attempt to bind to all available local interfaces. (This is considered default behavior for the UnboundedID SDK. For more information, refer to the UnboundedID documentation [link].)
106 |
107 | ## Local Deployment
108 |
109 | A local deployment can be invoked directly via Maven. When the IM-LDAP server is run in this mode, it will use the "test" classpath and "test" resources.
110 |
111 | 1. Access the git repository and navigate to the root directory.
112 | 2. Build the artifacts: `mvn clean install package -Dsli.env=local-ldap-server`.
113 | 3. Run the server using `mvn jetty:run`. This will use the “test” classpath (which means resources found beneath test).
114 | 4. Start the inBloom admin-rails application.
115 | 5. Select the URL: http://local.slidev.org:3001/application_authorizations
116 | 6. Login to the `inBloom` realm using `iladmin`.
117 | 7. Start the data browser: http://local.slidev.org:3000
118 | 8. Login to "Il daybreak 4529" realm using the user "rrogers". Remember, all passwords are reset to "username" + "1234" when LDIFs are loaded, so the password will be "rrogers1234".
119 | 9. Browse data.
120 |
121 | **NOTE:** It is possible to restart the IM-LDAP server while Simple IDP is running without affecting their respective functionality.
122 |
123 | ## Deployment
124 |
125 | A deployment can be invoked directly via Maven. When the IM-LDAP server is run in this mode, it will use the "test" classpath and "test" resources.
126 |
127 | 1. Access the git repository and navigate to the root directory.
128 | 2. Build the artifacts: `mvn clean install package`.
129 | 3. Copy the resulting war file into your Java servlet container/application server (e.g. Tomcat or Jetty).
130 | 4. Make the required resources are available on the classpath:
131 | 1. `ldap-inmemory-config.xml`
132 | 2. Resources specified within the XML must exist relative to classpath:
133 | - `src/test/resources/ldif/ldap-schema.ldif` (Replace with LDIF schema export if customized, one schema for all LDIFs.)
134 | - `src/test/resources/ldif/2014_01_16_ldap_export.ldif.modified` (Replace with LDIFs, if customized.)
135 | 5. Start the server.
136 | 6. Verify proper functionality using [Apache Directory Studio](https://directory.apache.org/studio/).
137 |
138 | ### Apache Directory Studio
139 | You can connect to the IM-LDAP by using the Apache Directory Studio.
140 | Create a new connection to the local LDAP:
141 |
142 | | Property | Setting |
143 | | ---------------------- | -------------------------- |
144 | | Hostname | 127.0.0.1 |
145 | | Port | 10389 |
146 | | Authentication Method | Simple Authentication |
147 | | Bind DN | cn=Admin,dc=slidev,dc=org |
148 | | Bind Password | test1234 |
149 |
150 | ## Resources
151 | The following list includes items related to this solution:
152 |
153 | - http://www.unboundid.com/products/ldap-sdk/docs/javadoc/
154 | - http://www.unboundid.com/products/ldapsdk/docs/javadoc/com/unboundid/ldap/listener/InMemoryDirectoryServer.html
155 | - http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/util/ssl/SSLUtil.html
156 | - http://github.com/trevershick/ldap-test-utils
157 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | 4.0.0
5 |
6 | org.slc.sli.ldap.inmemory
7 | ldap-inmemory
8 | war
9 | 1.0-SNAPSHOT
10 |
11 | ldap-inmemory Maven Webapp
12 | http://maven.apache.org
13 |
14 |
15 |
16 | UTF-8
17 | UTF-8
18 | 1.7
19 |
20 | 3.1
21 | 2.6
22 |
23 |
24 | 1.2
25 | 1.1.3
26 | 3.2.1
27 | 2.5
28 | 4.9
29 | 1.2.17
30 | 6.1.12
31 | 1.7.5
32 | 2.3.5
33 |
34 |
35 |
36 |
37 |
38 | javax.servlet
39 | servlet-api
40 | ${j2ee.servlet.version}
41 |
42 |
43 |
44 | com.unboundid
45 | unboundid-ldapsdk
46 | ${unbounded.ldap.version}
47 |
48 |
49 |
50 | org.apache.commons
51 | commons-lang3
52 | ${apache.commons.lang.version}
53 |
54 |
55 |
56 | commons-cli
57 | commons-cli
58 | ${apache.commons.cli.version}
59 |
60 |
61 |
62 | org.slf4j
63 | slf4j-api
64 | ${slf4j.version}
65 |
66 |
67 |
68 | org.slf4j
69 | slf4j-log4j12
70 | ${slf4j.version}
71 |
72 |
73 |
74 | log4j
75 | log4j
76 | ${log4j.version}
77 |
78 |
79 |
80 | junit
81 | junit
82 | ${junit.version}
83 | test
84 |
85 |
86 |
87 |
88 |
89 |
90 | ldap-inmemory
91 |
92 | src/main/java
93 | src/test/java
94 |
95 |
96 |
97 | src/main/resources
98 |
99 | log4j.xml
100 | ldap-inmemory-config.xsd
101 |
102 |
103 |
104 |
105 |
106 |
107 | src/test/resources
108 |
109 |
110 |
111 |
112 |
113 | org.apache.maven.plugins
114 | maven-compiler-plugin
115 | ${maven.plugin.compiler}
116 |
117 | ${jdk.version}
118 | ${jdk.version}
119 |
120 |
121 |
122 |
123 | org.mortbay.jetty
124 | maven-jetty-plugin
125 | ${maven.jetty.version}
126 |
127 |
128 | true
129 | 5
130 |
136 |
137 |
138 | 8093
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/LdapInMemoryContextListener.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import javax.servlet.ServletContext;
7 | import javax.servlet.ServletContextEvent;
8 | import javax.servlet.ServletContextListener;
9 |
10 | /**
11 | * Created by tfritz on 12/26/13.
12 | */
13 |
14 | public class LdapInMemoryContextListener implements ServletContextListener {
15 | private final static Logger LOG = LoggerFactory.getLogger(LdapInMemoryContextListener.class);
16 |
17 | ServletContext context;
18 |
19 | public void contextInitialized(ServletContextEvent event) {
20 | try {
21 | LdapServer.getInstance().start();
22 | } catch (Exception e) {
23 | LOG.error(e.getMessage(), e);
24 | }
25 | }
26 |
27 | public void contextDestroyed(ServletContextEvent event) {
28 | LdapServer.getInstance().stop();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/LdapServer.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | /**
7 | * This class encapsulates an in-memory LDAP server, which is hydrated from an LDIF file (an LDAP export),
8 | * using the UnboundedId java SDK.
9 | *
10 | * NOTE: THIS CLASS IS NOT INTENDED FOR PRODUCTION USE, IT IS ONLY INTENDED FOR DEVELOPMENT USES.
11 | *
12 | * Created by tfritz on 12/30/13.
13 | */
14 | public class LdapServer {
15 | private final static Logger LOG = LoggerFactory.getLogger(LdapServer.class);
16 |
17 | /**
18 | * Hide the default Constructor.
19 | */
20 | private LdapServer() {
21 | }
22 |
23 | private static volatile LdapServerImpl instance = null;
24 |
25 | public static LdapServerImpl getInstance() {
26 | if (instance == null) {
27 | synchronized(LdapServer.class) {
28 | if (instance == null)
29 | instance = new LdapServerImpl();
30 | }
31 | }
32 | return instance;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/LdapServerImpl.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory;
2 |
3 | import com.unboundid.ldap.listener.InMemoryDirectoryServer;
4 | import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
5 | import com.unboundid.ldap.listener.InMemoryListenerConfig;
6 | import com.unboundid.ldap.sdk.*;
7 | import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedRequest;
8 | import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedResult;
9 | import com.unboundid.ldap.sdk.schema.Schema;
10 | import com.unboundid.ldif.LDIFChangeRecord;
11 | import com.unboundid.ldif.LDIFReader;
12 | import org.apache.commons.lang3.StringUtils;
13 | import org.apache.commons.lang3.builder.ToStringBuilder;
14 | import org.apache.commons.lang3.builder.ToStringStyle;
15 | import org.apache.commons.lang3.exception.ExceptionUtils;
16 | import org.slc.sli.ldap.inmemory.domain.Ldif;
17 | import org.slc.sli.ldap.inmemory.domain.Listener;
18 | import org.slc.sli.ldap.inmemory.domain.PasswordRewriteStrategy;
19 | import org.slc.sli.ldap.inmemory.domain.Server;
20 | import org.slc.sli.ldap.inmemory.loghandlers.AccessLogHandler;
21 | import org.slc.sli.ldap.inmemory.loghandlers.LDAPDebugLogHandler;
22 | import org.slc.sli.ldap.inmemory.utils.ConfigurationLoader;
23 | import org.slf4j.Logger;
24 | import org.slf4j.LoggerFactory;
25 |
26 | import java.io.File;
27 | import java.io.FileNotFoundException;
28 | import java.io.InputStream;
29 | import java.net.BindException;
30 | import java.net.InetAddress;
31 | import java.net.URL;
32 | import java.util.ArrayList;
33 | import java.util.Collection;
34 | import java.util.concurrent.TimeUnit;
35 | import java.util.concurrent.atomic.AtomicBoolean;
36 | import java.util.concurrent.locks.ReentrantLock;
37 |
38 | /**
39 | * LDAP Server implementation using UnboundedId Java SDK to expose an in-memory LDAP server.
40 | * The LDIF file was generated by using Apache Directory studio to export an LDIF file from
41 | * the hosted LDAP server. Then, the LdifAdjustor was run against that export file to add
42 | * missing changetype attributes -- which are used to import the LDIF file into the in-memory
43 | * LDAP provider.
44 | * Also, be aware, the corresponding LDAP schema is also specified to be able to properly load
45 | * the LDIF export.
46 | * TODO Add an Mbean to expose server controls (generate snapshots/LDIF exports, etc), or could use REST endpoints but that would couple to servlet deployment.
47 | * TODO incorporate Spring for DI once implementation settles.
48 | */
49 | public class LdapServerImpl {
50 | private final static Logger LOG = LoggerFactory.getLogger(LdapServerImpl.class);
51 |
52 | /**
53 | * Lock timeout value. If needed, move this value into config XML.
54 | */
55 | private static final int LOCK_TIMEOUT = 60;
56 | /**
57 | * Lock timeout value Time Unit. If needed, move this value into config XML.
58 | */
59 | private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
60 |
61 | public static final String CONFIG_FILE = "ldap-inmemory-config.xml";
62 | public static final String CONFIG_SCHEMA_FILE = "ldap-inmemory-config.xsd";
63 | public static final String LDIF_SCHEMA_FILE = "ldif" + File.separator + "ldap-schema.ldif";
64 |
65 | private static final String LOCK_TIMEOUT_MSG = "Unable to obtain lock due to timeout after " + LOCK_TIMEOUT + " " + TIME_UNIT.toString();
66 | private static final String SERVER_NOT_STARTED = "The LDAP server is not started.";
67 | private static final String SERVER_ALREADY_STARTED = "The LDAP server is already started.";
68 |
69 | //should wrap this with atomicreference
70 | private InMemoryDirectoryServer server;
71 |
72 | private PasswordRewriteStrategy passwordStrategy = PasswordRewriteStrategy.getInstance();
73 |
74 | /**
75 | * Either synchronize access to start/stop methods, or protect isStarted with a lock.
76 | */
77 | private final AtomicBoolean isStarted = new AtomicBoolean(Boolean.FALSE);
78 |
79 | /**
80 | * Lock used to ensure threadsafe server state changes (e.g. startup, shutdown, etc).
81 | */
82 | private final ReentrantLock serverStateLock = new ReentrantLock();
83 |
84 | //should wrap this with atomicreference
85 | /**
86 | * Encapsulates configuration entries read from properties file.
87 | */
88 | private Server configuration;
89 |
90 | /**
91 | * Default Constructor.
92 | */
93 | public LdapServerImpl() {
94 | }
95 |
96 | /**
97 | * Helper method used to indentify if the server in memory LDAP server has been initialized, loaded and is listening for messages.
98 | *
99 | * @return boolean True if started.
100 | */
101 | public boolean isStarted() {
102 | return this.isStarted.get();
103 | }
104 |
105 | /**
106 | * Returns (or better yet, leaks) the instance of the InMemoryDirectoryServer. Since this class is for development, it's a convenience for testing.
107 | *
108 | * @return InMemoryDirectoryServer
109 | */
110 | public InMemoryDirectoryServer getInMemoryDirectoryServer() {
111 | boolean hasLock = false;
112 | InMemoryDirectoryServer inMemoryDirectoryServer = null;
113 | try {
114 | hasLock = serverStateLock.tryLock(LdapServerImpl.LOCK_TIMEOUT, LdapServerImpl.TIME_UNIT);
115 | if (hasLock) {
116 | inMemoryDirectoryServer = server;
117 | } else {
118 | throw new IllegalStateException(LdapServerImpl.LOCK_TIMEOUT_MSG);
119 | }
120 | } catch (InterruptedException ioe) {
121 | //lock interrupted
122 | LOG.error(ioe.getMessage(), ioe);
123 | } finally {
124 | if (hasLock) {
125 | serverStateLock.unlock();
126 | }
127 | }
128 | return inMemoryDirectoryServer;
129 | }
130 |
131 | /**
132 | * Starts an instance of the in memory LDAP server.
133 | *
134 | * @throws Exception
135 | */
136 | public void start() throws Exception {
137 | boolean hasLock = false;
138 | try {
139 | hasLock = serverStateLock.tryLock(LdapServerImpl.LOCK_TIMEOUT, LdapServerImpl.TIME_UNIT);
140 | if (hasLock) {
141 | String configFile = LdapServerImpl.CONFIG_FILE;
142 | String schemaFile = LdapServerImpl.CONFIG_SCHEMA_FILE;
143 | LOG.info(" configFile: {}", configFile);
144 | LOG.info(" schemaFile: {}", schemaFile);
145 | configuration = ConfigurationLoader.load(configFile, schemaFile);
146 | doStart();
147 | this.isStarted.set(Boolean.TRUE);
148 | } else {
149 | throw new IllegalStateException(LdapServerImpl.LOCK_TIMEOUT_MSG);
150 | }
151 | } catch (InterruptedException ioe) {
152 | //lock interrupted
153 | LOG.error(ioe.getMessage(), ioe);
154 | } finally {
155 | if (hasLock) {
156 | serverStateLock.unlock();
157 | }
158 | }
159 | }
160 |
161 | private void doStart() throws Exception {
162 | if (isStarted.get()) {
163 | throw new IllegalStateException(LdapServerImpl.SERVER_ALREADY_STARTED);
164 | }
165 | LOG.info("Starting up in-Memory Ldap Server.");
166 | configureAndStartServer();
167 | }
168 |
169 | /**
170 | * Returns the Listener Configs to register with he InMemory LDAP server.
171 | *
172 | * @return The server object loaded from XML file by JAXB.
173 | * @throws Exception
174 | */
175 | public Collection getInMemoryListenerConfigs() throws Exception {
176 | Collection listenerConfigs = new ArrayList();
177 |
178 | if (configuration.getListeners() != null) {
179 | for (Listener listener : configuration.getListeners()) {
180 | InetAddress listenAddress = null; //according to javadoc, will listen for all connections on all addresses on all interfaces.
181 | if (!StringUtils.isEmpty(listener.getAddress())) {
182 | listenAddress = InetAddress.getByName(listener.getAddress());
183 | }
184 | int listenPort = Integer.parseInt(listener.getPort());
185 | javax.net.ServerSocketFactory serverSocketFactory = null; //if null uses the JVM default socket factory.
186 | javax.net.SocketFactory clientSocketFactory = null; //if null uses the JVM default socket factory.
187 | javax.net.ssl.SSLSocketFactory startTLSSocketFactory = null; //StartTLS is not supported on this listener.
188 | InMemoryListenerConfig listenerConfig = new InMemoryListenerConfig(listener.getName(), listenAddress, listenPort, serverSocketFactory, clientSocketFactory, startTLSSocketFactory);
189 | listenerConfigs.add(listenerConfig);
190 | }
191 | }
192 |
193 | return listenerConfigs;
194 | }
195 |
196 | /**
197 | * Configures and starts the local LDAP server. This method is invoked by start().
198 | *
199 | * @throws Exception
200 | */
201 | protected synchronized void configureAndStartServer() throws Exception {
202 | LOG.info(">>>LdapServerImpl.configureServer()");
203 |
204 | Collection listenerConfigs = getInMemoryListenerConfigs();
205 |
206 | Schema schema = null;
207 | if (configuration.getSchema() == null || StringUtils.isEmpty(configuration.getSchema().getName())) {
208 | schema = Schema.getDefaultStandardSchema();
209 | } else {
210 | final String schemaName = configuration.getSchema().getName();
211 | URL schemaUrl = this.getClass().getClassLoader().getResource(schemaName);
212 | File schemaFile = new File(schemaUrl.toURI());
213 | schema = Schema.getSchema(schemaFile);
214 | }
215 |
216 | final String rootObjectDN = configuration.getRoot().getObjectDn();
217 | InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(new DN(rootObjectDN));
218 | //LOG.debug(System.getProperty("java.class.path"));
219 |
220 | config.setSchema(schema); //schema can be set on the rootDN too, per javadoc.
221 | config.setListenerConfigs(listenerConfigs);
222 |
223 | LOG.info(config.getSchema().toString());
224 |
225 | /* Add handlers for debug and acccess log events. These classes can be extended or dependency injected in a future version for more robust behavior. */
226 | config.setLDAPDebugLogHandler(new LDAPDebugLogHandler());
227 | config.setAccessLogHandler(new AccessLogHandler());
228 | config.addAdditionalBindCredentials(configuration.getBindDn(), configuration.getPassword());
229 |
230 | server = new InMemoryDirectoryServer(config);
231 |
232 | try {
233 | /* Clear entries from server. */
234 | server.clear();
235 | server.startListening();
236 |
237 | loadRootEntry();
238 | loadEntries();
239 | loadLdifFiles();
240 | resetPersonPasswords();
241 |
242 | LOG.info(" Total entry count: {}", server.countEntries());
243 | LOG.info("<<>>LdapServerImpl.loadRootEntry()");
286 |
287 | SearchResultEntry entry = this.server.getEntry(configuration.getRoot().getObjectDn());
288 | if (entry == null) {
289 | LOG.info(" Root entry not found, create it.");
290 | final String attributeName = "objectClass";
291 | final Collection attributeValues = new ArrayList();
292 | for (String name : configuration.getRoot().getObjectClasses()) {
293 | attributeValues.add(name);
294 | }
295 | Entry rootEntry = new Entry(new DN(configuration.getRoot().getObjectDn()));
296 | rootEntry.addAttribute(attributeName, attributeValues);
297 | this.server.add(rootEntry);
298 | }
299 | entry = this.server.getEntry(configuration.getRoot().getObjectDn());
300 | LOG.info(" Added root entry: {}", ToStringBuilder.reflectionToString(entry, ToStringStyle.MULTI_LINE_STYLE));
301 |
302 | }
303 |
304 | /**
305 | * Loads entries (non-attribute entries) as specified in the configuration file.
306 | *
307 | * @throws Exception
308 | */
309 | protected void loadEntries() throws Exception {
310 | LOG.info(">>>LdapServerImpl.loadEntries()");
311 |
312 | if (configuration.getEntries() != null && configuration.getEntries().size() > 0) {
313 | for (org.slc.sli.ldap.inmemory.domain.Entry configEntry : configuration.getEntries()) {
314 | LOG.info(" creating entry.");
315 | final String attributeName = "objectClass";
316 | final Collection attributeValues = new ArrayList();
317 |
318 | for (String name : configEntry.getObjectClasses()) {
319 | attributeValues.add(name);
320 | }
321 |
322 | Entry en = new Entry(new DN(configEntry.getObjectDn()));
323 | en.addAttribute(attributeName, attributeValues);
324 | this.server.add(en);
325 | LOG.info(" Added entry: {}", ToStringBuilder.reflectionToString(en, ToStringStyle.MULTI_LINE_STYLE));
326 | }
327 | }
328 | LOG.info("<<>>loadLdifFiles()");
338 |
339 | LOG.debug("{}", System.getProperty("java.class.path"));
340 | LOG.info(" ...loading LDIF resources: ");
341 |
342 | int ldifLoadCount = 0;
343 | for (Ldif ldif : configuration.getLdifs()) {
344 | LOG.info("----------------------------------------------------------");
345 | LOG.info(" loading LDIF: {}", ldif.getName());
346 | ldifLoadCount++;
347 | InputStream resourceAsStream = null;
348 | try {
349 | LOG.info(" file: '{}'", ldif.getName());
350 | resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(ldif.getName());
351 | if (resourceAsStream == null) {
352 | throw new FileNotFoundException("Should be able to load: " + ldif.getName());
353 | }
354 | LDIFReader r = new LDIFReader(resourceAsStream);
355 | LDIFChangeRecord readEntry = null;
356 | int entryCount = 0;
357 | while ((readEntry = r.readChangeRecord()) != null) {
358 | LOG.debug(" ...readEntry");
359 | LOG.debug("{}", ToStringBuilder.reflectionToString(readEntry, ToStringStyle.MULTI_LINE_STYLE));
360 | entryCount++;
361 | readEntry.processChange(server);
362 | }
363 | LOG.info(" # of entries loaded: {}", entryCount);
364 | } finally {
365 | if (resourceAsStream != null) {
366 | resourceAsStream.close();
367 | }
368 | }
369 | }
370 | LOG.info("----------------------------------------------------------");
371 | LOG.info(" # of LDIF files loaded: {}", ldifLoadCount);
372 | LOG.info(" Post LDIF load server entry count: {}", server.countEntries());
373 | LOG.info("<< objectClasses;
22 |
23 | public String getObjectDn() {
24 | return objectDn;
25 | }
26 |
27 | @XmlElement(name="objectDn", required = true)
28 | public void setObjectDn(String objectDn) {
29 | this.objectDn = objectDn;
30 | }
31 |
32 | public List getObjectClasses() {
33 | return objectClasses;
34 | }
35 |
36 | @XmlElementWrapper(name="objectClasses", required = true)
37 | @XmlElement(name="objectClass", required = true)
38 | public void setObjectClasses(List objectClasses) {
39 | this.objectClasses = objectClasses;
40 | }
41 |
42 | @Override
43 | public String toString() {
44 | return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/domain/Ldif.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory.domain;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.apache.commons.lang3.builder.ToStringStyle;
5 |
6 | import javax.xml.bind.annotation.XmlElement;
7 | import javax.xml.bind.annotation.XmlRootElement;
8 | import javax.xml.bind.annotation.XmlType;
9 |
10 | /**
11 | * Created by tfritz on 1/7/14.
12 | */
13 | @XmlRootElement(name= "ldif")
14 | @XmlType(propOrder = {"name"})
15 | public class Ldif {
16 |
17 | private String name;
18 |
19 | public String getName() {
20 | return name;
21 | }
22 |
23 | @XmlElement(name="name", required = true)
24 | public void setName(String name) {
25 | this.name = name;
26 | }
27 |
28 | @Override
29 | public String toString() {
30 | return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/domain/Listener.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory.domain;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.apache.commons.lang3.builder.ToStringStyle;
5 |
6 | import javax.xml.bind.annotation.XmlElement;
7 | import javax.xml.bind.annotation.XmlRootElement;
8 | import javax.xml.bind.annotation.XmlType;
9 |
10 | /**
11 | * Created by tfritz on 1/7/14.
12 | */
13 | @XmlRootElement(name="listener")
14 | @XmlType(propOrder = {"name","port","address"})
15 | public class Listener {
16 | private String name;
17 | private String port;
18 | private String address;
19 |
20 | public String getName() {
21 | return name;
22 | }
23 |
24 | @XmlElement(name="name", required = true)
25 | public void setName(String name) {
26 | this.name = name;
27 | }
28 |
29 | public String getPort() {
30 | return port;
31 | }
32 |
33 | @XmlElement(name="port", required = true)
34 | public void setPort(String port) {
35 | this.port = port;
36 | }
37 |
38 | public String getAddress() {
39 | return address;
40 | }
41 |
42 | @XmlElement(name="address", required = true)
43 | public void setAddress(String address) {
44 | this.address = address;
45 | }
46 |
47 | @Override
48 | public String toString() {
49 | return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/domain/PasswordRewriteStrategy.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory.domain;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * Created By: paullawler
8 | */
9 | public class PasswordRewriteStrategy {
10 |
11 | static Map users;
12 |
13 | private static PasswordRewriteStrategy instance = null;
14 |
15 | private PasswordRewriteStrategy() {
16 | users = new HashMap();
17 | users.put("developer-email@slidev.org", "test1234");
18 | }
19 |
20 | public static synchronized PasswordRewriteStrategy getInstance() {
21 | if (instance == null) {
22 | instance = new PasswordRewriteStrategy();
23 | }
24 | return instance;
25 | }
26 |
27 | public String generatePassword(String uid) {
28 | String newPwd = users.get(uid);
29 | if (newPwd == null) {
30 | return uid + "1234";
31 | }
32 | return newPwd;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/domain/Root.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory.domain;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.apache.commons.lang3.builder.ToStringStyle;
5 |
6 | import javax.xml.bind.annotation.XmlElement;
7 | import javax.xml.bind.annotation.XmlElementWrapper;
8 | import javax.xml.bind.annotation.XmlRootElement;
9 | import javax.xml.bind.annotation.XmlType;
10 | import java.util.List;
11 |
12 | /**
13 | * Created by tfritz on 1/7/14.
14 | */
15 | @XmlRootElement(name="root")
16 | @XmlType(propOrder = {"objectDn","objectClasses"})
17 | public class Root {
18 | private String objectDn;
19 | private List objectClasses;
20 |
21 |
22 | public String getObjectDn() {
23 | return objectDn;
24 | }
25 |
26 | @XmlElement(name="objectDn", required = true)
27 | public void setObjectDn(String objectDn) {
28 | this.objectDn = objectDn;
29 | }
30 |
31 | public java.util.List getObjectClasses() {
32 | return objectClasses;
33 | }
34 |
35 | @XmlElementWrapper(name="objectClasses", required = true)
36 | @XmlElement(name="objectClass", required = true)
37 | public void setObjectClasses(List objectClasses) {
38 | this.objectClasses = objectClasses;
39 | }
40 |
41 | @Override
42 | public String toString() {
43 | return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/domain/Schema.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory.domain;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.apache.commons.lang3.builder.ToStringStyle;
5 |
6 | import javax.xml.bind.annotation.XmlElement;
7 | import javax.xml.bind.annotation.XmlRootElement;
8 | import javax.xml.bind.annotation.XmlType;
9 |
10 | /**
11 | * Created by tfritz on 1/7/14.
12 | */
13 | @XmlRootElement(name="schema")
14 | @XmlType(propOrder = {"name"})
15 | public class Schema {
16 |
17 | private String name;
18 |
19 | public String getName() {
20 | return name;
21 | }
22 |
23 | @XmlElement(name="name", required = true)
24 | public void setName(String name) {
25 | this.name = name;
26 | }
27 |
28 | @Override
29 | public String toString() {
30 | return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/domain/Server.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory.domain;
2 |
3 | /**
4 | * Encapsulates configuration for the in-memory LDAP server, which is loaded by JAXB and validated against an XSD.
5 | * Created by tfritz on 1/7/14.
6 | */
7 |
8 |
9 | import org.apache.commons.lang3.builder.ToStringBuilder;
10 | import org.apache.commons.lang3.builder.ToStringStyle;
11 |
12 | import javax.xml.bind.annotation.XmlElement;
13 | import javax.xml.bind.annotation.XmlElementWrapper;
14 | import javax.xml.bind.annotation.XmlRootElement;
15 | import javax.xml.bind.annotation.XmlType;
16 | import java.util.List;
17 |
18 | @XmlRootElement(name="server")
19 | @XmlType(propOrder={"bindDn","password","schema","root","entries","listeners","ldifs"})
20 | public class Server {
21 | private String bindDn;
22 | private String password;
23 | private Schema schema;
24 | private Root root;
25 | private List entries;
26 | private List listeners;
27 | private List ldifs;
28 |
29 | public String getBindDn() {
30 | return bindDn;
31 | }
32 |
33 | @XmlElement(name="bindDn", required = true)
34 | public void setBindDn(String bindDn) {
35 | this.bindDn = bindDn;
36 | }
37 |
38 | public String getPassword() {
39 | return password;
40 | }
41 |
42 | @XmlElement(name="password", required = true)
43 | public void setPassword(String password) {
44 | this.password = password;
45 | }
46 |
47 | public Schema getSchema() {
48 | return schema;
49 | }
50 |
51 | @XmlElement(name="schema", required = true)
52 | public void setSchema(Schema schema) {
53 | this.schema = schema;
54 | }
55 |
56 | public Root getRoot() {
57 | return root;
58 | }
59 |
60 | @XmlElement
61 | public void setRoot(Root root) {
62 | this.root = root;
63 | }
64 |
65 | public List getEntries() {
66 | return entries;
67 | }
68 |
69 | @XmlElementWrapper(name="entries", required = true)
70 | @XmlElement(name="entry", required = true)
71 | public void setEntries(List entries) {
72 | this.entries = entries;
73 | }
74 |
75 | public List getListeners() {
76 | return listeners;
77 | }
78 |
79 | @XmlElementWrapper(name="listeners", required = true)
80 | @XmlElement(name="listener", required = true)
81 | public void setListeners(List listeners) {
82 | this.listeners = listeners;
83 | }
84 |
85 | public List getLdifs() {
86 | return ldifs;
87 | }
88 |
89 | @XmlElementWrapper(name="ldifs", required = true)
90 | @XmlElement(name= "ldif", required = true)
91 | public void setLdifs(List ldifs) {
92 | this.ldifs = ldifs;
93 | }
94 |
95 | @Override
96 | public String toString() {
97 | return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/loghandlers/AccessLogHandler.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory.loghandlers;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.apache.commons.lang3.builder.ToStringStyle;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import java.util.logging.Handler;
9 | import java.util.logging.LogRecord;
10 |
11 | /**
12 | * Created by tfritz on 1/13/14.
13 | */
14 | public class AccessLogHandler extends Handler {
15 | private final static Logger LOG = LoggerFactory.getLogger(AccessLogHandler.class);
16 |
17 | @Override
18 | public void publish(LogRecord logRecord) {
19 | LOG.debug(ToStringBuilder.reflectionToString(logRecord, ToStringStyle.MULTI_LINE_STYLE));
20 | }
21 |
22 | @Override
23 | public void flush() {
24 |
25 | }
26 |
27 | @Override
28 | public void close() throws SecurityException {
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/loghandlers/LDAPDebugLogHandler.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory.loghandlers;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.apache.commons.lang3.builder.ToStringStyle;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import java.util.logging.Handler;
9 | import java.util.logging.LogRecord;
10 |
11 | /**
12 | * Created by tfritz on 1/13/14.
13 | */
14 | public class LDAPDebugLogHandler extends Handler {
15 | private final static Logger LOG = LoggerFactory.getLogger(LDAPDebugLogHandler.class);
16 |
17 | @Override
18 | public void publish(LogRecord logRecord) {
19 | LOG.debug(ToStringBuilder.reflectionToString(logRecord, ToStringStyle.MULTI_LINE_STYLE));
20 | }
21 |
22 | @Override
23 | public void flush() {
24 |
25 | }
26 |
27 | @Override
28 | public void close() throws SecurityException {
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/org/slc/sli/ldap/inmemory/utils/ConfigurationLoader.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory.utils;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.apache.commons.lang3.builder.ToStringStyle;
5 | import org.slc.sli.ldap.inmemory.domain.Server;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import javax.xml.XMLConstants;
10 | import javax.xml.bind.JAXBContext;
11 | import javax.xml.bind.Unmarshaller;
12 | import javax.xml.validation.Schema;
13 | import javax.xml.validation.SchemaFactory;
14 | import java.io.File;
15 | import java.io.FileNotFoundException;
16 | import java.net.URL;
17 |
18 | /**
19 | * Created by tfritz on 1/2/14.
20 | *
21 | * http://www.code-thrill.com/2012/05/configuration-that-rocks-with-apache.html
22 | *
23 | */
24 | public class ConfigurationLoader {
25 | private final static Logger LOG = LoggerFactory.getLogger(ConfigurationLoader.class);
26 |
27 | public static Server load(final String configFile, final String configSchemaFile) throws Exception {
28 | LOG.debug(">>>ConfigurationLoader.loadConfiguration()");
29 |
30 | SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
31 | URL schemaUrl = ConfigurationLoader.class.getClassLoader().getResource(configSchemaFile);
32 |
33 | if (schemaUrl == null) {
34 | throw new FileNotFoundException("The config schema file " + configSchemaFile + " was not found on the classpath. This file should exist within the WAR.");
35 | }
36 |
37 | Schema schema = sf.newSchema(new File(schemaUrl.toURI()));
38 | JAXBContext context = JAXBContext.newInstance(Server.class);
39 | Unmarshaller unmarshaller = context.createUnmarshaller();
40 | unmarshaller.setSchema(schema);
41 | URL configUrl = ConfigurationLoader.class.getClassLoader().getResource(configFile);
42 |
43 | if (configUrl == null) {
44 | throw new FileNotFoundException("The config file " + configFile + " was not found on the classpath.");
45 | }
46 |
47 | File file = new File(configUrl.toURI());
48 |
49 | Server server = (Server) unmarshaller.unmarshal(file);
50 |
51 | if (LOG.isDebugEnabled()) {
52 | LOG.debug(ToStringBuilder.reflectionToString(server, ToStringStyle.MULTI_LINE_STYLE));
53 | }
54 |
55 | LOG.debug("<<
2 |
3 |
4 |
5 |
6 |
7 |
8 | If bind_dn is provided the password is assigned.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | If address is null, listen on all available addresses and interfaces.
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | Only one schema per server is allowed. Zero to many LDIF elements are allowed and loaded.
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/src/main/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | InBloom In-Memory LDAP development server
9 |
10 | org.slc.sli.ldap.inmemory.LdapInMemoryContextListener
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/test/java/org/slc/sli/ldap/inmemory/LdapServerSingletonHarness.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory;
2 |
3 | import org.apache.commons.lang3.exception.ExceptionUtils;
4 |
5 | /**
6 | * Harness to run the LDAP sever locally, within the IDE. To shut down the LDAP server, terminate the class/thread.
7 | * Created by tfritz on 1/9/14.
8 | */
9 | public class LdapServerSingletonHarness {
10 | public static void main(String[] args) {
11 | System.out.println(">>>LdapServerSingletonHarness.main()");
12 | try {
13 | LdapServer.getInstance().start();
14 | System.out.println(" startLdapServer entry count: " + LdapServer.getInstance().getInMemoryDirectoryServer().countEntries());
15 | } catch (Exception e) {
16 | System.err.println(ExceptionUtils.getStackTrace(e));
17 | }
18 | System.out.println("<<>>LdapServerTest.startLdapServer()");
29 | try {
30 | LdapServer.getInstance().start();
31 | System.out.println(" startLdapServer entry count: " + LdapServer.getInstance().getInMemoryDirectoryServer().countEntries());
32 | } catch (Exception e) {
33 | System.err.println(ExceptionUtils.getStackTrace(e));
34 | System.exit(1); //if exception occurs on startup exit the tests.
35 | }
36 | System.out.println("<<>>LdapServerTest.testLdapServerStarted()");
45 | boolean isStarted = LdapServer.getInstance().isStarted();
46 | if (!isStarted) {
47 | System.exit(1); //exit if server cannot startup.
48 | }
49 | System.out.println("<<>>LdapServerTest.createLocalLdapConnection()");
58 | boolean result = Boolean.FALSE;
59 | LDAPConnection connection = null;
60 | try {
61 | if (LdapServer.getInstance().getInMemoryDirectoryServer() == null) {
62 | System.out.println(" InMemoryDirectoryServer is NULL...");
63 | }
64 | connection = LdapServer.getInstance().getInMemoryDirectoryServer().getConnection();
65 | result = Boolean.TRUE;
66 | } catch (LDAPException lse) {
67 | System.out.println(" message = " + lse.getMessage());
68 | System.out.println(" exceptionMessage = " + lse.getExceptionMessage());
69 | System.out.println(" diagnosticMessage = " + lse.getDiagnosticMessage());
70 | System.out.println(" resultCode = " + lse.getResultCode());
71 | System.out.println(" errorMessageFromServer = +" + lse.getDiagnosticMessage());
72 | System.err.println(ExceptionUtils.getStackTrace(lse));
73 | result = Boolean.FALSE;
74 | } finally {
75 | if (connection != null && connection.isConnected()) {
76 | connection.close();
77 | }
78 | }
79 | Assert.assertTrue(result);
80 | System.out.println("<<>>LdapServerTest.getRootDSE()");
89 | boolean result = Boolean.FALSE;
90 | LDAPConnection connection = null;
91 | try {
92 | int entryCount = LdapServer.getInstance().getInMemoryDirectoryServer().countEntries();
93 |
94 | System.out.println(" getRootDSEentry count: " + entryCount);
95 | Assert.assertEquals(LdapServerTest.ROOT_ENTITY_COUNT, entryCount);
96 |
97 | /* Establish a secure connection using the socket factory. */
98 | connection = LdapServer.getInstance().getInMemoryDirectoryServer().getConnection();
99 | RootDSE rootDSE = connection.getRootDSE();
100 | String rootDseToString = ToStringBuilder.reflectionToString(rootDSE, ToStringStyle.SHORT_PREFIX_STYLE);
101 |
102 | System.out.println(" rootDSE: " + rootDseToString);
103 | Assert.assertTrue(!StringUtils.isEmpty(rootDseToString));
104 | result = Boolean.TRUE;
105 | } catch (Exception e) {
106 | System.err.println(ExceptionUtils.getStackTrace(e));
107 | result = Boolean.FALSE;
108 | } finally {
109 | if (connection != null && connection.isConnected()) {
110 | connection.close();
111 | }
112 | }
113 | Assert.assertTrue(result);
114 | System.out.println("<<>>LdapServerTest.stopLdapServer()");
265 | LdapServer.getInstance().stop();
266 | System.out.println("<< attributes = new ArrayList();
46 | String attr1 = "root Object Class 1";
47 | String attr2 = "root Object Class 2";
48 | attributes.add(attr1);
49 | attributes.add(attr2);
50 | Assert.assertTrue(attributes.size() == 2);
51 | Assert.assertTrue(attributes.contains(attr1));
52 | Assert.assertTrue(attributes.contains(attr2));
53 | root.setObjectClasses(attributes);
54 | server.setRoot(root);
55 | Assert.assertTrue(root.getObjectClasses().size()== 2);
56 | Assert.assertTrue(server.getRoot() != null);
57 | Assert.assertEquals(root, server.getRoot());
58 | String rootToString = "Root[objectDn=some root dn,objectClasses=[root Object Class 1, root Object Class 2]]";
59 | Assert.assertEquals(server.getRoot().toString(), rootToString);
60 |
61 | /* Test LDIF. */
62 | List ldifs = new ArrayList();
63 | String ldif_name1 = "ldif/ldif-export-file1.ldif";
64 | Ldif ldif1 = new Ldif();
65 | ldif1.setName(ldif_name1);
66 | Assert.assertEquals(ldif1.getName(), ldif_name1);
67 | String ldif_name2 = "ldif/ldif-export-file2.ldif";
68 | Ldif ldif2 = new Ldif();
69 | ldif2.setName(ldif_name2);
70 | Assert.assertEquals(ldif2.getName(), ldif_name2);
71 | String ldif_name3 = "ldif/ldif-export-file3.ldif";
72 | Ldif ldif3 = new Ldif();
73 | ldif3.setName(ldif_name3);
74 | Assert.assertEquals(ldif3.getName(), ldif_name3);
75 | ldifs.add(ldif1);
76 | ldifs.add(ldif2);
77 | ldifs.add(ldif3);
78 | Assert.assertTrue(ldifs.size() == 3);
79 | server.setLdifs(ldifs);
80 | Assert.assertTrue(server.getLdifs() != null);
81 | Assert.assertEquals(ldifs, server.getLdifs());
82 | Assert.assertTrue(server.getLdifs().size() == 3);
83 | Map ldifToStringMap = new HashMap();
84 | ldifToStringMap.put(1, "Ldif[name=ldif/ldif-export-file1.ldif]");
85 | ldifToStringMap.put(2, "Ldif[name=ldif/ldif-export-file2.ldif]");
86 | ldifToStringMap.put(3, "Ldif[name=ldif/ldif-export-file3.ldif]");
87 | int index = 0;
88 | for (Ldif ldif : server.getLdifs()) {
89 | index++;
90 | Assert.assertEquals(ldif.toString(), ldifToStringMap.get(index));
91 | }
92 |
93 | /* Test Entries. */
94 | List entries = new ArrayList();
95 | Entry entry1 = new Entry();
96 | String entryDn1 = "Some entry DN 1";
97 | entry1.setObjectDn(entryDn1);
98 | Assert.assertEquals(entry1.getObjectDn(), entryDn1);
99 | List entry1_attributes = new ArrayList();
100 | String entry1_attr1 = "entry 1 Object Class 1";
101 | String entry1_attr2 = "entry 1 Object Class 2";
102 | entry1_attributes.add(entry1_attr1);
103 | entry1_attributes.add(entry1_attr2);
104 | Assert.assertTrue(entry1_attributes.size() == 2);
105 | Assert.assertTrue(entry1_attributes.contains(entry1_attr1));
106 | Assert.assertTrue(entry1_attributes.contains(entry1_attr2));
107 | entry1.setObjectClasses(entry1_attributes);
108 | Assert.assertTrue(entry1.getObjectClasses().size() == 2);
109 | entries.add(entry1);
110 | Entry entry2 = new Entry();
111 | String entryDn2 = "Some entry DN 2";
112 | entry2.setObjectDn(entryDn2);
113 | Assert.assertEquals(entry2.getObjectDn(), entryDn2);
114 | Collection entry2_attributes = new ArrayList();
115 | String entry2_attr1 = "entry 2 Object Class 1";
116 | String entry2_attr2 = "entry 2 Object Class 2";
117 | entry2_attributes.add(entry2_attr1);
118 | entry2_attributes.add(entry2_attr2);
119 | Assert.assertTrue(entry2_attributes.size() == 2);
120 | Assert.assertTrue(entry2_attributes.contains(entry2_attr1));
121 | Assert.assertTrue(entry2_attributes.contains(entry2_attr2));
122 | entry2.setObjectClasses(entry1_attributes);
123 | Assert.assertTrue(entry2.getObjectClasses().size() == 2);
124 | entries.add(entry2);
125 | Assert.assertTrue(entries.size() == 2);
126 | server.setEntries(entries);
127 | Assert.assertTrue(server.getEntries().size() == 2);
128 | Map entryToStringMap = new HashMap();
129 | entryToStringMap.put(1, "Entry[objectDn=Some entry DN 1,objectClasses=[entry 1 Object Class 1, entry 1 Object Class 2]]");
130 | entryToStringMap.put(2, "Entry[objectDn=Some entry DN 2,objectClasses=[entry 1 Object Class 1, entry 1 Object Class 2]]");
131 | index = 0;
132 | for (Entry entry : server.getEntries()) {
133 | index++;
134 | Assert.assertEquals(entry.toString(), entryToStringMap.get(index));
135 | }
136 |
137 | /* Test Listeners. */
138 | List listeners = new ArrayList();
139 | Listener listener1 = new Listener();
140 | String listener1Name = "listener 1 name";
141 | String listener1Address = "listener 1 address";
142 | String listener1Port = "1001";
143 | listener1.setName(listener1Name);
144 | listener1.setAddress(listener1Address);
145 | listener1.setPort(listener1Port);
146 | Assert.assertEquals(listener1.getName(), listener1Name);
147 | Assert.assertEquals(listener1.getAddress(), listener1Address);
148 | Assert.assertEquals(listener1.getPort(), listener1Port);
149 | listeners.add(listener1);
150 | Listener listener2 = new Listener();
151 | String listener2Name = "listener 2 name";
152 | String listener2Address = "listener 2 address";
153 | String listener2Port = "1002";
154 | listener2.setName(listener2Name);
155 | listener2.setAddress(listener2Address);
156 | listener2.setPort(listener2Port);
157 | Assert.assertEquals(listener2.getName(), listener2Name);
158 | Assert.assertEquals(listener2.getAddress(), listener2Address);
159 | Assert.assertEquals(listener2.getPort(), listener2Port);
160 | listeners.add(listener2);
161 | Assert.assertTrue(listeners.size() == 2);
162 | server.setListeners(listeners);
163 | Assert.assertTrue(server.getListeners().size() == 2);
164 | Map listenerToStringMap = new HashMap();
165 | listenerToStringMap.put(1, "Listener[name=listener 1 name,port=1001,address=listener 1 address]");
166 | listenerToStringMap.put(2, "Listener[name=listener 2 name,port=1002,address=listener 2 address]");
167 | index = 0;
168 | for (Listener listener : server.getListeners()) {
169 | index++;
170 | Assert.assertEquals(listener.toString(), listenerToStringMap.get(index));
171 | }
172 |
173 | String serverToString = "Server[bindDn=some bind dn,password=some bind dn related password,schema=Schema[name=some schema name]," +
174 | "root=Root[objectDn=some root dn,objectClasses=[root Object Class 1, root Object Class 2]],entries=[Entry[objectDn=Some entry DN 1," +
175 | "objectClasses=[entry 1 Object Class 1, entry 1 Object Class 2]], Entry[objectDn=Some entry DN 2,objectClasses=[entry 1 Object Class " +
176 | "1, entry 1 Object Class 2]]],listeners=[Listener[name=listener 1 name,port=1001,address=listener 1 address], Listener[name=listener" +
177 | " 2 name,port=1002,address=listener 2 address]],ldifs=[Ldif[name=ldif/ldif-export-file1.ldif], Ldif[name=ldif/ldif-export-file2.ldif], " +
178 | "Ldif[name=ldif/ldif-export-file3.ldif]]]";
179 |
180 | Assert.assertEquals(server.toString(), serverToString);
181 |
182 | // TODO validateJaxbMarshalledOutput; throws an exception trying to marshall the object. This does not affect feature implementation.
183 |
184 | // try {
185 | // validateJaxbMarshalledOutput(server);
186 | // } catch (Exception e) {
187 | // System.err.println(ExceptionUtils.getStackTrace(e));
188 | // Assert.assertTrue(false); //fail the test.
189 | // }
190 | }
191 |
192 | // private void validateJaxbMarshalledOutput(final Server server) throws Exception {
193 | // SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
194 | // URL schemaUrl = ConfigurationLoader.class.getClassLoader().getResource(LdapServerImpl.CONFIG_SCHEMA_FILE);
195 | // javax.xml.validation.Schema schema = sf.newSchema(new File(schemaUrl.toURI()));
196 | // JAXBContext context = JAXBContext.newInstance(Server.class);
197 | // Marshaller marshaller = context.createMarshaller();
198 | // marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
199 | // marshaller.setSchema(schema);
200 | // marshaller.marshal(server, System.out);
201 | // }
202 |
203 | }
204 |
--------------------------------------------------------------------------------
/src/test/java/org/slc/sli/ldap/inmemory/utils/ConfigurationLoaderTest.java:
--------------------------------------------------------------------------------
1 | package org.slc.sli.ldap.inmemory.utils;
2 |
3 | import junit.framework.Assert;
4 | import org.apache.commons.lang3.exception.ExceptionUtils;
5 | import org.junit.Test;
6 | import org.slc.sli.ldap.inmemory.LdapServerImpl;
7 | import org.slc.sli.ldap.inmemory.domain.Server;
8 |
9 |
10 | /**
11 | * Created by tfritz on 1/2/14.
12 | */
13 | public class ConfigurationLoaderTest {
14 | @Test
15 | public void loadConfiguration() {
16 | System.out.println(">>>ConfigurationLoaderTest.loadConfiguration()");
17 | try {
18 | String configFile = LdapServerImpl.CONFIG_FILE;
19 | String schemaFile = LdapServerImpl.CONFIG_SCHEMA_FILE;
20 | System.out.println(" configFile: " + configFile);
21 | System.out.println(" schemaFile: " + schemaFile);
22 | Server config = ConfigurationLoader.load(configFile, schemaFile);
23 | } catch (Exception e) {
24 | System.err.println(ExceptionUtils.getStackTrace(e));
25 | Assert.assertTrue(false); //fail test
26 | }
27 | System.out.println("<< ldifFile;
27 |
28 | private boolean FILTER_USERS = true;
29 | private Set INCLUDE_USERS = new LinkedHashSet();
30 |
31 | public enum CliParams {
32 | HELP("help"),
33 | FILE_NAME("f"),
34 | SOURCE_DIR("s"),
35 | OUTPUT_DIR("o"),
36 | DEBUG("debug"),
37 | OBFUSCATE_PASSWORDS("obfuscate_passwords"),
38 | SUFFIX("suffix");
39 |
40 | private String name;
41 |
42 | private CliParams(final String name) {
43 | this.name = name;
44 | }
45 |
46 | public String getName() {
47 | return name;
48 | }
49 | }
50 |
51 | public enum SectionTypeEnum {
52 | PEOPLE,
53 | GROUP,
54 | OTHER;
55 | }
56 |
57 | public class Section implements Serializable {
58 | public String key;
59 | public List lines = new ArrayList();
60 | }
61 |
62 |
63 | public Options buildOptions() {
64 | Options options = new Options();
65 |
66 | options.addOption(CliParams.HELP.getName(), null, false, "Show help");
67 |
68 | options.addOption(CliParams.DEBUG.getName(), null, false, "Show additional debug info.");
69 |
70 | options.addOption(CliParams.OBFUSCATE_PASSWORDS.getName(), null, false, "Obfuscate passwords using Linda Kim.");
71 |
72 | options.addOption(OptionBuilder.withLongOpt(CliParams.FILE_NAME.getName())
73 | .withDescription("The LDIF file name.")
74 | .hasArg()
75 | .withArgName("File Name")
76 | .create());
77 |
78 | options.addOption(OptionBuilder.withLongOpt(CliParams.SOURCE_DIR.getName())
79 | .withDescription("The fully qualified source directory.")
80 | .hasArg()
81 | .withArgName("source directory")
82 | .create());
83 |
84 |
85 | options.addOption(OptionBuilder.withLongOpt(CliParams.OUTPUT_DIR.getName())
86 | .withDescription("The fully qualified output directory.")
87 | .hasArg()
88 | .withArgName("output directory")
89 | .create());
90 |
91 | options.addOption(OptionBuilder.withLongOpt(CliParams.SUFFIX.getName())
92 | .withDescription("The suffix to append to the filename when saving to output directory.")
93 | .hasArg()
94 | .withArgName("file suffix")
95 | .create());
96 |
97 | return options;
98 | }
99 |
100 | public boolean stringContainsUser(final String line) {
101 | if (StringUtils.isEmpty(line)) {
102 | return false;
103 | } else {
104 | Iterator iter = INCLUDE_USERS.iterator();
105 | while (iter.hasNext()) {
106 | String val = (String)iter.next();
107 | if (line.contains(val)) {
108 | return true;
109 | }
110 | }
111 | }
112 | return false;
113 | }
114 |
115 | public boolean stringEndsWithUser(final String line) {
116 | if (StringUtils.isEmpty(line)) {
117 | return false;
118 | } else {
119 | Iterator iter = INCLUDE_USERS.iterator();
120 | while (iter.hasNext()) {
121 | String val = (String)iter.next();
122 | if (line.endsWith(val)) {
123 | return true;
124 | }
125 | }
126 | }
127 | return false;
128 | }
129 |
130 | public SectionTypeEnum detectSectionType(final String sectionHeader) {
131 | if (StringUtils.isEmpty(sectionHeader)) {
132 | System.out.println("Unable to identify section type");
133 | //System.exit(0);
134 | return SectionTypeEnum.OTHER;
135 | }
136 | if (sectionHeader.contains("ou=people")) {
137 | return SectionTypeEnum.PEOPLE;
138 | }
139 | if (sectionHeader.contains("ou=groups")) {
140 | return SectionTypeEnum.GROUP;
141 | }
142 | System.out.println("Unable to identify section type: " + sectionHeader);
143 | return SectionTypeEnum.OTHER;
144 | }
145 |
146 | /**
147 | * Parse the LDIF input file into sections.
148 | * @param inputFile
149 | * @return
150 | * @throws FileNotFoundException
151 | * @throws IOException
152 | */
153 | public List readLdifIntoList(final String inputFile) throws FileNotFoundException, IOException {
154 | System.out.println(">>>LdifAdjustor.readLdifIntoList()");
155 | List output = new LinkedList();
156 |
157 | FileInputStream fstream = new FileInputStream(inputFile);
158 | DataInputStream in = new DataInputStream(fstream);
159 | BufferedReader br = new BufferedReader(new InputStreamReader(in));
160 | String line;
161 |
162 | while ((line = br.readLine()) != null) {
163 | output.add(line);
164 | }
165 |
166 | System.out.println("<<.
173 | * @return
174 | * @throws FileNotFoundException
175 | * @throws IOException
176 | */
177 | public List parseLdif(final List ldif) throws FileNotFoundException, IOException {
178 | System.out.println(">>>LdifAdjustor.parseLdif()");
179 |
180 | List output = new LinkedList();
181 |
182 | int fileLineCount = 0;
183 | int sectionLineCount = 0; //counter to track # of lines
184 | Section section = new Section();
185 |
186 | boolean hasSection = false; //this boolean is used to skip blank lines at the beginning of an LDIF, should they exist.
187 | boolean beginNewSection = true; //boolean used to control logic to begin a new section.
188 |
189 | for (String line : ldif) {
190 | //a blank link indicates that a new section should begin.
191 | //the blank line will be saved at the end of a section (for when file is created from array).
192 |
193 | if (StringUtils.isEmpty(line)) {
194 |
195 | //empty line detected, which means finish the existing section, add to output, and start a new section.
196 | //BUT, if the parser has not yet built a section (e.g. beginning of file, then skip these lines.
197 | if (hasSection) {
198 | sectionLineCount++;
199 | section.lines.add(line);
200 | //save existing section and start a new one.
201 | if (StringUtils.isEmpty(section.key)) {
202 | section.key = section.lines.get(0);
203 | System.out.println("...setting sectionHeader to: " + section.key);
204 | }
205 | if (StringUtils.isEmpty(section.key)) {
206 | System.out.println("ADDED EMPTY LINE AS KEY..."); //you know what this means for a Map... output.put(sectionHeader, new ArrayList(section));
207 | System.exit(0); //examine LDIF and fix, or revise this utility.
208 | }
209 | System.out.println("...Adding section with " + section.lines.size() + " lines."); //you know what this means for a Map...
210 | output.add(section);
211 |
212 | beginNewSection = true;
213 | } else {
214 | //skip empty lines at start of file.
215 | System.out.println("...SKIPPING empty line at start of LDIF...");
216 | }
217 |
218 | } else {
219 | //the first non-empty string will fall here, as will additions to an existing section.
220 | hasSection = true;
221 |
222 | sectionLineCount++;
223 | fileLineCount++;
224 |
225 | //handle new section first.
226 | if (beginNewSection) {
227 | beginNewSection = false;
228 | section = new Section();
229 | section.lines.add(line);
230 | sectionLineCount = 1;
231 | section.key = line;
232 | } else {
233 | section.lines.add(line);
234 | }
235 |
236 | }
237 | }
238 |
239 | System.out.println("# of lines in LDIF file: " + fileLineCount);
240 | System.out.println("<< sections) {
250 | System.out.println(">>>LdifAdjustor.showSections()");
251 | System.out.println("# of entries: " + sections.size());
252 | for (Section section : sections) {
253 | for (String ln : section.lines) {
254 | System.out.println(ln);
255 | }
256 | }
257 | System.out.println("<< processGroups(List sections) {
266 | System.out.println(">>>LdifAdjustor.processGroups()");
267 |
268 | final List output = new LinkedList();
269 |
270 | //iterate through each group and perform required
271 | for (Section section : sections) {
272 | Section newSection = new Section();
273 | newSection.key = section.key;
274 | newSection.lines = section.lines;
275 |
276 | SectionTypeEnum sectionType = detectSectionType(newSection.key);
277 | if (sectionType == SectionTypeEnum.GROUP) {
278 | System.out.println(newSection.key);
279 | System.out.println(" " + sectionType);
280 | //remove memberUid entry if it does not contain one of the users specified in the white list
281 | System.out.println(" lines: " + section.lines.size());
282 |
283 | final List newLines = new LinkedList();
284 |
285 | for (String s : section.lines) {
286 | if (StringUtils.isEmpty(s)) {
287 | newLines.add(s);
288 | } else if (s.startsWith("memberUid:")) {
289 | if (stringContainsUser(s)) {
290 | newLines.add(s);
291 | } else {
292 | System.out.println("Removing group memberUid entry: " + s);
293 | }
294 | } else {
295 | newLines.add(s);
296 | }
297 |
298 | }
299 |
300 | newSection.lines = newLines;
301 |
302 | }
303 |
304 | System.out.println(" newSection lines: " + section.lines.size());
305 | output.add(newSection);
306 | }
307 |
308 | for (Section section : output) {
309 | System.out.println("key: " + section.key);
310 | System.out.println(" # of lines: " + section.lines.size());
311 | }
312 |
313 | System.out.println("<< processPeople(List sections) {
324 | List output = new LinkedList();
325 |
326 | System.out.println(">>>LdifAdjustor.processPeople()");
327 | //iterate through each person and perform required
328 | for (Section section : sections) {
329 |
330 | SectionTypeEnum sectionType = detectSectionType(section.key);
331 | if (sectionType == SectionTypeEnum.PEOPLE) {
332 | System.out.println(section.key);
333 | System.out.println(" " + sectionType);
334 |
335 | //if this section does not belong to a whitelist user then remove it.
336 | boolean removeUser = false;
337 | System.out.println(" lines: " + section.lines.size());
338 |
339 | for (String s : section.lines) {
340 | if (!StringUtils.isEmpty(s) && s.startsWith("uid: ")) {
341 | removeUser = true;
342 | System.out.println(" uid -- " + s);
343 | if (stringContainsUser(s)) {
344 | if (stringEndsWithUser(s)) {
345 | System.out.println("...keep user: " + s);
346 | removeUser = false;
347 | }
348 | }
349 | }
350 | }
351 |
352 | if (removeUser) {
353 | System.out.println("Excluding person section for user: " + section.key);
354 | } else {
355 | output.add(section);
356 | }
357 | } else {
358 | //keep unidentified sections; this code adds them.
359 | output.add(section);
360 | }
361 |
362 | }
363 |
364 | System.out.println("<< processAllSections(List sections) {
375 | List output = new LinkedList();
376 |
377 | System.out.println(">>>LdifAdjustor.processAllSections()");
378 | //iterate through all sections and section items
379 | for (Section section : sections) {
380 | Section newSection = new Section();
381 | newSection.key = section.key;
382 |
383 | boolean containsDN = false;
384 | boolean addedChangeType = false;
385 |
386 | for (String s : section.lines) {
387 | if (s.startsWith("dn: ")) {
388 | containsDN = true;
389 | }
390 | //exclude entryCSN lines
391 | if (s.startsWith("entryCSN") ) { //&& !strLine.startsWith("hasSubordinates")) {
392 | System.out.println("skipping entryCSN...");
393 | } else {
394 | //changetype needs to be 2nd entry after dn, which can span multiple lines but usually
395 | //preceedes the objectClass. The rules are the section MUST begin with DN.
396 | if (s.startsWith("objectClass:") && containsDN && !addedChangeType) {
397 | newSection.lines.add("changetype: add");
398 | addedChangeType = true;
399 | }
400 | newSection.lines.add(s);
401 | }
402 | }
403 |
404 | output.add(newSection);
405 |
406 | }
407 |
408 | System.out.println("<< replaceUserPasswordEntries(List sections, String userPasswordLine) {
425 | List output = new LinkedList();
426 |
427 | if (StringUtils.isEmpty(userPasswordLine)) {
428 | return sections;
429 | }
430 |
431 | System.out.println(">>>LdifAdjustor.replaceUserPasswordEntries()");
432 |
433 | //iterate through all sections and section items
434 | for (Section section : sections) {
435 | Section newSection = new Section();
436 | newSection.key = section.key;
437 |
438 | //indicator to track when pw line is found (use recursion in future if more robust impl needed).
439 | boolean currentAttributeIsUserPassword = false;
440 |
441 | for (String s : section.lines) {
442 | //exclude entryCSN lines
443 | if (s.startsWith(LdifAdjustor.PASSWORD_ENTRY)) {
444 | newSection.lines.add(userPasswordLine);
445 | currentAttributeIsUserPassword = true;
446 | } else if (currentAttributeIsUserPassword) {
447 | if (s.startsWith(" ")) {
448 | System.out.println(" removing line wrap for userPassword");
449 | } else {
450 | currentAttributeIsUserPassword = false;
451 | newSection.lines.add(s);
452 | }
453 | } else {
454 | newSection.lines.add(s);
455 | }
456 | }
457 |
458 | output.add(newSection);
459 |
460 | }
461 |
462 | System.out.println("<< sections, String uid) {
474 | String userPasswordLine = null;
475 |
476 | System.out.println(">>>LdifAdjustor.findPasswordForUser()");
477 | //iterate through each person and perform required
478 | for (Section section : sections) {
479 |
480 | SectionTypeEnum sectionType = detectSectionType(section.key);
481 | if (sectionType == SectionTypeEnum.PEOPLE) {
482 | userPasswordLine = null;
483 | boolean foundUser = false;
484 | boolean foundUserPassword = false;
485 |
486 | for (String s : section.lines) {
487 | //uid should be before password, but in case it is not check for them individually.
488 | if (!StringUtils.isEmpty(s) && s.startsWith("uid: ")) {
489 | if (s.endsWith(uid)) {
490 | System.out.println(" Found uid for: " + uid);
491 | foundUser = true;
492 | }
493 | }
494 |
495 | if (!StringUtils.isEmpty(s) && s.startsWith(LdifAdjustor.PASSWORD_ENTRY)) {
496 | userPasswordLine = s;
497 | foundUserPassword = true;
498 | }
499 | }
500 |
501 | if (foundUser) {
502 | System.out.println(" Found entry for user: " + uid);
503 | if (foundUserPassword) {
504 | System.out.println(" Found password for user: " + userPasswordLine);
505 | return userPasswordLine;
506 | }
507 | }
508 | }
509 |
510 | }
511 |
512 | System.out.println(" userPassword not found for uid: " + uid);
513 | System.out.println("<<>>Starting main()");
527 | LdifAdjustor impl = new LdifAdjustor();
528 | impl.run(args);
529 | System.out.println("<<>>Starting LdifAdjustor()");
539 |
540 | final String defaultSuffix = ".modified";
541 |
542 | boolean debugEnabled = false;
543 | boolean obfuscatePasswords = false;
544 |
545 | CommandLineParser parser = new BasicParser();
546 |
547 | List parsedLdifFile = null;
548 |
549 | try {
550 | // parse the command line arguments
551 | Options options = buildOptions();
552 | CommandLine line = parser.parse(options, args);
553 |
554 | /* If help switch is provided, show help and exit. */
555 | if(line.hasOption(CliParams.HELP.getName())) {
556 | HelpFormatter formatter = new HelpFormatter();
557 | formatter.printHelp(LdifAdjustor.class.getName(), options);
558 | return;
559 | }
560 |
561 | if (line.hasOption(CliParams.DEBUG.getName())) {
562 | debugEnabled = true;
563 | }
564 |
565 | if (line.hasOption(CliParams.OBFUSCATE_PASSWORDS.getName())) {
566 | obfuscatePasswords = true;
567 | }
568 |
569 | final String fileName = line.getOptionValue(CliParams.FILE_NAME.getName());
570 | final String sourcePath = line.getOptionValue(CliParams.SOURCE_DIR.getName());
571 | final String outputPath = line.getOptionValue(CliParams.OUTPUT_DIR.getName());
572 | String suffix = defaultSuffix;
573 | if (line.hasOption(CliParams.SUFFIX.getName())) {
574 | suffix = line.getOptionValue(CliParams.SUFFIX.getName());
575 | }
576 |
577 | final String inputFile = sourcePath + File.separator + fileName;
578 | final String outputFile = outputPath + File.separator + fileName + "." + suffix;
579 |
580 | System.out.println("inputFile: " + inputFile);
581 | System.out.println("outputFile: " + outputFile);
582 |
583 | if (FILTER_USERS) {
584 | INCLUDE_USERS = LdifAdjustor.readUserIncludeConfig(sourcePath + File.separator + LdifAdjustor.INCLUDE_USERS_CONFIG);
585 | for (String user : INCLUDE_USERS) {
586 | System.out.println(" including user: " + user);
587 | }
588 | }
589 |
590 | /* Load the LDIF file into a String array. */
591 | ldifFile = readLdifIntoList(inputFile);
592 |
593 | //Parse the LDIF into sections.
594 | parsedLdifFile = parseLdif(this.ldifFile);
595 |
596 | if (debugEnabled) {
597 | showSections(parsedLdifFile);
598 | }
599 |
600 | //Perform group level processing
601 | parsedLdifFile = processGroups(parsedLdifFile);
602 | System.out.println("# of sections after group processing: " + parsedLdifFile.size());
603 |
604 | //perform person(people node) level processing.
605 | //TODO improve impl to register observers to process each line item accordingly.
606 | parsedLdifFile = processPeople(parsedLdifFile);
607 | System.out.println("# of sections after people processing: " + parsedLdifFile.size());
608 |
609 | //perform processing across all sections.
610 | parsedLdifFile = processAllSections(parsedLdifFile);
611 |
612 |
613 | if (obfuscatePasswords) {
614 | /* perform password processing, which replaces existing userPassword values within an LDIF
615 | with that from a common user. Note: The FIRST occurrence of the user that is found is what
616 | will be used. */
617 | //TODO parameterize the target user.
618 | String uid = "linda.kim";
619 | String userPasswordLine = this.findPasswordForUser(parsedLdifFile, uid);
620 | //iterate through people sections and replace the userPasswordLine (if it is not null)
621 | parsedLdifFile = this.replaceUserPasswordEntries(parsedLdifFile, userPasswordLine);
622 | }
623 |
624 | System.out.println("writing output file..." + outputFile);
625 | try {
626 | saveFile(outputFile, parsedLdifFile);
627 | System.out.println("...completed");
628 | } catch (FileNotFoundException e) {
629 | System.out.println(ExceptionUtils.getStackTrace(e));
630 | }
631 |
632 | } catch (Exception e) {//Catch exception if any
633 | System.err.println("Error: " + e.getMessage());
634 | }
635 |
636 | System.out.println("<< readUserIncludeConfig(final String name) throws FileNotFoundException, IOException {
648 | Set usernames = new LinkedHashSet(255);
649 | FileInputStream fstream = new FileInputStream(name); //throws FileNotFoundException
650 | DataInputStream in = new DataInputStream(fstream);
651 | BufferedReader br = new BufferedReader(new InputStreamReader(in));
652 | String line;
653 |
654 | while ((line = br.readLine()) != null) {
655 | usernames.add(line);
656 | }
657 |
658 | in.close();
659 | return usernames;
660 | }
661 |
662 |
663 | public void saveFile(String fileName, List sections) throws FileNotFoundException {
664 | System.out.println(">>>LdifAdjustor.saveFile()");
665 |
666 | try {
667 | BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
668 |
669 | for (Section section : sections) {
670 | for (String s : section.lines) {
671 | writer.write(s);
672 | writer.newLine();
673 | writer.flush();
674 | }
675 | }
676 | writer.close();
677 | } catch (IOException ex) {
678 | ex.printStackTrace();
679 | }
680 | System.out.println("<<
2 |
3 | cn=Admin,dc=slidev,dc=org
4 |
5 | test1234
6 |
7 | ldif/ldap-schema.ldif
8 |
9 |
10 | dc=slidev,dc=org
11 |
12 | domain
13 | top
14 |
15 |
16 |
24 |
25 |
26 | LDAP
27 |
28 |
29 | 10389
30 |
31 |
32 |
33 |
34 |
35 |
44 |
45 | ldif/2014_01_16_ldap_export.ldif.modified
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/test/resources/ldif/original/include_users.txt:
--------------------------------------------------------------------------------
1 | administrator
2 | akopel
3 | anotherfakerealmadmin
4 | anothersandboxdeveloper
5 | baduser
6 | carmen.ortiz
7 | cee.gray
8 | cegray
9 | cgray
10 | cgrayadmin
11 | cgraycgray1234
12 | charles.gray
13 | chartereducator
14 | ckoch
15 | demo
16 | developer
17 | fakerealmadmin
18 | ignatio.ortiz
19 | iladmin_adminApp
20 | iladmin
21 | ingestionuser
22 | jjackson
23 | johndoe
24 | jpratt
25 | jstevenson
26 | jvasquez
27 | jwashington
28 | leader.m.sollars
29 | leader
30 | linda.kim
31 | linda.kimadmin
32 | llogan
33 | manthony
34 | marsha.sollars
35 | mee.tran
36 | mgonzales
37 | miha.tran
38 | mjohnson
39 | mmagic
40 | morion
41 | msmith
42 | operator
43 | rbraverman
44 | rlindsey
45 | rrogers
46 | rrogersAppAuth
47 | rrogerslimitedwrite
48 | rrogersrrogers1234
49 | sandboxadministrator
50 | sandboxdeveloper
51 | sandboxingestionuser
52 | sandboxoperator
53 | sbantu
54 | slcdeveloper
55 | slcoperator
56 | student.m.sollars
57 | stweed
58 | sunsetadmin
59 | sunsetrealmadmin
60 | unprovisioned_sunset_admin
61 | Iforgotmyusername
62 | InvalidJohnDoe
63 | Jwashington
64 | admintest-developer@slidev.org
65 | agibbs
66 | badadmin
67 | charteradmin
68 | daybreakadmin
69 | daybreaknorealmadmin
70 | developer-email@slidev.org
71 | jdoe
72 | jmacey
73 | mabernathy
74 | mario.sanchez
75 | nyadmin
76 | rbelding
77 | slcdeveloper
78 | slcoperator-email@slidev.org
79 | swood
80 | tcuyper
81 | wronguser
82 | xbell
83 | devldapuser
84 | e2etestsea
85 | e2etestlea
86 | e2echarter
87 | e2etestdev
88 | e2etestdev_sb
89 | e2etestdev_sb2
90 | mreynolds
91 | sunsetingestionuser
92 | sandboxslcoperator
93 | Ingestion_User7
94 | SLC_Operator2
95 | SEA_Administrator2
96 | LEA_Administrator2
97 | Realm_Administrator2
98 | Ingestion_User2
99 | LEA_Administrator19
100 | LEA_Administrator17
101 | SLC_Operator3
102 | SEA_Administrator3
103 | LEA_Administrator3
104 | Realm_Administrator3
105 | Ingestion_User3
106 | SLC_Operator4
107 | SEA_Administrator4
108 | Realm_Administrator4
109 | Ingestion_User4
110 | SLC_Operator5
111 | SEA_Administrator5
112 | LEA_Administrator5
113 | Realm_Administrator5
114 | SLC_Operator6
115 | SLC_Operator7
116 | SEA_Administrator7
117 | LEA_Administrator7
118 | Realm_Administrator7
119 | Ingestion_User7
120 | SLC_Operator8
121 | SEA_Administrator8
122 | LEA_Administrator8
123 | Realm_Administrator8
124 | Ingestion_User8
125 | SEA_Administrator9
126 | Realm_Administrator9
127 | Ingestion_User9
128 | SLC_Operator10
129 | SEA_Administrator10
130 | LEA_Administrator10
131 | Realm_Administrator10
132 | Ingestion_User10
133 | SLC_Operator11
134 | SEA_Administrator11
135 | LEA_Administrator11
136 | Realm_Administrator11
137 | Ingestion_User11
138 | homerSimpson
139 | homerThePoet
140 | LEA_Administrator18
141 | Sandbox_SLC_Operator2
142 | Sandbox_Administrator2
143 | Application_Developer2
144 | Ingestion_User2
145 | Sandbox_SLC_Operator3
146 | Sandbox_Administrator3
147 | Application_Developer3
148 | Ingestion_User3
149 | Sandbox_SLC_Operator4
150 | Sandbox_Administrator4
151 | Application_Developer4
152 | Ingestion_User4
153 | Sandbox_SLC_Operator2
154 | Sandbox_Administrator2
155 | Application_Developer2
156 | Ingestion_User2
157 | Sandbox_SLC_Operator3
158 | Sandbox_Administrator3
159 | Application_Developer3
160 | Ingestion_User3
161 | Sandbox_SLC_Operator4
162 | Sandbox_Administrator4
163 | Application_Developer4
164 | Ingestion_User4
--------------------------------------------------------------------------------
/src/test/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------