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