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}