xref: /OpenGrok/plugins/src/main/java/opengrok/auth/plugin/AbstractLdapPlugin.java (revision 19f90b674f696a6c8f6f9b48059100e00a3f435b)
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 /*
21*19f90b67SVladimir Kotal  * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
22b28a5538SAdam Hornacek  */
23b28a5538SAdam Hornacek package opengrok.auth.plugin;
24b28a5538SAdam Hornacek 
25b28a5538SAdam Hornacek import java.io.File;
26b28a5538SAdam Hornacek import java.io.IOException;
27b28a5538SAdam Hornacek import java.util.Map;
28b28a5538SAdam Hornacek import java.util.concurrent.ConcurrentHashMap;
29aa6abf42SAdam Hornacek 
30aa6abf42SAdam Hornacek import jakarta.servlet.http.HttpServletRequest;
31b28a5538SAdam Hornacek import opengrok.auth.plugin.configuration.Configuration;
32b28a5538SAdam Hornacek import opengrok.auth.plugin.entity.User;
33b28a5538SAdam Hornacek import opengrok.auth.plugin.ldap.AbstractLdapProvider;
34b28a5538SAdam Hornacek import opengrok.auth.plugin.ldap.LdapFacade;
35b28a5538SAdam Hornacek import org.opengrok.indexer.authorization.IAuthorizationPlugin;
36b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Group;
37b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Project;
38b28a5538SAdam Hornacek 
39b28a5538SAdam Hornacek /**
40b28a5538SAdam Hornacek  * Abstract class for all plug-ins working with LDAP. Takes care of
414a04c503SChris Fraire  * <p><ul>
42b28a5538SAdam Hornacek  * <li>controlling the established session</li>
43b28a5538SAdam Hornacek  * <li>controlling if the session belongs to the user</li>
44b28a5538SAdam Hornacek  * </ul>
45b28a5538SAdam Hornacek  *
46b28a5538SAdam Hornacek  * <p>
47b28a5538SAdam Hornacek  * The intended methods to implement are the
48b28a5538SAdam Hornacek  * {@link #checkEntity(HttpServletRequest, Project)} and
49b28a5538SAdam Hornacek  * {@link #checkEntity(HttpServletRequest, Group)}.
50b28a5538SAdam Hornacek  *
51b28a5538SAdam Hornacek  * @author Krystof Tulinger
52b28a5538SAdam Hornacek  */
53b28a5538SAdam Hornacek public abstract class AbstractLdapPlugin implements IAuthorizationPlugin {
54b28a5538SAdam Hornacek 
55b28a5538SAdam Hornacek     /**
56b28a5538SAdam Hornacek      * This is used to ensure that every instance of this plug-in has its own
57b28a5538SAdam Hornacek      * unique name for its session parameters.
58b28a5538SAdam Hornacek      */
598dbee6eeSVladimir Kotal     protected static long nextId = 1;
60b28a5538SAdam Hornacek 
61b28a5538SAdam Hornacek     protected static final String CONFIGURATION_PARAM = "configuration";
62b28a5538SAdam Hornacek 
63b28a5538SAdam Hornacek     private static final String SESSION_PREFIX = "opengrok-abstract-ldap-plugin-";
648dbee6eeSVladimir Kotal     protected String sessionUsername = SESSION_PREFIX + "username";
658dbee6eeSVladimir Kotal     protected String sessionEstablished = SESSION_PREFIX + "session-established";
66b28a5538SAdam Hornacek 
67b28a5538SAdam Hornacek     /**
68b28a5538SAdam Hornacek      * Configuration for the LDAP servers.
69b28a5538SAdam Hornacek      */
70b28a5538SAdam Hornacek     private Configuration cfg;
71b28a5538SAdam Hornacek 
72b28a5538SAdam Hornacek     /**
73b28a5538SAdam Hornacek      * Map of currently used configurations.<br>
74b28a5538SAdam Hornacek      * file path => object.
75b28a5538SAdam Hornacek      */
76b28a5538SAdam Hornacek     private static final Map<String, Configuration> LOADED_CONFIGURATIONS = new ConcurrentHashMap<>();
77b28a5538SAdam Hornacek 
78b28a5538SAdam Hornacek     /**
79b28a5538SAdam Hornacek      * LDAP lookup facade.
80b28a5538SAdam Hornacek      */
81b28a5538SAdam Hornacek     private AbstractLdapProvider ldapProvider;
82b28a5538SAdam Hornacek 
AbstractLdapPlugin()838dbee6eeSVladimir Kotal     protected AbstractLdapPlugin() {
848dbee6eeSVladimir Kotal         sessionUsername += "-" + nextId;
858dbee6eeSVladimir Kotal         sessionEstablished += "-" + nextId;
86b28a5538SAdam Hornacek         nextId++;
87b28a5538SAdam Hornacek     }
88b28a5538SAdam Hornacek 
89b28a5538SAdam Hornacek     /**
90b28a5538SAdam Hornacek      * Fill the session with some information related to the subclass.
91b28a5538SAdam Hornacek      *
92b28a5538SAdam Hornacek      * @param req the current request
93b28a5538SAdam Hornacek      * @param user user decoded from the headers
94b28a5538SAdam Hornacek      */
fillSession(HttpServletRequest req, User user)95b28a5538SAdam Hornacek     public abstract void fillSession(HttpServletRequest req, User user);
96b28a5538SAdam Hornacek 
97b28a5538SAdam Hornacek     /**
98b28a5538SAdam Hornacek      * Decide if the project should be allowed for this request.
99b28a5538SAdam Hornacek      *
100b28a5538SAdam Hornacek      * @param request the request
101b28a5538SAdam Hornacek      * @param project the project
102b28a5538SAdam Hornacek      * @return true if yes; false otherwise
103b28a5538SAdam Hornacek      */
checkEntity(HttpServletRequest request, Project project)104b28a5538SAdam Hornacek     public abstract boolean checkEntity(HttpServletRequest request, Project project);
105b28a5538SAdam Hornacek 
106b28a5538SAdam Hornacek     /**
107b28a5538SAdam Hornacek      * Decide if the group should be allowed for this request.
108b28a5538SAdam Hornacek      *
109b28a5538SAdam Hornacek      * @param request the request
110b28a5538SAdam Hornacek      * @param group the group
111b28a5538SAdam Hornacek      * @return true if yes; false otherwise
112b28a5538SAdam Hornacek      */
checkEntity(HttpServletRequest request, Group group)113b28a5538SAdam Hornacek     public abstract boolean checkEntity(HttpServletRequest request, Group group);
114b28a5538SAdam Hornacek 
1153c16dad8SVladimir Kotal     // for testing
load(AbstractLdapProvider provider)1163c16dad8SVladimir Kotal     void load(AbstractLdapProvider provider) {
1173c16dad8SVladimir Kotal         ldapProvider = provider;
1183c16dad8SVladimir Kotal     }
1193c16dad8SVladimir Kotal 
120b28a5538SAdam Hornacek     /**
121b28a5538SAdam Hornacek      * Loads the configuration into memory.
122b28a5538SAdam Hornacek      */
123b28a5538SAdam Hornacek     @Override
load(Map<String, Object> parameters)124b28a5538SAdam Hornacek     public void load(Map<String, Object> parameters) {
125b28a5538SAdam Hornacek         String configurationPath;
126b28a5538SAdam Hornacek 
127b28a5538SAdam Hornacek         if ((configurationPath = (String) parameters.get(CONFIGURATION_PARAM)) == null) {
128b28a5538SAdam Hornacek             throw new NullPointerException("Missing param [" + CONFIGURATION_PARAM + "]");
129b28a5538SAdam Hornacek         }
130b28a5538SAdam Hornacek 
131b28a5538SAdam Hornacek         try {
132b28a5538SAdam Hornacek             cfg = getConfiguration(configurationPath);
133b28a5538SAdam Hornacek             ldapProvider = new LdapFacade(cfg);
134b28a5538SAdam Hornacek         } catch (IOException ex) {
135b28a5538SAdam Hornacek             throw new IllegalArgumentException("Unable to read the configuration", ex);
136b28a5538SAdam Hornacek         }
137b28a5538SAdam Hornacek     }
138b28a5538SAdam Hornacek 
139b28a5538SAdam Hornacek     /**
140b28a5538SAdam Hornacek      * Return the configuration for the given path. If the configuration is
141b28a5538SAdam Hornacek      * already loaded, use that one. Otherwise try to load the file into the
142b28a5538SAdam Hornacek      * configuration.
143b28a5538SAdam Hornacek      *
144b28a5538SAdam Hornacek      * @param configurationPath the path to the file with the configuration
145b28a5538SAdam Hornacek      * @return the object (new or from cache)
146b28a5538SAdam Hornacek      * @throws IOException when any IO error occurs
147b28a5538SAdam Hornacek      */
getConfiguration(String configurationPath)148b28a5538SAdam Hornacek     protected Configuration getConfiguration(String configurationPath) throws IOException {
149b28a5538SAdam Hornacek         if ((cfg = LOADED_CONFIGURATIONS.get(configurationPath)) == null) {
150b28a5538SAdam Hornacek             LOADED_CONFIGURATIONS.put(configurationPath, cfg =
151b28a5538SAdam Hornacek                     Configuration.read(new File(configurationPath)));
152b28a5538SAdam Hornacek         }
153b28a5538SAdam Hornacek         return cfg;
154b28a5538SAdam Hornacek     }
155b28a5538SAdam Hornacek 
156b28a5538SAdam Hornacek     /**
157b28a5538SAdam Hornacek      * Closes the LDAP connections.
158b28a5538SAdam Hornacek      */
159b28a5538SAdam Hornacek     @Override
unload()160b28a5538SAdam Hornacek     public void unload() {
161b28a5538SAdam Hornacek         if (ldapProvider != null) {
162b28a5538SAdam Hornacek             ldapProvider.close();
163b28a5538SAdam Hornacek             ldapProvider = null;
164b28a5538SAdam Hornacek         }
165b28a5538SAdam Hornacek         cfg = null;
166b28a5538SAdam Hornacek     }
167b28a5538SAdam Hornacek 
168b28a5538SAdam Hornacek     /**
169b28a5538SAdam Hornacek      * Return the configuration object.
170b28a5538SAdam Hornacek      *
171b28a5538SAdam Hornacek      * @return the configuration
172b28a5538SAdam Hornacek      */
getConfiguration()173b28a5538SAdam Hornacek     public Configuration getConfiguration() {
174b28a5538SAdam Hornacek         return cfg;
175b28a5538SAdam Hornacek     }
176b28a5538SAdam Hornacek 
177b28a5538SAdam Hornacek     /**
178b28a5538SAdam Hornacek      * Return the LDAP provider.
179b28a5538SAdam Hornacek      *
180b28a5538SAdam Hornacek      * @return the LDAP provider
181b28a5538SAdam Hornacek      */
getLdapProvider()182b28a5538SAdam Hornacek     public AbstractLdapProvider getLdapProvider() {
183b28a5538SAdam Hornacek         return ldapProvider;
184b28a5538SAdam Hornacek     }
185b28a5538SAdam Hornacek 
186b28a5538SAdam Hornacek     /**
187b28a5538SAdam Hornacek      * Check if the session user corresponds to the authenticated user.
188b28a5538SAdam Hornacek      *
189b28a5538SAdam Hornacek      * @param sessionUsername user from the session
190b28a5538SAdam Hornacek      * @param authUser user from the request
191b28a5538SAdam Hornacek      * @return true if it does; false otherwise
192b28a5538SAdam Hornacek      */
isSameUser(String sessionUsername, String authUser)193b28a5538SAdam Hornacek     protected boolean isSameUser(String sessionUsername, String authUser) {
194b28a5538SAdam Hornacek         return sessionUsername != null
195b28a5538SAdam Hornacek                 && sessionUsername.equals(authUser);
196b28a5538SAdam Hornacek     }
197b28a5538SAdam Hornacek 
198b28a5538SAdam Hornacek     /**
199b28a5538SAdam Hornacek      * Check if the session exists and contains all necessary fields required by
200b28a5538SAdam Hornacek      * this plug-in.
201b28a5538SAdam Hornacek      *
202b28a5538SAdam Hornacek      * @param req the HTTP request
203b28a5538SAdam Hornacek      * @return true if it does; false otherwise
204b28a5538SAdam Hornacek      */
sessionExists(HttpServletRequest req)205b28a5538SAdam Hornacek     protected boolean sessionExists(HttpServletRequest req) {
206b28a5538SAdam Hornacek         return req != null && req.getSession() != null
2078dbee6eeSVladimir Kotal                 && req.getSession().getAttribute(sessionEstablished) != null
2088dbee6eeSVladimir Kotal                 && req.getSession().getAttribute(sessionUsername) != null;
209b28a5538SAdam Hornacek     }
210b28a5538SAdam Hornacek 
211b28a5538SAdam Hornacek     /**
212b28a5538SAdam Hornacek      * Ensures that after the call the session for the user will be created with
213b28a5538SAdam Hornacek      * appropriate fields. If any error occurs during the call which might be:
214b28a5538SAdam Hornacek      * <ul>
215b28a5538SAdam Hornacek      * <li>The user has not been authenticated</li>
216b28a5538SAdam Hornacek      * <li>The user can not be retrieved from LDAP</li>
217b28a5538SAdam Hornacek      * <li>There are no records for authorization for the user</li>
218b28a5538SAdam Hornacek      * </ul>
219b28a5538SAdam Hornacek      * the session is established as an empty session to avoid any exception in
220b28a5538SAdam Hornacek      * the caller.
221b28a5538SAdam Hornacek      *
222b28a5538SAdam Hornacek      * @param req the HTTP request
223b28a5538SAdam Hornacek      */
ensureSessionExists(HttpServletRequest req)224b28a5538SAdam Hornacek     private void ensureSessionExists(HttpServletRequest req) {
225b28a5538SAdam Hornacek         if (req.getSession() == null) {
226b28a5538SAdam Hornacek             // old/invalid request (should not happen)
227b28a5538SAdam Hornacek             return;
228b28a5538SAdam Hornacek         }
229b28a5538SAdam Hornacek 
230b28a5538SAdam Hornacek         // The cast to User should not be problem as this object is stored
231b28a5538SAdam Hornacek         // in the request itself (as opposed to in the session).
232b28a5538SAdam Hornacek         User user;
233b28a5538SAdam Hornacek         if ((user = (User) req.getAttribute(UserPlugin.REQUEST_ATTR)) == null) {
234b28a5538SAdam Hornacek             updateSession(req, null, false);
235b28a5538SAdam Hornacek             return;
236b28a5538SAdam Hornacek         }
237b28a5538SAdam Hornacek 
238b28a5538SAdam Hornacek         if (sessionExists(req)
239b28a5538SAdam Hornacek                 // we've already filled the groups and projects
2408dbee6eeSVladimir Kotal                 && (boolean) req.getSession().getAttribute(sessionEstablished)
241b28a5538SAdam Hornacek                 // the session belongs to the user from the request
2428dbee6eeSVladimir Kotal                 && isSameUser((String) req.getSession().getAttribute(sessionUsername), user.getUsername())) {
2438dbee6eeSVladimir Kotal             /*
2448dbee6eeSVladimir Kotal              * The session is already filled so no need to call updateSession().
245b28a5538SAdam Hornacek              */
246b28a5538SAdam Hornacek             return;
247b28a5538SAdam Hornacek         }
248b28a5538SAdam Hornacek 
249b28a5538SAdam Hornacek         updateSession(req, user.getUsername(), false);
250b28a5538SAdam Hornacek 
251b28a5538SAdam Hornacek         if (ldapProvider == null) {
252b28a5538SAdam Hornacek             return;
253b28a5538SAdam Hornacek         }
254b28a5538SAdam Hornacek 
255b28a5538SAdam Hornacek         fillSession(req, user);
256b28a5538SAdam Hornacek 
257b28a5538SAdam Hornacek         updateSession(req, user.getUsername(), true);
258b28a5538SAdam Hornacek     }
259b28a5538SAdam Hornacek 
260b28a5538SAdam Hornacek     /**
261b28a5538SAdam Hornacek      * Fill the session with new values.
262b28a5538SAdam Hornacek      *
263b28a5538SAdam Hornacek      * @param req the request
264b28a5538SAdam Hornacek      * @param username new username
265b28a5538SAdam Hornacek      * @param established new value for established
266b28a5538SAdam Hornacek      */
updateSession(HttpServletRequest req, String username, boolean established)267*19f90b67SVladimir Kotal     protected void updateSession(HttpServletRequest req, String username, boolean established) {
268b28a5538SAdam Hornacek         setSessionEstablished(req, established);
269b28a5538SAdam Hornacek         setSessionUsername(req, username);
270b28a5538SAdam Hornacek     }
271b28a5538SAdam Hornacek 
272b28a5538SAdam Hornacek     /**
273b28a5538SAdam Hornacek      * Set session established flag into the session.
274b28a5538SAdam Hornacek      *
275b28a5538SAdam Hornacek      * @param req request containing the session
276b28a5538SAdam Hornacek      * @param value the value
277b28a5538SAdam Hornacek      */
setSessionEstablished(HttpServletRequest req, Boolean value)278b28a5538SAdam Hornacek     protected void setSessionEstablished(HttpServletRequest req, Boolean value) {
2798dbee6eeSVladimir Kotal         req.getSession().setAttribute(sessionEstablished, value);
280b28a5538SAdam Hornacek     }
281b28a5538SAdam Hornacek 
282b28a5538SAdam Hornacek     /**
283b28a5538SAdam Hornacek      * Set session username for the user.
284b28a5538SAdam Hornacek      *
285b28a5538SAdam Hornacek      * @param req request containing the session
286b28a5538SAdam Hornacek      * @param value the value
287b28a5538SAdam Hornacek      */
setSessionUsername(HttpServletRequest req, String value)288b28a5538SAdam Hornacek     protected void setSessionUsername(HttpServletRequest req, String value) {
2898dbee6eeSVladimir Kotal         req.getSession().setAttribute(sessionUsername, value);
290b28a5538SAdam Hornacek     }
291b28a5538SAdam Hornacek 
292b28a5538SAdam Hornacek     @Override
isAllowed(HttpServletRequest request, Project project)293b28a5538SAdam Hornacek     public boolean isAllowed(HttpServletRequest request, Project project) {
294b28a5538SAdam Hornacek         ensureSessionExists(request);
295b28a5538SAdam Hornacek 
296b28a5538SAdam Hornacek         if (request.getSession() == null) {
297b28a5538SAdam Hornacek             return false;
298b28a5538SAdam Hornacek         }
299b28a5538SAdam Hornacek 
300b28a5538SAdam Hornacek         return checkEntity(request, project);
301b28a5538SAdam Hornacek     }
302b28a5538SAdam Hornacek 
303b28a5538SAdam Hornacek     @Override
isAllowed(HttpServletRequest request, Group group)304b28a5538SAdam Hornacek     public boolean isAllowed(HttpServletRequest request, Group group) {
305b28a5538SAdam Hornacek         ensureSessionExists(request);
306b28a5538SAdam Hornacek 
307b28a5538SAdam Hornacek         if (request.getSession() == null) {
308b28a5538SAdam Hornacek             return false;
309b28a5538SAdam Hornacek         }
310b28a5538SAdam Hornacek 
311b28a5538SAdam Hornacek         return checkEntity(request, group);
312b28a5538SAdam Hornacek     }
313b28a5538SAdam Hornacek }
314