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