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