001/* 002 * Copyright 2016 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.core.impl.checker; 017 018import java.util.HashMap; 019import java.util.Hashtable; 020import java.util.List; 021import java.util.Map; 022 023import javax.naming.Context; 024import javax.naming.NamingEnumeration; 025import javax.naming.NamingException; 026import javax.naming.directory.Attribute; 027import javax.naming.directory.Attributes; 028import javax.naming.directory.SearchControls; 029import javax.naming.directory.SearchResult; 030import javax.naming.ldap.InitialLdapContext; 031import javax.naming.ldap.LdapContext; 032 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.avalon.framework.service.Serviceable; 036 037import org.ametys.core.datasource.AbstractDataSourceManager.DataSourceDefinition; 038import org.ametys.core.datasource.LDAPDataSourceManager; 039import org.ametys.core.util.ldap.ScopeEnumerator; 040import org.ametys.runtime.parameter.ParameterChecker; 041import org.ametys.runtime.parameter.ParameterCheckerTestFailureException; 042import org.ametys.runtime.plugin.component.AbstractLogEnabled; 043 044/** 045 * Tests the LDAP user directory is not empty 046 */ 047public class LdapUserDirectoryChecker extends AbstractLogEnabled implements ParameterChecker, Serviceable 048{ 049 /** The service manager */ 050 private ServiceManager _manager; 051 052 /** The LDAP data source manager */ 053 private LDAPDataSourceManager _ldapDataSourceManager; 054 055 @Override 056 public void service(ServiceManager manager) throws ServiceException 057 { 058 _manager = manager; 059 } 060 061 @Override 062 public void check(List<String> values) throws ParameterCheckerTestFailureException 063 { 064 if (_ldapDataSourceManager == null) 065 { 066 try 067 { 068 _ldapDataSourceManager = (LDAPDataSourceManager) _manager.lookup(LDAPDataSourceManager.ROLE); 069 } 070 catch (ServiceException e) 071 { 072 throw new ParameterCheckerTestFailureException("The test cannot be tested now", e); 073 } 074 } 075 076 String datasourceId = values.get(0); 077 String usersRelativeDN = values.get(1); 078 String usersObjectFilter = values.get(2); 079 int usersSearchScope = ScopeEnumerator.parseScope(values.get(3)); 080 String usersLoginAttribute = values.get(4); 081 String usersFirstnameAttribute = values.get(5); 082 if (usersFirstnameAttribute != null && usersFirstnameAttribute.length() == 0) 083 { 084 usersFirstnameAttribute = null; 085 } 086 String usersLastnameAttribute = values.get(6); 087 String usersEmailAttribute = values.get(7); 088 boolean userEmailIsMandatory = "true".equals(values.get(8)); 089 090 DataSourceDefinition ldapDefinition = _ldapDataSourceManager.getDataSourceDefinition(datasourceId); 091 if (ldapDefinition == null) 092 { 093 throw new ParameterCheckerTestFailureException ("Unable to find the data source definition for the id '" + datasourceId + "'."); 094 } 095 else 096 { 097 // Search some users 098 LdapContext context = null; 099 NamingEnumeration<SearchResult> results = null; 100 101 try 102 { 103 // Connection to the LDAP server 104 context = new InitialLdapContext(_getContextEnv(ldapDefinition), null); 105 106 // Execute ldap search 107 results = context.search(usersRelativeDN, 108 usersObjectFilter, 109 new Object[0], 110 _getSearchConstraint(0, usersFirstnameAttribute, usersLoginAttribute, usersLastnameAttribute, usersEmailAttribute, usersSearchScope)); 111 112 boolean userFound = false; 113 while (results.hasMoreElements() && !userFound) 114 { 115 SearchResult result = results.nextElement(); 116 Map<String, Object> attrs = _getAttributes(result, usersLoginAttribute, usersFirstnameAttribute, usersLastnameAttribute, usersEmailAttribute, userEmailIsMandatory); 117 if (attrs != null) 118 { 119 // a user was found 120 userFound = true; 121 } 122 } 123 124 if (!userFound) 125 { 126 throw new ParameterCheckerTestFailureException("The LDAP repository does not return any user with the given parameters."); 127 } 128 } 129 catch (IllegalArgumentException | NamingException e) 130 { 131 throw new ParameterCheckerTestFailureException(e); 132 } 133 finally 134 { 135 // Close connections 136 try 137 { 138 _cleanup(context, results); 139 } 140 catch (NamingException e) 141 { 142 getLogger().error("Cleaning the LDAP connection during test failed.", e); 143 throw new ParameterCheckerTestFailureException(e); 144 } 145 } 146 } 147 } 148 149 private Hashtable<String, String> _getContextEnv(DataSourceDefinition ldapDefinition) 150 { 151 Map<String, String> ldapParameters = ldapDefinition.getParameters(); 152 153 String ldapUrl = ldapParameters.get(LDAPDataSourceManager.PARAM_BASE_URL); 154 String ldapBaseDN = ldapParameters.get(LDAPDataSourceManager.PARAM_BASE_DN); 155 String ldapAdminRelativeDN = ldapParameters.get(LDAPDataSourceManager.PARAM_ADMIN_DN); 156 String ldapAdminPassword = ldapParameters.get(LDAPDataSourceManager.PARAM_ADMIN_PASSWORD); 157 String ldapAuthenticationMethod = ldapParameters.get(LDAPDataSourceManager.PARAM_AUTHENTICATION_METHOD); 158 boolean ldapUseSSL = "true".equals(ldapParameters.get(LDAPDataSourceManager.PARAM_USE_SSL)); 159 boolean ldapFollowReferrals = "true".equals(ldapParameters.get(LDAPDataSourceManager.PARAM_FOLLOW_REFERRALS)); 160 String ldapAliasDerefMode = ldapParameters.get(LDAPDataSourceManager.PARAM_ALIAS_DEREFERENCING); 161 162 Hashtable<String, String> env = new Hashtable<>(); 163 164 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 165 env.put(Context.PROVIDER_URL, ldapUrl + "/" + ldapBaseDN); 166 env.put(Context.SECURITY_AUTHENTICATION, ldapAuthenticationMethod); 167 168 if (!ldapAuthenticationMethod.equals("none")) 169 { 170 env.put(Context.SECURITY_PRINCIPAL, ldapAdminRelativeDN); 171 env.put(Context.SECURITY_CREDENTIALS, ldapAdminPassword); 172 } 173 174 if (ldapUseSSL) 175 { 176 // Encrypt the connection to the server with SSL 177 env.put(Context.SECURITY_PROTOCOL, "ssl"); 178 } 179 180 // Default is to ignore. 181 if (ldapFollowReferrals) 182 { 183 env.put(Context.REFERRAL, "follow"); 184 } 185 else 186 { 187 env.put(Context.REFERRAL, "ignore"); 188 } 189 190 env.put("java.naming.ldap.derefAliases", ldapAliasDerefMode); 191 192 // Use ldap pool connection 193 env.put("com.sun.jndi.ldap.connect.pool", "true"); 194 195 return env; 196 } 197 198 private SearchControls _getSearchConstraint(int maxResults, String usersFirstnameAttribute, String usersLoginAttribute, String usersLastnameAttribute, String usersEmailAttribute, int usersSearchScope) 199 { 200 // Search parameters 201 SearchControls constraints = new SearchControls(); 202 int attributesCount = 4; 203 int index = 0; 204 205 if (usersFirstnameAttribute == null) 206 { 207 attributesCount--; 208 } 209 210 // Position the wanted attributes 211 String[] attrs = new String[attributesCount]; 212 213 attrs[index++] = usersLoginAttribute; 214 if (usersFirstnameAttribute != null) 215 { 216 attrs[index++] = usersFirstnameAttribute; 217 } 218 attrs[index++] = usersLastnameAttribute; 219 attrs[index++] = usersEmailAttribute; 220 221 constraints.setReturningAttributes(attrs); 222 223 // Choose depth of search 224 constraints.setSearchScope(usersSearchScope); 225 226 if (maxResults > 0) 227 { 228 constraints.setCountLimit(maxResults); 229 } 230 231 return constraints; 232 } 233 234 private Map<String, Object> _getAttributes(SearchResult entry, String usersLoginAttribute, String usersFirstnameAttribute, String usersLastnameAttribute, String usersEmailAttribute, boolean userEmailIsMandatory) throws NamingException 235 { 236 Map<String, Object> result = new HashMap<>(); 237 238 // Retrieve the entry attributes 239 Attributes attrs = entry.getAttributes(); 240 241 // Retrieve the login 242 Attribute ldapAttr = attrs.get(usersLoginAttribute); 243 if (ldapAttr == null) 244 { 245 if (getLogger().isWarnEnabled()) 246 { 247 getLogger().warn("Missing login attribute : '{}'", usersLoginAttribute); 248 } 249 return null; 250 } 251 252 result.put(usersLoginAttribute, ldapAttr.get()); 253 254 if (usersFirstnameAttribute != null) 255 { 256 // Retrieve the first name 257 ldapAttr = attrs.get(usersFirstnameAttribute); 258 if (ldapAttr == null) 259 { 260 if (getLogger().isWarnEnabled()) 261 { 262 getLogger().warn("Missing firstname attribute : '{}', for user '{}'.", usersFirstnameAttribute, result.get(usersLoginAttribute)); 263 } 264 return null; 265 } 266 267 result.put(usersFirstnameAttribute, ldapAttr.get()); 268 } 269 270 // Retrieve the last name 271 ldapAttr = attrs.get(usersLastnameAttribute); 272 if (ldapAttr == null) 273 { 274 if (getLogger().isWarnEnabled()) 275 { 276 getLogger().warn("Missing lastname attribute : '{}', for user '{}'.", usersLastnameAttribute, result.get(usersLoginAttribute)); 277 } 278 return null; 279 } 280 281 result.put(usersLastnameAttribute, ldapAttr.get()); 282 283 // Retrieve the email 284 ldapAttr = attrs.get(usersEmailAttribute); 285 if (ldapAttr == null && userEmailIsMandatory) 286 { 287 if (getLogger().isWarnEnabled()) 288 { 289 getLogger().warn("Missing email attribute : '{}', for user '{}'.", usersEmailAttribute, result.get(usersLoginAttribute)); 290 } 291 return null; 292 } 293 294 if (ldapAttr == null) 295 { 296 result.put(usersEmailAttribute, ""); 297 } 298 else 299 { 300 result.put(usersEmailAttribute, ldapAttr.get()); 301 } 302 303 return result; 304 } 305 306 private void _cleanup(Context context, NamingEnumeration result) throws NamingException 307 { 308 if (result != null) 309 { 310 // Fermer le result 311 result.close(); 312 } 313 if (context != null) 314 { 315 // Fermer la connexion au serveur 316 context.close(); 317 } 318 } 319 320}