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