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