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