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