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