xref: /OpenGrok/plugins/src/main/java/opengrok/auth/plugin/LdapUserPlugin.java (revision d630fdc80623f85cf43a81c7f7168517ae5f6794)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
22  */
23 package opengrok.auth.plugin;
24 
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.logging.Level;
32 import java.util.logging.Logger;
33 
34 import jakarta.servlet.http.HttpServletRequest;
35 import opengrok.auth.entity.LdapUser;
36 import opengrok.auth.plugin.entity.User;
37 import opengrok.auth.plugin.ldap.AbstractLdapProvider;
38 import opengrok.auth.plugin.ldap.LdapException;
39 import org.jetbrains.annotations.NotNull;
40 import org.opengrok.indexer.authorization.AuthorizationException;
41 import org.opengrok.indexer.configuration.Group;
42 import org.opengrok.indexer.configuration.Project;
43 
44 import static opengrok.auth.plugin.util.FilterUtil.expandUserFilter;
45 
46 /**
47  * Authorization plug-in to extract user's LDAP attributes.
48  * The attributes can be then used by the other LDAP plugins down the stack.
49  *
50  * @author Krystof Tulinger
51  */
52 public class LdapUserPlugin extends AbstractLdapPlugin {
53 
54     private static final Logger LOGGER = Logger.getLogger(LdapUserPlugin.class.getName());
55 
56     static final String SESSION_ATTR = "opengrok-ldap-plugin-user";
57     static final String NEGATIVE_CACHE_ATTR = "opengrok-ldap-plugin-user-invalid-user";
58 
59     /**
60      * List of configuration names.
61      * <ul>
62      * <li><code>filter</code> is LDAP filter used for searching (optional)</li>
63      * <li><code>useDN</code> boolean value indicating if User.username should be treated as Distinguished Name
64      * (optional, default is false)</li>
65      * <li><code>attributes</code> is comma separated list of LDAP attributes to be produced (mandatory)</li>
66      * <li><code>instance</code> integer that can be used to identify instance of this plugin by other LDAP plugins
67      * (optional, default empty)</li>
68      * </ul>
69      */
70     static final String LDAP_FILTER = "filter";
71     static final String ATTRIBUTES = "attributes";
72     static final String USE_DN = "useDN";
73     static final String INSTANCE = "instance";
74 
75     private String ldapFilter;
76     private Boolean useDN;
77     private Set<String> attrSet;
78     private Integer instanceNum;
79 
80     // for testing
load(Map<String, Object> parameters, AbstractLdapProvider provider)81     void load(Map<String, Object> parameters, AbstractLdapProvider provider) {
82         super.load(provider);
83 
84         init(parameters);
85     }
86 
87     @Override
load(Map<String, Object> parameters)88     public void load(Map<String, Object> parameters) {
89         super.load(parameters);
90 
91         init(parameters);
92     }
93 
init(Map<String, Object> parameters)94     private void init(Map<String, Object> parameters) {
95         String attributesVal;
96         if ((attributesVal = (String) parameters.get(ATTRIBUTES)) == null) {
97             throw new NullPointerException("Missing configuration parameter [" + ATTRIBUTES + "] in the setup");
98         }
99         attrSet = new HashSet<>(Arrays.asList(attributesVal.split(",")));
100 
101         ldapFilter = (String) parameters.get(LDAP_FILTER);
102 
103         if ((useDN = (Boolean) parameters.get(USE_DN)) == null) {
104             useDN = false;
105         }
106 
107         String instanceParam = (String) parameters.get(INSTANCE);
108         if (instanceParam != null) {
109             instanceNum = Integer.parseInt(instanceParam);
110         }
111 
112         LOGGER.log(Level.FINE, "LdapUser plugin loaded with filter={0}, " +
113                         "attributes={1}, useDN={2}, instance={3}",
114                 new Object[]{ldapFilter, attrSet, useDN, instanceNum});
115     }
116 
117     /**
118      * Check if the session exists and contains all necessary fields required by
119      * this plug-in.
120      *
121      * @param req the HTTP request
122      * @return true if it does; false otherwise
123      */
124     @Override
sessionExists(HttpServletRequest req)125     protected boolean sessionExists(HttpServletRequest req) {
126         return super.sessionExists(req) && req.getSession().getAttribute(getSessionAttrName()) != null;
127     }
128 
129     /**
130      * Expand {@code User} object attribute values into the filter.
131      *
132      * @see opengrok.auth.plugin.util.FilterUtil
133      *
134      * Use \% for printing the '%' character.
135      *
136      * @param user User object from the request (created by {@code UserPlugin})
137      * @return replaced result
138      */
expandFilter(User user)139     String expandFilter(User user) {
140         String filter = ldapFilter;
141         filter = expandUserFilter(user, filter);
142         filter = filter.replace("\\%", "%");
143 
144         return filter;
145     }
146 
147     @Override
fillSession(HttpServletRequest req, User user)148     public void fillSession(HttpServletRequest req, User user) {
149         Map<String, Set<String>> records;
150 
151         updateSession(req, null);
152 
153         if (getLdapProvider() == null) {
154             LOGGER.log(Level.WARNING, "cannot get LDAP provider");
155             return;
156         }
157 
158         String dn = null;
159         if (Boolean.TRUE.equals(useDN)) {
160             dn = user.getUsername();
161             LOGGER.log(Level.FINEST, "using DN ''{0}'' for user {1}", new Object[]{dn, user});
162         }
163 
164         String expandedFilter = null;
165         if (ldapFilter != null) {
166             expandedFilter = expandFilter(user);
167             LOGGER.log(Level.FINEST, "expanded filter for user {0} into ''{1}''",
168                     new Object[]{user, expandedFilter});
169         }
170 
171         AbstractLdapProvider ldapProvider = getLdapProvider();
172         try {
173             AbstractLdapProvider.LdapSearchResult<Map<String, Set<String>>> res;
174             if ((res = ldapProvider.lookupLdapContent(dn, expandedFilter, attrSet.toArray(new String[0]))) == null) {
175                 LOGGER.log(Level.WARNING, "failed to get LDAP attributes ''{2}'' for user {0} " +
176                                 "with filter ''{1}'' from LDAP provider {3}",
177                         new Object[]{user, expandedFilter, attrSet, getLdapProvider()});
178                 LdapUser ldapUser = new LdapUser(dn, null);
179                 ldapUser.setAttribute(NEGATIVE_CACHE_ATTR, Collections.singleton(null));
180                 updateSession(req, ldapUser);
181                 return;
182             }
183 
184             records = res.getAttrs();
185             if (Boolean.FALSE.equals(useDN)) {
186                 dn = res.getDN();
187                 LOGGER.log(Level.FINEST, "got DN ''{0}'' for user {1}", new Object[]{dn, user});
188             }
189         } catch (LdapException ex) {
190             throw new AuthorizationException(ex);
191         }
192 
193         if (records.isEmpty()) {
194             LOGGER.log(Level.WARNING, "LDAP records for user {0} are empty on {1}",
195                     new Object[]{user, ldapProvider});
196             return;
197         }
198 
199         for (String attrName : attrSet) {
200             if (!records.containsKey(attrName) || records.get(attrName) == null || records.get(attrName).isEmpty()) {
201                 LOGGER.log(Level.WARNING, "''{0}'' record for user {1} is not present or empty on {2}",
202                         new Object[]{attrName, user, ldapProvider});
203             }
204         }
205 
206         Map<String, Set<String>> userAttrSet = new HashMap<>();
207         for (String attrName : this.attrSet) {
208             userAttrSet.put(attrName, records.get(attrName));
209         }
210 
211         LOGGER.log(Level.FINEST, "DN for user {0} is ''{1}'' on {2}", new Object[]{user, dn, ldapProvider});
212         updateSession(req, new LdapUser(dn, userAttrSet));
213     }
214 
215     /**
216      * Add a new user value into the session.
217      *
218      * @param req the request
219      * @param user the new value for user
220      */
updateSession(@otNull HttpServletRequest req, LdapUser user)221     void updateSession(@NotNull HttpServletRequest req, LdapUser user) {
222         req.getSession().setAttribute(getSessionAttrName(), user);
223     }
224 
getSessionAttrName(Integer instance)225     static String getSessionAttrName(Integer instance) {
226         return (SESSION_ATTR + (instance != null ? instance.toString() : ""));
227     }
228 
getSessionAttrName()229     private String getSessionAttrName() {
230         return getSessionAttrName(instanceNum);
231     }
232 
checkUser(@otNull HttpServletRequest request)233     private boolean checkUser(@NotNull HttpServletRequest request) {
234         LdapUser ldapUser = (LdapUser) request.getSession().getAttribute(getSessionAttrName());
235         return ldapUser != null && ldapUser.getAttribute(NEGATIVE_CACHE_ATTR) == null;
236     }
237 
238     @Override
checkEntity(HttpServletRequest request, Project project)239     public boolean checkEntity(HttpServletRequest request, Project project) {
240         return checkUser(request);
241     }
242 
243     @Override
checkEntity(HttpServletRequest request, Group group)244     public boolean checkEntity(HttpServletRequest request, Group group) {
245         return checkUser(request);
246     }
247 }
248