001package com.nimbusds.openid.connect.provider.spi.claims.ldap;
002
003
004import java.util.Properties;
005
006import org.apache.logging.log4j.LogManager;
007import org.apache.logging.log4j.Logger;
008
009import com.unboundid.ldap.sdk.DN;
010import com.unboundid.ldap.sdk.LDAPException;
011import com.unboundid.ldap.sdk.SearchScope;
012
013import com.thetransactioncompany.util.PropertyParseException;
014import com.thetransactioncompany.util.PropertyRetriever;
015
016import com.nimbusds.common.config.ConfigurationException;
017import com.nimbusds.common.config.CustomKeyStoreConfiguration;
018import com.nimbusds.common.config.CustomTrustStoreConfiguration;
019import com.nimbusds.common.config.DirectoryUser;
020import com.nimbusds.common.config.LDAPServerConnectionPoolDetails;
021import com.nimbusds.common.config.LoggableConfiguration;
022import com.nimbusds.common.ldap.FilterTemplate;
023
024
025/**
026 * LDAP claims source configuration. It is typically derived from a Java key / 
027 * value properties file. The configuration is stored as public fields which 
028 * become immutable (final) after their initialisation.
029 *
030 * <p>Example configuration properties:
031 *
032 * <pre>
033 * op.ldapClaimsSource.enable = true
034 *
035 * op.ldapClaimsSource.server.url = ldap://localhost:1389 ldap://remotehost:1389
036 * op.ldapClaimsSource.server.selectionAlgorithm = FAILOVER
037 * op.ldapClaimsSource.server.connectTimeout = 250
038 * op.ldapClaimsSource.server.security = STARTTLS
039 * op.ldapClaimsSource.server.trustSelfSignedCerts = true
040 * op.ldapClaimsSource.server.connectionPoolSize = 10
041 * op.ldapClaimsSource.server.connectionPoolMaxWaitTime = 250
042 * op.ldapClaimsSource.server.connectionMaxAge = 0
043 *
044 * op.ldapClaimsSource.directory.user.dn = cn=Directory Manager
045 * op.ldapClaimsSource.directory.user.password = secret
046 *
047 * op.ldapClaimsSource.directory.baseDN = ou=people,dc=wonderland,dc=net
048 * op.ldapClaimsSource.directory.scope = ONE
049 * op.ldapClaimsSource.directory.filter = (uid=%u)
050 *
051 * op.ldapClaimsSource.directory.attributeMap = oidcClaimsLdapMap.json
052 *
053 * op.ldapClaimsSource.customTrustStore.enable = false
054 * op.ldapClaimsSource.customTrustStore.file =
055 * op.ldapClaimsSource.customTrustStore.password =
056 * op.ldapClaimsSource.customTrustStore.type =
057 *
058 * op.ldapClaimsSource.customKeyStore.enable = false
059 * op.ldapClaimsSource.customKeyStore.file =
060 * op.ldapClaimsSource.customKeyStore.password =
061 * op.ldapClaimsSource.customKeyStore.type =
062 * </pre>
063 */
064public final class Configuration implements LoggableConfiguration {
065
066
067        /**
068         * The default properties prefix.
069         */
070        public static final String DEFAULT_PREFIX = "op.ldapClaimsSource.";
071
072
073        /**
074         * Directory store details.
075         */
076        public static class Directory implements LoggableConfiguration {
077
078
079                /**
080                 * The directory user credentials. Should have read permission
081                 * to the directory tree containing the user entries.
082                 *
083                 * <p>Property keys: [prefix]user.*
084                 */
085                public final DirectoryUser user;
086
087
088                /**
089                 * The base distinguished name (DN) of the directory branch
090                 * where the user entries are stored.
091                 *
092                 * <p>Property key: [prefix]baseDN
093                 */
094                public final DN baseDN;
095
096
097                /**
098                 * The search scope for the user entries with respect to the
099                 * specified base DN.
100                 *
101                 * <p>Property key: [prefix]scope
102                 */
103                public final SearchScope scope;
104
105
106                /**
107                 * The search filter to retrieve the user entries. The "%u"
108                 * placeholder will be substituted with the subject identifier.
109                 *
110                 * <p>Property key: [prefix]filter
111                 */
112                public final FilterTemplate filter;
113
114
115                /**
116                 * Creates a new directory store details instance from the
117                 * specified properties.
118                 *
119                 * @param prefix The properties prefix. Must not be
120                 *               {@code null}.
121                 * @param props  The properties. Must not be {@code null}.
122                 *
123                 * @throws PropertyParseException On a missing or invalid
124                 *                                property.
125                 */
126                public Directory(final String prefix, final Properties props)
127                        throws PropertyParseException {
128
129                        user = new DirectoryUser(prefix + "user.", props);
130
131                        PropertyRetriever pr = new PropertyRetriever(props);
132
133                        String dnString = pr.getString(prefix + "baseDN");
134
135                        try {
136                                baseDN = new DN(dnString);
137
138                        } catch (LDAPException e) {
139
140                                throw new PropertyParseException("Invalid base DN: " + e.getMessage(),
141                                        prefix + "baseDN",
142                                        dnString);
143                        }
144
145                        String scopeString = pr.getString(prefix + "scope");
146
147                        if (scopeString.equalsIgnoreCase("BASE"))
148                                scope = SearchScope.BASE;
149                        else if (scopeString.equalsIgnoreCase("ONE"))
150                                scope = SearchScope.ONE;
151                        else if (scopeString.equalsIgnoreCase("SUB"))
152                                scope = SearchScope.SUB;
153                        else if (scopeString.equalsIgnoreCase("SUBORDINATES"))
154                                scope = SearchScope.SUBORDINATE_SUBTREE;
155                        else
156                                throw new PropertyParseException("Invalid search scope",
157                                        prefix + "scope",
158                                        scopeString);
159
160                        String filterString = pr.getString(prefix + "filter");
161
162                        try {
163                                filter = new FilterTemplate(filterString);
164
165                        } catch (IllegalArgumentException e) {
166
167                                throw new PropertyParseException("Invalid filter template: " + e.getMessage(),
168                                        prefix + "filter",
169                                        filterString);
170                        }
171                }
172
173
174                @Override
175                public void log() {
176
177                        Logger log = LogManager.getLogger("MAIN");
178                        user.log();
179                        log.info("LDAP claims source: Base DN: {}", baseDN.toString());
180                        log.info("LDAP claims source: Search scope: {}", scope);
181                        log.info("LDAP claims source: Filter: {}", filter);
182                }
183        }
184
185
186        /**
187         * Enables / disables the LDAP claims source.
188         *
189         * <p>Property key: [prefix]enable
190         */
191        public final boolean enable;
192
193
194        /**
195         * The LDAP server connect details.
196         */
197        public final LDAPServerConnectionPoolDetails server;
198
199
200        /**
201         * The directory store details.
202         */
203        public final Directory directory;
204
205
206        /**
207         * The custom trust store details.
208         */
209        public final CustomTrustStoreConfiguration customTrustStore;
210
211
212        /**
213         * The custom key store details.
214         */
215        public final CustomKeyStoreConfiguration customKeyStore;
216
217
218        /**
219         * Creates a new LDAP claims source configuration from the specified
220         * properties.
221         *
222         * @param props The properties. Must not be {@code null}.
223         *
224         * @throws ConfigurationException On a missing or invalid property.
225         */
226        public Configuration(final Properties props)
227                throws ConfigurationException {
228
229                PropertyRetriever pr = new PropertyRetriever(props);
230
231                try {
232                        enable = pr.getBoolean(DEFAULT_PREFIX + "enable");
233
234                        server = new LDAPServerConnectionPoolDetails(DEFAULT_PREFIX + "server.", props);
235
236                        directory = new Directory(DEFAULT_PREFIX + "directory.", props);
237
238                        customTrustStore = new CustomTrustStoreConfiguration(DEFAULT_PREFIX + "customTrustStore.", props);
239
240                        customKeyStore = new CustomKeyStoreConfiguration(DEFAULT_PREFIX + "customKeyStore.", props);
241
242                } catch (PropertyParseException e) {
243
244                        throw new ConfigurationException(e.getMessage() + ": Property: " + e.getPropertyKey());
245                }
246        }
247
248
249        /**
250         * Logs the configuration details at INFO level. Properties that may
251         * adversely affect security are logged at WARN level.
252         */
253        @Override
254        public void log() {
255
256                Logger log = LogManager.getLogger("MAIN");
257
258                log.info("LDAP claims source configuration:");
259                log.info("LDAP claims source enabled: {}", enable);
260                server.log();
261                directory.log();
262                customTrustStore.log();
263                customKeyStore.log();
264        }
265}
266