View Javadoc

1   /*
2    * Copyright (C) 2017-2019 Centre National d'Etudes Spatiales (CNES).
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 3.0 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this library; if not, write to the Free Software
16   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17   * MA 02110-1301  USA
18   */
19  package fr.cnes.doi.plugin.impl.db;
20  
21  import java.util.ArrayList;
22  import java.util.Hashtable;
23  import java.util.List;
24  
25  import javax.naming.Context;
26  import javax.naming.NamingEnumeration;
27  import javax.naming.NamingException;
28  import javax.naming.directory.Attribute;
29  import javax.naming.directory.DirContext;
30  import javax.naming.directory.SearchControls;
31  import javax.naming.directory.SearchResult;
32  import javax.naming.ldap.InitialLdapContext;
33  
34  import org.apache.logging.log4j.LogManager;
35  import org.apache.logging.log4j.Logger;
36  
37  import fr.cnes.doi.exception.AuthenticationAccessException;
38  import fr.cnes.doi.db.model.AuthSystemUser;
39  import fr.cnes.doi.exception.DoiRuntimeException;
40  import fr.cnes.doi.utils.Utils;
41  import fr.cnes.doi.plugin.AbstractAuthenticationPluginHelper;
42  import fr.cnes.doi.settings.DoiSettings;
43  import java.util.Map;
44  
45  /**
46   * Implementation of the LDAP.
47   *
48   * @author Jean-Christophe Malapert (jean-christophe.malapert@cnes.fr)
49   */
50  public final class DefaultLDAPImpl extends AbstractAuthenticationPluginHelper {
51  
52      /**
53       * LDAP url
54       */
55      public static final String LDAP_URL = "Starter.LDAP.url";
56  
57      /**
58       * LDAP user
59       */
60      public static final String LDAP_USER = "Starter.LDAP.user";
61  
62      /**
63       * LDAP pwd
64       */
65      public static final String LDAP_PWD = "Starter.LDAP.password";
66  
67      /**
68       * LDAP project
69       */
70      public static final String LDAP_PROJECT = "Starter.LDAP.project";
71  
72      /**
73       * LDAP username who is the administrator of the DOI server
74       */
75      public static final String LDAP_DOI_ADMIN = "Starter.LDAP.user.admin";
76  
77      /**
78       * Specifies the filter expression to get the group.
79       */
80      public static final String LDAP_SEARCH_GROUP = "Starter.LDAP.search.group";
81  
82      /**
83       * Specifies the filter expression to get the users.
84       */
85      public static final String LDAP_SEARCH_USER = "Starter.LDAP.search.user";
86  
87      /**
88       * Attributes name in LDAP for username.
89       */
90      public static final String LDAP_ATTR_USERNAME = "Starter.LDAP.attr.username";
91  
92      /**
93       * Attributes name in LDAP for mail.
94       */
95      public static final String LDAP_ATTR_MAIL = "Starter.LDAP.attr.mail";
96  
97      /**
98       * Attributes name in LDAP for fullname.
99       */
100     public static final String LDAP_ATTR_FULLNAME = "Starter.LDAP.attr.fullname";
101 
102     /**
103      * Logger.
104      */
105     private static final Logger LOGGER = LogManager.getLogger(DefaultLDAPImpl.class.getName());
106 
107     /**
108      * Plugin description.
109      */
110     private static final String DESCRIPTION = "Provides a pre-defined list of users and groups";
111     /**
112      * Plugin version.
113      */
114     private static final String VERSION = "1.0.0";
115     /**
116      * Plugin owner.
117      */
118     private static final String OWNER = "CNES";
119     /**
120      * Plugin author.
121      */
122     private static final String AUTHOR = "Jean-Christophe Malapert";
123     /**
124      * Plugin license.
125      */
126     private static final String LICENSE = "LGPLV3";
127     /**
128      * Plugin name.
129      */
130     private final String NAME = this.getClass().getName();
131 
132     /**
133      * Configuration file.
134      */
135     private Map<String, String> conf;
136 
137     /**
138      * Status of the plugin configuration.
139      */
140     private boolean configured = false;
141 
142     /**
143      * {@inheritDoc }
144      */
145     @Override
146     public void setConfiguration(final Object configuration) {
147         this.conf = (Map<String, String>) configuration;
148         LOGGER.info("[CONF] Plugin LDAP URL : {}", this.conf.get(LDAP_URL));
149         LOGGER.info("[CONF] Plugin LDAP user : {}", this.conf.get(LDAP_USER));
150         LOGGER.info("[CONF] Plugin LDAP password : {}", Utils.transformPasswordToStars(this.conf.
151                 get(LDAP_PWD)));
152         LOGGER.info("[CONF] Plugin LDAP project : {}", this.conf.get(LDAP_PROJECT));
153         LOGGER.info("[CONF] Plugin LDAP admin for DOI : {}", this.conf.get(LDAP_DOI_ADMIN));
154         LOGGER.info("[CONF] Plugin LDAP attribute for fullname : {}", this.conf.get(
155                 LDAP_ATTR_FULLNAME));
156         LOGGER.info("[CONF] Plugin LDAP attribute for mail : {}", this.conf.get(LDAP_ATTR_MAIL));
157         LOGGER.info("[CONF] Plugin LDAP attribute for username : {}", this.conf.get(
158                 LDAP_ATTR_USERNAME));
159         LOGGER.info("[CONF] Plugin LDAP search group : {}", this.conf.get(LDAP_SEARCH_GROUP));
160         LOGGER.info("[CONF] Plugin LDAP search user : {}", this.conf.get(LDAP_SEARCH_USER));
161         this.configured = true;
162     }
163 
164     /**
165      * {@inheritDoc }
166      */
167     @Override
168     public void initConnection() throws DoiRuntimeException {
169         try {
170             final InitialLdapContext context = getContext();
171             if (context == null) {
172                 throw new DoiRuntimeException("LDAPAccessImpl: Unable to connect to Ldap");
173             } else {
174                 context.close();
175             }
176         } catch (NamingException ex) {
177             throw new DoiRuntimeException("LDAPAccessImpl: Unable to connect to Ldap", ex);
178         }
179     }
180 
181     /**
182      * {@inheritDoc}
183      */
184     @Override
185     public List<AuthSystemUser> getDOIProjectMembers() throws AuthenticationAccessException {
186         LOGGER.traceEntry();
187         DirContext context = null;
188         try {
189             try {
190                 context = getContext();
191             } catch (NamingException ex) {
192                 LOGGER.error("LDAPAccessImpl getContext: Unable to connect to Ldap", ex);
193             }
194             if (context == null) {
195                 throw new AuthenticationAccessException("Configuration problem with the LDAP",
196                         new Exception());
197             } else {
198                 return LOGGER.traceExit(getAllDOIProjectMembers((InitialLdapContext) context));
199             }
200         } finally {
201             if (context != null) {
202                 try {
203                     context.close();
204                 } catch (NamingException e) {
205                     LOGGER.warn("LDAPAccessImpl getDOIProjectMembers: Unable to close the context",
206                             e);
207                 }
208             }
209         }
210     }
211 
212     /**
213      * Returns true when the LDAP context is well configured.
214      *
215      * @return true when the LDAP context is well configured otherwise false
216      */
217     private boolean isLdapConfigured() {
218         final String ldapUser = conf.getOrDefault(LDAP_USER, "");
219         final String ldapPwd = conf.getOrDefault(LDAP_PWD, "");
220         final String ldapSearchUser = conf.getOrDefault(LDAP_SEARCH_USER, "");
221         return !ldapUser.isEmpty() && !ldapPwd.isEmpty() && !ldapSearchUser.isEmpty();
222     }
223 
224     /**
225      * Init LDAP context.
226      *
227      * @return the context or null when a LDAP configuration is missing
228      * @throws NamingException Unable to connect to Ldap
229      */
230     private InitialLdapContext getContext() throws NamingException {
231         LOGGER.traceEntry();
232         InitialLdapContext context = null;
233         if (isLdapConfigured()) {
234             final Hashtable<String, String> prop = new Hashtable<>();
235             final String ldapUser = conf.get(LDAP_USER);
236             final String ldapPwd = DoiSettings.getInstance().getSecretValue(conf.get(LDAP_PWD));
237             final String securityPrincipal = ldapUser;
238             final String ldapUrl = conf.get(LDAP_URL);
239             prop.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
240             prop.put(Context.PROVIDER_URL, ldapUrl);
241             prop.put(Context.SECURITY_AUTHENTICATION, "simple");
242             prop.put(Context.SECURITY_PRINCIPAL, securityPrincipal);
243             prop.put(Context.SECURITY_CREDENTIALS, ldapPwd);
244 
245             LOGGER.info("LDAP context:\n  {}={}\n  {}={}\n  {}={}\n  {}={}\n  {}={}",
246                     Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory",
247                     Context.PROVIDER_URL, ldapUrl,
248                     Context.SECURITY_AUTHENTICATION, "simple",
249                     Context.SECURITY_PRINCIPAL, securityPrincipal,
250                     Context.SECURITY_CREDENTIALS, Utils.transformPasswordToStars(ldapPwd));
251             context = new InitialLdapContext(prop, null);
252         } else {
253             LOGGER.error("LDAP is not well configured. Checks the configuration file");
254         }
255         return LOGGER.traceExit(context);
256     }
257 
258     /**
259      * {@inheritDoc}
260      */
261     @Override
262     public boolean authenticateUser(final String login, final String password) {
263         final Hashtable<String, String> prop = new Hashtable<>();
264         final String securityPrincipal = String.format(
265                 "uid=%s,%s",
266                 login,
267                 conf.get(LDAP_SEARCH_USER));
268         final String ldapUrl = conf.get(LDAP_URL);
269         prop.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
270         prop.put(Context.PROVIDER_URL, ldapUrl);
271         prop.put(Context.SECURITY_AUTHENTICATION, "simple");
272         prop.put(Context.SECURITY_PRINCIPAL, securityPrincipal);
273         prop.put(Context.SECURITY_CREDENTIALS, password);
274 
275         LOGGER.info("LDAP authentication:\n  {}={}\n  {}={}\n  {}={}\n  {}={}\n  {}={}",
276                 Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory",
277                 Context.PROVIDER_URL, ldapUrl,
278                 Context.SECURITY_AUTHENTICATION, "simple",
279                 Context.SECURITY_PRINCIPAL, securityPrincipal,
280                 Context.SECURITY_CREDENTIALS, Utils.transformPasswordToStars(password));
281 
282         InitialLdapContext context = null;
283         boolean isAuthenticate;
284         try {
285             context = new InitialLdapContext(prop, null);
286             isAuthenticate = true;
287         } catch (NamingException e) {
288             LOGGER.error("LDAPAccessImpl getContext: Unable to identify user", e);
289             isAuthenticate = false;
290         } catch (Exception e) {
291             LOGGER.error("LDAPAccessImpl getContext: Unexpected exception", e);
292             isAuthenticate = false;
293         } finally {
294             try {
295                 if (context != null) {
296                     context.close();
297                 }
298             } catch (NamingException e) {
299                 LOGGER.error("LDAPAccessImpl getContext: Unable to close context", e);
300             }
301         }
302         LOGGER.info("LDAP authentication: {}", isAuthenticate);
303         return isAuthenticate;
304     }
305 
306     /**
307      * Search on LDAP all users which are in the group Consts.LDAP_PROJECT.
308      *
309      * @param context context
310      * @return all LDAP users which are in the group Consts.LDAP_PROJECT
311      * @throws AuthenticationAccessException Exception
312      */
313     public List<AuthSystemUser> getAllDOIProjectMembers(final InitialLdapContext context)
314             throws AuthenticationAccessException {
315         try {
316             LOGGER.traceEntry("Parameters : {}", context);
317             final String searchGroup = conf.get(LDAP_SEARCH_GROUP);
318             final String ldapProject = conf.get(LDAP_PROJECT);
319 
320             final SearchControls constraints = new SearchControls();
321             constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
322             final String[] attrIDs = {"gidNumber",};
323             constraints.setReturningAttributes(attrIDs);
324 
325             LOGGER.info("LDAP search({},{},{})", searchGroup, "cn=" + ldapProject, constraints);
326             final NamingEnumeration answer = context.search(searchGroup, "cn=" + ldapProject,
327                     constraints);
328             LOGGER.info("LDAP search : OK");
329             final List<AuthSystemUser> members = new ArrayList<>();
330             if (answer.hasMore()) {
331                 final NamingEnumeration<?> attrs = ((SearchResult) answer.next()).getAttributes().
332                         get("gidNumber").getAll();
333                 members.addAll(getLdapUsers(context, attrs.next().toString()));
334             }
335             return LOGGER.traceExit(members);
336         } catch (NamingException e) {
337             LOGGER.error(e);
338             throw new AuthenticationAccessException("", e);
339         }
340     }
341 
342     /**
343      * Search on LDAP all users which are in the group Consts.LDAP_PROJECT in a
344      * LDAP group
345      *
346      * @param context context
347      * @param gidNumber LDAP group ID
348      * @return all LDAP users which are in the group Consts.LDAP_PROJECT for a
349      * specific LDAP group
350      * @throws NamingException Exception
351      */
352     private List<AuthSystemUser> getLdapUsers(final DirContext context, final String gidNumber)
353             throws NamingException {
354         LOGGER.traceEntry("Parameters : {}", context, gidNumber);
355         final List<AuthSystemUser> ldapuserList = new ArrayList<>();
356         final SearchControls controls = new SearchControls();
357         controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
358         final String[] attrIDs = {
359             conf.get(LDAP_ATTR_USERNAME),
360             conf.get(LDAP_ATTR_MAIL),
361             conf.get(LDAP_ATTR_FULLNAME)
362         };
363         LOGGER.info("Getting attributes from LDAP: {}", (Object[]) attrIDs);
364         controls.setReturningAttributes(attrIDs);
365 
366         final String ldapProject = conf.get(LDAP_PROJECT);
367         final String ldapSearchGroup = conf.get(LDAP_SEARCH_GROUP);
368         final String ldapSearchUser = conf.get(LDAP_SEARCH_USER);
369         final String ldapSearchAttr = String.format(
370                 "(|(gidNumber=%s)(memberOf=cn=%s,%s))",
371                 gidNumber, ldapProject, ldapSearchGroup
372         );
373 
374         LOGGER.info("LDAP search({},{},{}", ldapSearchUser, ldapSearchAttr, controls);
375         final NamingEnumeration answer = context.search(ldapSearchUser, ldapSearchAttr, controls);
376         LOGGER.info("LDAP search : OK");
377         while (answer.hasMore()) {
378             final NamingEnumeration<? extends Attribute> attrbs = ((SearchResult) answer.next())
379                     .getAttributes().getAll();
380             String fullname = null;
381             String uid = null;
382             String mail = null;
383             while (attrbs.hasMore()) {
384                 final Attribute att = attrbs.next();
385                 final String attId = att.getID();
386                 if (attrIDs[0].equals(attId)) {
387                     final NamingEnumeration<?> values = att.getAll();
388                     while (values.hasMoreElements()) {
389                         uid = values.next().toString();
390                         break;
391                     }
392                 } else if (attrIDs[1].equals(attId)) {
393                     final NamingEnumeration<?> values = att.getAll();
394                     while (values.hasMoreElements()) {
395                         mail = values.next().toString();
396                         break;
397                     }
398                 } else if (attrIDs[2].equals(attId)) {
399                     final NamingEnumeration<?> values = att.getAll();
400                     while (values.hasMoreElements()) {
401                         fullname = values.next().toString();
402                         break;
403                     }
404                 }
405             }
406             if ((mail != null) && (uid != null)) {
407                 final AuthSystemUser ldapuser = new AuthSystemUser();
408                 ldapuser.setFullname(fullname);
409                 ldapuser.setEmail(mail);
410                 ldapuser.setUsername(uid);
411                 ldapuserList.add(ldapuser);
412                 LOGGER.debug("Create LDAP user : {}", ldapuser);
413             }
414         }
415         return LOGGER.traceExit(ldapuserList);
416     }
417 
418     /**
419      * {@inheritDoc}
420      */
421     @Override
422     public String getName() {
423         return NAME;
424     }
425 
426     /**
427      * {@inheritDoc}
428      */
429     @Override
430     public String getDescription() {
431         return DESCRIPTION;
432     }
433 
434     /**
435      * {@inheritDoc}
436      */
437     @Override
438     public String getVersion() {
439         return VERSION;
440     }
441 
442     /**
443      * {@inheritDoc}
444      */
445     @Override
446     public String getAuthor() {
447         return AUTHOR;
448     }
449 
450     /**
451      * {@inheritDoc}
452      */
453     @Override
454     public String getOwner() {
455         return OWNER;
456     }
457 
458     /**
459      * {@inheritDoc}
460      */
461     @Override
462     public String getLicense() {
463         return LICENSE;
464     }
465 
466     /**
467      * {@inheritDoc}
468      */
469     @Override
470     public String getDOIAdmin() {
471         return conf.get(LDAP_DOI_ADMIN);
472     }
473 
474     /**
475      * {@inheritDoc}
476      */
477     @Override
478     public StringBuilder validate() {
479         final StringBuilder validation = new StringBuilder();
480         final String message = "Sets ";
481         if (!this.conf.containsKey(LDAP_ATTR_FULLNAME)) {
482             validation.append(message).append(LDAP_ATTR_FULLNAME).append("\n");
483         }
484         if (!this.conf.containsKey(LDAP_ATTR_MAIL)) {
485             validation.append(message).append(LDAP_ATTR_MAIL).append("\n");
486         }
487         if (!this.conf.containsKey(LDAP_ATTR_USERNAME)) {
488             validation.append(message).append(LDAP_ATTR_USERNAME).append("\n");
489         }
490         if (!this.conf.containsKey(LDAP_PROJECT)) {
491             validation.append(message).append(LDAP_PROJECT).append("\n");
492         }
493         if (!this.conf.containsKey(LDAP_URL)) {
494             validation.append(message).append(LDAP_URL).append("\n");
495         }
496         if (!this.conf.containsKey(LDAP_SEARCH_GROUP)) {
497             validation.append(message).append(LDAP_SEARCH_GROUP).append("\n");
498         }
499         if (!this.conf.containsKey(LDAP_SEARCH_USER)) {
500             validation.append(message).append(LDAP_SEARCH_USER).append("\n");
501         }
502         if (!this.conf.containsKey(LDAP_USER)) {
503             validation.append(message).append(LDAP_USER).append("\n");
504         }
505         if (!this.conf.containsKey(LDAP_PWD)) {
506             validation.append(message).append(LDAP_PWD).append("\n");
507         }
508         return validation;
509     }
510 
511     /**
512      * Checks if the keyword is a password.
513      *
514      * @param key keyword to check
515      * @return True when the keyword is a password otherwise False
516      */
517     public static boolean isPassword(final String key) {
518         return LDAP_PWD.equals(key);
519     }
520 
521     /**
522      * {@inheritDoc}
523      */
524     @Override
525     public void release() {
526         this.configured = false;
527     }
528 
529     /**
530      * {@inheritDoc}
531      */
532     @Override
533     public boolean isConfigured() {
534         return this.configured;
535     }
536 }