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