xref: /OpenGrok/plugins/src/main/java/opengrok/auth/plugin/LdapUserPlugin.java (revision d630fdc80623f85cf43a81c7f7168517ae5f6794)
1b28a5538SAdam Hornacek /*
2b28a5538SAdam Hornacek  * CDDL HEADER START
3b28a5538SAdam Hornacek  *
4b28a5538SAdam Hornacek  * The contents of this file are subject to the terms of the
5b28a5538SAdam Hornacek  * Common Development and Distribution License (the "License").
6b28a5538SAdam Hornacek  * You may not use this file except in compliance with the License.
7b28a5538SAdam Hornacek  *
8b28a5538SAdam Hornacek  * See LICENSE.txt included in this distribution for the specific
9b28a5538SAdam Hornacek  * language governing permissions and limitations under the License.
10b28a5538SAdam Hornacek  *
11b28a5538SAdam Hornacek  * When distributing Covered Code, include this CDDL HEADER in each
12b28a5538SAdam Hornacek  * file and include the License file at LICENSE.txt.
13b28a5538SAdam Hornacek  * If applicable, add the following below this CDDL HEADER, with the
14b28a5538SAdam Hornacek  * fields enclosed by brackets "[]" replaced with your own identifying
15b28a5538SAdam Hornacek  * information: Portions Copyright [yyyy] [name of copyright owner]
16b28a5538SAdam Hornacek  *
17b28a5538SAdam Hornacek  * CDDL HEADER END
18b28a5538SAdam Hornacek  */
19b28a5538SAdam Hornacek 
20b28a5538SAdam Hornacek /*
215aedecbdSVladimir Kotal  * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
22b28a5538SAdam Hornacek  */
23b28a5538SAdam Hornacek package opengrok.auth.plugin;
24b28a5538SAdam Hornacek 
2553c33ae5SVladimir Kotal import java.util.Arrays;
26*d630fdc8SVladimir Kotal import java.util.Collections;
27b28a5538SAdam Hornacek import java.util.HashMap;
2853c33ae5SVladimir Kotal import java.util.HashSet;
29b28a5538SAdam Hornacek import java.util.Map;
30b28a5538SAdam Hornacek import java.util.Set;
31b28a5538SAdam Hornacek import java.util.logging.Level;
32b28a5538SAdam Hornacek import java.util.logging.Logger;
33aa6abf42SAdam Hornacek 
34aa6abf42SAdam Hornacek import jakarta.servlet.http.HttpServletRequest;
35b28a5538SAdam Hornacek import opengrok.auth.entity.LdapUser;
36b28a5538SAdam Hornacek import opengrok.auth.plugin.entity.User;
3717deb9edSVladimir Kotal import opengrok.auth.plugin.ldap.AbstractLdapProvider;
38b28a5538SAdam Hornacek import opengrok.auth.plugin.ldap.LdapException;
39*d630fdc8SVladimir Kotal import org.jetbrains.annotations.NotNull;
40b28a5538SAdam Hornacek import org.opengrok.indexer.authorization.AuthorizationException;
41b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Group;
42b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Project;
43b28a5538SAdam Hornacek 
44f305899cSVladimir Kotal import static opengrok.auth.plugin.util.FilterUtil.expandUserFilter;
45f305899cSVladimir Kotal 
46b28a5538SAdam Hornacek /**
47b28a5538SAdam Hornacek  * Authorization plug-in to extract user's LDAP attributes.
48b28a5538SAdam Hornacek  * The attributes can be then used by the other LDAP plugins down the stack.
49b28a5538SAdam Hornacek  *
50b28a5538SAdam Hornacek  * @author Krystof Tulinger
51b28a5538SAdam Hornacek  */
52b28a5538SAdam Hornacek public class LdapUserPlugin extends AbstractLdapPlugin {
53b28a5538SAdam Hornacek 
54b28a5538SAdam Hornacek     private static final Logger LOGGER = Logger.getLogger(LdapUserPlugin.class.getName());
55b28a5538SAdam Hornacek 
56bf4d2fd4SVladimir Kotal     static final String SESSION_ATTR = "opengrok-ldap-plugin-user";
57*d630fdc8SVladimir Kotal     static final String NEGATIVE_CACHE_ATTR = "opengrok-ldap-plugin-user-invalid-user";
58b28a5538SAdam Hornacek 
5953c33ae5SVladimir Kotal     /**
600e7ff20bSVladimir Kotal      * List of configuration names.
6153c33ae5SVladimir Kotal      * <ul>
6253c33ae5SVladimir Kotal      * <li><code>filter</code> is LDAP filter used for searching (optional)</li>
635aedecbdSVladimir Kotal      * <li><code>useDN</code> boolean value indicating if User.username should be treated as Distinguished Name
645aedecbdSVladimir Kotal      * (optional, default is false)</li>
6553c33ae5SVladimir Kotal      * <li><code>attributes</code> is comma separated list of LDAP attributes to be produced (mandatory)</li>
665aedecbdSVladimir Kotal      * <li><code>instance</code> integer that can be used to identify instance of this plugin by other LDAP plugins
675aedecbdSVladimir Kotal      * (optional, default empty)</li>
6853c33ae5SVladimir Kotal      * </ul>
6953c33ae5SVladimir Kotal      */
70bf4d2fd4SVladimir Kotal     static final String LDAP_FILTER = "filter";
71bf4d2fd4SVladimir Kotal     static final String ATTRIBUTES = "attributes";
72bf4d2fd4SVladimir Kotal     static final String USE_DN = "useDN";
73bf4d2fd4SVladimir Kotal     static final String INSTANCE = "instance";
74b28a5538SAdam Hornacek 
7553c33ae5SVladimir Kotal     private String ldapFilter;
7653c33ae5SVladimir Kotal     private Boolean useDN;
7782105c7dSVladimir Kotal     private Set<String> attrSet;
7882105c7dSVladimir Kotal     private Integer instanceNum;
79b28a5538SAdam Hornacek 
803c16dad8SVladimir Kotal     // for testing
load(Map<String, Object> parameters, AbstractLdapProvider provider)813c16dad8SVladimir Kotal     void load(Map<String, Object> parameters, AbstractLdapProvider provider) {
823c16dad8SVladimir Kotal         super.load(provider);
833c16dad8SVladimir Kotal 
843c16dad8SVladimir Kotal         init(parameters);
853c16dad8SVladimir Kotal     }
863c16dad8SVladimir Kotal 
87b28a5538SAdam Hornacek     @Override
load(Map<String, Object> parameters)88b28a5538SAdam Hornacek     public void load(Map<String, Object> parameters) {
89b28a5538SAdam Hornacek         super.load(parameters);
90b28a5538SAdam Hornacek 
913c16dad8SVladimir Kotal         init(parameters);
923c16dad8SVladimir Kotal     }
933c16dad8SVladimir Kotal 
init(Map<String, Object> parameters)943c16dad8SVladimir Kotal     private void init(Map<String, Object> parameters) {
95b28a5538SAdam Hornacek         String attributesVal;
96b28a5538SAdam Hornacek         if ((attributesVal = (String) parameters.get(ATTRIBUTES)) == null) {
975aedecbdSVladimir Kotal             throw new NullPointerException("Missing configuration parameter [" + ATTRIBUTES + "] in the setup");
98b28a5538SAdam Hornacek         }
9982105c7dSVladimir Kotal         attrSet = new HashSet<>(Arrays.asList(attributesVal.split(",")));
100b28a5538SAdam Hornacek 
10153c33ae5SVladimir Kotal         ldapFilter = (String) parameters.get(LDAP_FILTER);
10253c33ae5SVladimir Kotal 
10353c33ae5SVladimir Kotal         if ((useDN = (Boolean) parameters.get(USE_DN)) == null) {
10453c33ae5SVladimir Kotal             useDN = false;
10553c33ae5SVladimir Kotal         }
10653c33ae5SVladimir Kotal 
10782105c7dSVladimir Kotal         String instanceParam = (String) parameters.get(INSTANCE);
10882105c7dSVladimir Kotal         if (instanceParam != null) {
10982105c7dSVladimir Kotal             instanceNum = Integer.parseInt(instanceParam);
110e173e36dSVladimir Kotal         }
11166cf937cSVladimir Kotal 
11253c33ae5SVladimir Kotal         LOGGER.log(Level.FINE, "LdapUser plugin loaded with filter={0}, " +
11366cf937cSVladimir Kotal                         "attributes={1}, useDN={2}, instance={3}",
11482105c7dSVladimir Kotal                 new Object[]{ldapFilter, attrSet, useDN, instanceNum});
115b28a5538SAdam Hornacek     }
116b28a5538SAdam Hornacek 
117b28a5538SAdam Hornacek     /**
118b28a5538SAdam Hornacek      * Check if the session exists and contains all necessary fields required by
119b28a5538SAdam Hornacek      * this plug-in.
120b28a5538SAdam Hornacek      *
121b28a5538SAdam Hornacek      * @param req the HTTP request
122b28a5538SAdam Hornacek      * @return true if it does; false otherwise
123b28a5538SAdam Hornacek      */
124b28a5538SAdam Hornacek     @Override
sessionExists(HttpServletRequest req)125b28a5538SAdam Hornacek     protected boolean sessionExists(HttpServletRequest req) {
1265aedecbdSVladimir Kotal         return super.sessionExists(req) && req.getSession().getAttribute(getSessionAttrName()) != null;
127b28a5538SAdam Hornacek     }
128b28a5538SAdam Hornacek 
12953c33ae5SVladimir Kotal     /**
130f305899cSVladimir Kotal      * Expand {@code User} object attribute values into the filter.
13153c33ae5SVladimir Kotal      *
132f305899cSVladimir Kotal      * @see opengrok.auth.plugin.util.FilterUtil
13353c33ae5SVladimir Kotal      *
13453c33ae5SVladimir Kotal      * Use \% for printing the '%' character.
13553c33ae5SVladimir Kotal      *
136750d880bSVladimir Kotal      * @param user User object from the request (created by {@code UserPlugin})
13753c33ae5SVladimir Kotal      * @return replaced result
13853c33ae5SVladimir Kotal      */
expandFilter(User user)139bf4d2fd4SVladimir Kotal     String expandFilter(User user) {
14053c33ae5SVladimir Kotal         String filter = ldapFilter;
141f305899cSVladimir Kotal         filter = expandUserFilter(user, filter);
1429d74bf96SVladimir Kotal         filter = filter.replace("\\%", "%");
14353c33ae5SVladimir Kotal 
14453c33ae5SVladimir Kotal         return filter;
145b28a5538SAdam Hornacek     }
146b28a5538SAdam Hornacek 
147b28a5538SAdam Hornacek     @Override
fillSession(HttpServletRequest req, User user)148b28a5538SAdam Hornacek     public void fillSession(HttpServletRequest req, User user) {
149b28a5538SAdam Hornacek         Map<String, Set<String>> records;
150b28a5538SAdam Hornacek 
151b28a5538SAdam Hornacek         updateSession(req, null);
152b28a5538SAdam Hornacek 
153b28a5538SAdam Hornacek         if (getLdapProvider() == null) {
15453c33ae5SVladimir Kotal             LOGGER.log(Level.WARNING, "cannot get LDAP provider");
155b28a5538SAdam Hornacek             return;
156b28a5538SAdam Hornacek         }
157b28a5538SAdam Hornacek 
15802df4614SVladimir Kotal         String dn = null;
15982105c7dSVladimir Kotal         if (Boolean.TRUE.equals(useDN)) {
16002df4614SVladimir Kotal             dn = user.getUsername();
1615aedecbdSVladimir Kotal             LOGGER.log(Level.FINEST, "using DN ''{0}'' for user {1}", new Object[]{dn, user});
16202df4614SVladimir Kotal         }
163cbd2aafbSVladimir Kotal 
164cbd2aafbSVladimir Kotal         String expandedFilter = null;
16553c33ae5SVladimir Kotal         if (ldapFilter != null) {
16653c33ae5SVladimir Kotal             expandedFilter = expandFilter(user);
167cbd2aafbSVladimir Kotal             LOGGER.log(Level.FINEST, "expanded filter for user {0} into ''{1}''",
168cbd2aafbSVladimir Kotal                     new Object[]{user, expandedFilter});
16953c33ae5SVladimir Kotal         }
170cbd2aafbSVladimir Kotal 
171cbd2aafbSVladimir Kotal         AbstractLdapProvider ldapProvider = getLdapProvider();
172b28a5538SAdam Hornacek         try {
17317deb9edSVladimir Kotal             AbstractLdapProvider.LdapSearchResult<Map<String, Set<String>>> res;
174*d630fdc8SVladimir Kotal             if ((res = ldapProvider.lookupLdapContent(dn, expandedFilter, attrSet.toArray(new String[0]))) == null) {
175afd2f2f7SVladimir Kotal                 LOGGER.log(Level.WARNING, "failed to get LDAP attributes ''{2}'' for user {0} " +
176e5567996SVladimir Kotal                                 "with filter ''{1}'' from LDAP provider {3}",
17782105c7dSVladimir Kotal                         new Object[]{user, expandedFilter, attrSet, getLdapProvider()});
178*d630fdc8SVladimir Kotal                 LdapUser ldapUser = new LdapUser(dn, null);
179*d630fdc8SVladimir Kotal                 ldapUser.setAttribute(NEGATIVE_CACHE_ATTR, Collections.singleton(null));
180*d630fdc8SVladimir Kotal                 updateSession(req, ldapUser);
181b28a5538SAdam Hornacek                 return;
182b28a5538SAdam Hornacek             }
18317deb9edSVladimir Kotal 
18417deb9edSVladimir Kotal             records = res.getAttrs();
18582105c7dSVladimir Kotal             if (Boolean.FALSE.equals(useDN)) {
18617deb9edSVladimir Kotal                 dn = res.getDN();
187*d630fdc8SVladimir Kotal                 LOGGER.log(Level.FINEST, "got DN ''{0}'' for user {1}", new Object[]{dn, user});
18802df4614SVladimir Kotal             }
189b28a5538SAdam Hornacek         } catch (LdapException ex) {
190b28a5538SAdam Hornacek             throw new AuthorizationException(ex);
191b28a5538SAdam Hornacek         }
192b28a5538SAdam Hornacek 
193b28a5538SAdam Hornacek         if (records.isEmpty()) {
194cbd2aafbSVladimir Kotal             LOGGER.log(Level.WARNING, "LDAP records for user {0} are empty on {1}",
195cbd2aafbSVladimir Kotal                     new Object[]{user, ldapProvider});
196b28a5538SAdam Hornacek             return;
197b28a5538SAdam Hornacek         }
198b28a5538SAdam Hornacek 
19982105c7dSVladimir Kotal         for (String attrName : attrSet) {
20002df4614SVladimir Kotal             if (!records.containsKey(attrName) || records.get(attrName) == null || records.get(attrName).isEmpty()) {
201cbd2aafbSVladimir Kotal                 LOGGER.log(Level.WARNING, "''{0}'' record for user {1} is not present or empty on {2}",
202cbd2aafbSVladimir Kotal                         new Object[]{attrName, user, ldapProvider});
203b28a5538SAdam Hornacek             }
204b28a5538SAdam Hornacek         }
205b28a5538SAdam Hornacek 
20682105c7dSVladimir Kotal         Map<String, Set<String>> userAttrSet = new HashMap<>();
20782105c7dSVladimir Kotal         for (String attrName : this.attrSet) {
20882105c7dSVladimir Kotal             userAttrSet.put(attrName, records.get(attrName));
209b28a5538SAdam Hornacek         }
210b28a5538SAdam Hornacek 
211*d630fdc8SVladimir Kotal         LOGGER.log(Level.FINEST, "DN for user {0} is ''{1}'' on {2}", new Object[]{user, dn, ldapProvider});
21282105c7dSVladimir Kotal         updateSession(req, new LdapUser(dn, userAttrSet));
213b28a5538SAdam Hornacek     }
214b28a5538SAdam Hornacek 
215b28a5538SAdam Hornacek     /**
216b28a5538SAdam Hornacek      * Add a new user value into the session.
217b28a5538SAdam Hornacek      *
218b28a5538SAdam Hornacek      * @param req the request
219b28a5538SAdam Hornacek      * @param user the new value for user
220b28a5538SAdam Hornacek      */
updateSession(@otNull HttpServletRequest req, LdapUser user)221*d630fdc8SVladimir Kotal     void updateSession(@NotNull HttpServletRequest req, LdapUser user) {
222d7630e3aSVladimir Kotal         req.getSession().setAttribute(getSessionAttrName(), user);
22366cf937cSVladimir Kotal     }
22466cf937cSVladimir Kotal 
getSessionAttrName(Integer instance)225d7630e3aSVladimir Kotal     static String getSessionAttrName(Integer instance) {
22666cf937cSVladimir Kotal         return (SESSION_ATTR + (instance != null ? instance.toString() : ""));
227b28a5538SAdam Hornacek     }
228b28a5538SAdam Hornacek 
getSessionAttrName()229d7630e3aSVladimir Kotal     private String getSessionAttrName() {
23082105c7dSVladimir Kotal         return getSessionAttrName(instanceNum);
231d7630e3aSVladimir Kotal     }
232d7630e3aSVladimir Kotal 
checkUser(@otNull HttpServletRequest request)233*d630fdc8SVladimir Kotal     private boolean checkUser(@NotNull HttpServletRequest request) {
234*d630fdc8SVladimir Kotal         LdapUser ldapUser = (LdapUser) request.getSession().getAttribute(getSessionAttrName());
235*d630fdc8SVladimir Kotal         return ldapUser != null && ldapUser.getAttribute(NEGATIVE_CACHE_ATTR) == null;
236*d630fdc8SVladimir Kotal     }
237*d630fdc8SVladimir Kotal 
238b28a5538SAdam Hornacek     @Override
checkEntity(HttpServletRequest request, Project project)239b28a5538SAdam Hornacek     public boolean checkEntity(HttpServletRequest request, Project project) {
240*d630fdc8SVladimir Kotal         return checkUser(request);
241b28a5538SAdam Hornacek     }
242b28a5538SAdam Hornacek 
243b28a5538SAdam Hornacek     @Override
checkEntity(HttpServletRequest request, Group group)244b28a5538SAdam Hornacek     public boolean checkEntity(HttpServletRequest request, Group group) {
245*d630fdc8SVladimir Kotal         return checkUser(request);
246b28a5538SAdam Hornacek     }
247b28a5538SAdam Hornacek }
248