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 /* 215aedecbdSVladimir Kotal * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. 22b28a5538SAdam Hornacek */ 23b28a5538SAdam Hornacek package opengrok.auth.plugin; 24b28a5538SAdam Hornacek 2553c33ae5SVladimir Kotal import java.util.Arrays; 26*d630fdc8SVladimir Kotal import java.util.Collections; 27b28a5538SAdam Hornacek import java.util.HashMap; 2853c33ae5SVladimir Kotal import java.util.HashSet; 29b28a5538SAdam Hornacek import java.util.Map; 30b28a5538SAdam Hornacek import java.util.Set; 31b28a5538SAdam Hornacek import java.util.logging.Level; 32b28a5538SAdam Hornacek import java.util.logging.Logger; 33aa6abf42SAdam Hornacek 34aa6abf42SAdam Hornacek import jakarta.servlet.http.HttpServletRequest; 35b28a5538SAdam Hornacek import opengrok.auth.entity.LdapUser; 36b28a5538SAdam Hornacek import opengrok.auth.plugin.entity.User; 3717deb9edSVladimir Kotal import opengrok.auth.plugin.ldap.AbstractLdapProvider; 38b28a5538SAdam Hornacek import opengrok.auth.plugin.ldap.LdapException; 39*d630fdc8SVladimir Kotal import org.jetbrains.annotations.NotNull; 40b28a5538SAdam Hornacek import org.opengrok.indexer.authorization.AuthorizationException; 41b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Group; 42b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Project; 43b28a5538SAdam Hornacek 44f305899cSVladimir Kotal import static opengrok.auth.plugin.util.FilterUtil.expandUserFilter; 45f305899cSVladimir Kotal 46b28a5538SAdam Hornacek /** 47b28a5538SAdam Hornacek * Authorization plug-in to extract user's LDAP attributes. 48b28a5538SAdam Hornacek * The attributes can be then used by the other LDAP plugins down the stack. 49b28a5538SAdam Hornacek * 50b28a5538SAdam Hornacek * @author Krystof Tulinger 51b28a5538SAdam Hornacek */ 52b28a5538SAdam Hornacek public class LdapUserPlugin extends AbstractLdapPlugin { 53b28a5538SAdam Hornacek 54b28a5538SAdam Hornacek private static final Logger LOGGER = Logger.getLogger(LdapUserPlugin.class.getName()); 55b28a5538SAdam Hornacek 56bf4d2fd4SVladimir Kotal static final String SESSION_ATTR = "opengrok-ldap-plugin-user"; 57*d630fdc8SVladimir Kotal static final String NEGATIVE_CACHE_ATTR = "opengrok-ldap-plugin-user-invalid-user"; 58b28a5538SAdam Hornacek 5953c33ae5SVladimir Kotal /** 600e7ff20bSVladimir Kotal * List of configuration names. 6153c33ae5SVladimir Kotal * <ul> 6253c33ae5SVladimir Kotal * <li><code>filter</code> is LDAP filter used for searching (optional)</li> 635aedecbdSVladimir Kotal * <li><code>useDN</code> boolean value indicating if User.username should be treated as Distinguished Name 645aedecbdSVladimir Kotal * (optional, default is false)</li> 6553c33ae5SVladimir Kotal * <li><code>attributes</code> is comma separated list of LDAP attributes to be produced (mandatory)</li> 665aedecbdSVladimir Kotal * <li><code>instance</code> integer that can be used to identify instance of this plugin by other LDAP plugins 675aedecbdSVladimir Kotal * (optional, default empty)</li> 6853c33ae5SVladimir Kotal * </ul> 6953c33ae5SVladimir Kotal */ 70bf4d2fd4SVladimir Kotal static final String LDAP_FILTER = "filter"; 71bf4d2fd4SVladimir Kotal static final String ATTRIBUTES = "attributes"; 72bf4d2fd4SVladimir Kotal static final String USE_DN = "useDN"; 73bf4d2fd4SVladimir Kotal static final String INSTANCE = "instance"; 74b28a5538SAdam Hornacek 7553c33ae5SVladimir Kotal private String ldapFilter; 7653c33ae5SVladimir Kotal private Boolean useDN; 7782105c7dSVladimir Kotal private Set<String> attrSet; 7882105c7dSVladimir Kotal private Integer instanceNum; 79b28a5538SAdam Hornacek 803c16dad8SVladimir Kotal // for testing load(Map<String, Object> parameters, AbstractLdapProvider provider)813c16dad8SVladimir Kotal void load(Map<String, Object> parameters, AbstractLdapProvider provider) { 823c16dad8SVladimir Kotal super.load(provider); 833c16dad8SVladimir Kotal 843c16dad8SVladimir Kotal init(parameters); 853c16dad8SVladimir Kotal } 863c16dad8SVladimir Kotal 87b28a5538SAdam Hornacek @Override load(Map<String, Object> parameters)88b28a5538SAdam Hornacek public void load(Map<String, Object> parameters) { 89b28a5538SAdam Hornacek super.load(parameters); 90b28a5538SAdam Hornacek 913c16dad8SVladimir Kotal init(parameters); 923c16dad8SVladimir Kotal } 933c16dad8SVladimir Kotal init(Map<String, Object> parameters)943c16dad8SVladimir Kotal private void init(Map<String, Object> parameters) { 95b28a5538SAdam Hornacek String attributesVal; 96b28a5538SAdam Hornacek if ((attributesVal = (String) parameters.get(ATTRIBUTES)) == null) { 975aedecbdSVladimir Kotal throw new NullPointerException("Missing configuration parameter [" + ATTRIBUTES + "] in the setup"); 98b28a5538SAdam Hornacek } 9982105c7dSVladimir Kotal attrSet = new HashSet<>(Arrays.asList(attributesVal.split(","))); 100b28a5538SAdam Hornacek 10153c33ae5SVladimir Kotal ldapFilter = (String) parameters.get(LDAP_FILTER); 10253c33ae5SVladimir Kotal 10353c33ae5SVladimir Kotal if ((useDN = (Boolean) parameters.get(USE_DN)) == null) { 10453c33ae5SVladimir Kotal useDN = false; 10553c33ae5SVladimir Kotal } 10653c33ae5SVladimir Kotal 10782105c7dSVladimir Kotal String instanceParam = (String) parameters.get(INSTANCE); 10882105c7dSVladimir Kotal if (instanceParam != null) { 10982105c7dSVladimir Kotal instanceNum = Integer.parseInt(instanceParam); 110e173e36dSVladimir Kotal } 11166cf937cSVladimir Kotal 11253c33ae5SVladimir Kotal LOGGER.log(Level.FINE, "LdapUser plugin loaded with filter={0}, " + 11366cf937cSVladimir Kotal "attributes={1}, useDN={2}, instance={3}", 11482105c7dSVladimir Kotal new Object[]{ldapFilter, attrSet, useDN, instanceNum}); 115b28a5538SAdam Hornacek } 116b28a5538SAdam Hornacek 117b28a5538SAdam Hornacek /** 118b28a5538SAdam Hornacek * Check if the session exists and contains all necessary fields required by 119b28a5538SAdam Hornacek * this plug-in. 120b28a5538SAdam Hornacek * 121b28a5538SAdam Hornacek * @param req the HTTP request 122b28a5538SAdam Hornacek * @return true if it does; false otherwise 123b28a5538SAdam Hornacek */ 124b28a5538SAdam Hornacek @Override sessionExists(HttpServletRequest req)125b28a5538SAdam Hornacek protected boolean sessionExists(HttpServletRequest req) { 1265aedecbdSVladimir Kotal return super.sessionExists(req) && req.getSession().getAttribute(getSessionAttrName()) != null; 127b28a5538SAdam Hornacek } 128b28a5538SAdam Hornacek 12953c33ae5SVladimir Kotal /** 130f305899cSVladimir Kotal * Expand {@code User} object attribute values into the filter. 13153c33ae5SVladimir Kotal * 132f305899cSVladimir Kotal * @see opengrok.auth.plugin.util.FilterUtil 13353c33ae5SVladimir Kotal * 13453c33ae5SVladimir Kotal * Use \% for printing the '%' character. 13553c33ae5SVladimir Kotal * 136750d880bSVladimir Kotal * @param user User object from the request (created by {@code UserPlugin}) 13753c33ae5SVladimir Kotal * @return replaced result 13853c33ae5SVladimir Kotal */ expandFilter(User user)139bf4d2fd4SVladimir Kotal String expandFilter(User user) { 14053c33ae5SVladimir Kotal String filter = ldapFilter; 141f305899cSVladimir Kotal filter = expandUserFilter(user, filter); 1429d74bf96SVladimir Kotal filter = filter.replace("\\%", "%"); 14353c33ae5SVladimir Kotal 14453c33ae5SVladimir Kotal return filter; 145b28a5538SAdam Hornacek } 146b28a5538SAdam Hornacek 147b28a5538SAdam Hornacek @Override fillSession(HttpServletRequest req, User user)148b28a5538SAdam Hornacek public void fillSession(HttpServletRequest req, User user) { 149b28a5538SAdam Hornacek Map<String, Set<String>> records; 150b28a5538SAdam Hornacek 151b28a5538SAdam Hornacek updateSession(req, null); 152b28a5538SAdam Hornacek 153b28a5538SAdam Hornacek if (getLdapProvider() == null) { 15453c33ae5SVladimir Kotal LOGGER.log(Level.WARNING, "cannot get LDAP provider"); 155b28a5538SAdam Hornacek return; 156b28a5538SAdam Hornacek } 157b28a5538SAdam Hornacek 15802df4614SVladimir Kotal String dn = null; 15982105c7dSVladimir Kotal if (Boolean.TRUE.equals(useDN)) { 16002df4614SVladimir Kotal dn = user.getUsername(); 1615aedecbdSVladimir Kotal LOGGER.log(Level.FINEST, "using DN ''{0}'' for user {1}", new Object[]{dn, user}); 16202df4614SVladimir Kotal } 163cbd2aafbSVladimir Kotal 164cbd2aafbSVladimir Kotal String expandedFilter = null; 16553c33ae5SVladimir Kotal if (ldapFilter != null) { 16653c33ae5SVladimir Kotal expandedFilter = expandFilter(user); 167cbd2aafbSVladimir Kotal LOGGER.log(Level.FINEST, "expanded filter for user {0} into ''{1}''", 168cbd2aafbSVladimir Kotal new Object[]{user, expandedFilter}); 16953c33ae5SVladimir Kotal } 170cbd2aafbSVladimir Kotal 171cbd2aafbSVladimir Kotal AbstractLdapProvider ldapProvider = getLdapProvider(); 172b28a5538SAdam Hornacek try { 17317deb9edSVladimir Kotal AbstractLdapProvider.LdapSearchResult<Map<String, Set<String>>> res; 174*d630fdc8SVladimir Kotal if ((res = ldapProvider.lookupLdapContent(dn, expandedFilter, attrSet.toArray(new String[0]))) == null) { 175afd2f2f7SVladimir Kotal LOGGER.log(Level.WARNING, "failed to get LDAP attributes ''{2}'' for user {0} " + 176e5567996SVladimir Kotal "with filter ''{1}'' from LDAP provider {3}", 17782105c7dSVladimir Kotal new Object[]{user, expandedFilter, attrSet, getLdapProvider()}); 178*d630fdc8SVladimir Kotal LdapUser ldapUser = new LdapUser(dn, null); 179*d630fdc8SVladimir Kotal ldapUser.setAttribute(NEGATIVE_CACHE_ATTR, Collections.singleton(null)); 180*d630fdc8SVladimir Kotal updateSession(req, ldapUser); 181b28a5538SAdam Hornacek return; 182b28a5538SAdam Hornacek } 18317deb9edSVladimir Kotal 18417deb9edSVladimir Kotal records = res.getAttrs(); 18582105c7dSVladimir Kotal if (Boolean.FALSE.equals(useDN)) { 18617deb9edSVladimir Kotal dn = res.getDN(); 187*d630fdc8SVladimir Kotal LOGGER.log(Level.FINEST, "got DN ''{0}'' for user {1}", new Object[]{dn, user}); 18802df4614SVladimir Kotal } 189b28a5538SAdam Hornacek } catch (LdapException ex) { 190b28a5538SAdam Hornacek throw new AuthorizationException(ex); 191b28a5538SAdam Hornacek } 192b28a5538SAdam Hornacek 193b28a5538SAdam Hornacek if (records.isEmpty()) { 194cbd2aafbSVladimir Kotal LOGGER.log(Level.WARNING, "LDAP records for user {0} are empty on {1}", 195cbd2aafbSVladimir Kotal new Object[]{user, ldapProvider}); 196b28a5538SAdam Hornacek return; 197b28a5538SAdam Hornacek } 198b28a5538SAdam Hornacek 19982105c7dSVladimir Kotal for (String attrName : attrSet) { 20002df4614SVladimir Kotal if (!records.containsKey(attrName) || records.get(attrName) == null || records.get(attrName).isEmpty()) { 201cbd2aafbSVladimir Kotal LOGGER.log(Level.WARNING, "''{0}'' record for user {1} is not present or empty on {2}", 202cbd2aafbSVladimir Kotal new Object[]{attrName, user, ldapProvider}); 203b28a5538SAdam Hornacek } 204b28a5538SAdam Hornacek } 205b28a5538SAdam Hornacek 20682105c7dSVladimir Kotal Map<String, Set<String>> userAttrSet = new HashMap<>(); 20782105c7dSVladimir Kotal for (String attrName : this.attrSet) { 20882105c7dSVladimir Kotal userAttrSet.put(attrName, records.get(attrName)); 209b28a5538SAdam Hornacek } 210b28a5538SAdam Hornacek 211*d630fdc8SVladimir Kotal LOGGER.log(Level.FINEST, "DN for user {0} is ''{1}'' on {2}", new Object[]{user, dn, ldapProvider}); 21282105c7dSVladimir Kotal updateSession(req, new LdapUser(dn, userAttrSet)); 213b28a5538SAdam Hornacek } 214b28a5538SAdam Hornacek 215b28a5538SAdam Hornacek /** 216b28a5538SAdam Hornacek * Add a new user value into the session. 217b28a5538SAdam Hornacek * 218b28a5538SAdam Hornacek * @param req the request 219b28a5538SAdam Hornacek * @param user the new value for user 220b28a5538SAdam Hornacek */ updateSession(@otNull HttpServletRequest req, LdapUser user)221*d630fdc8SVladimir Kotal void updateSession(@NotNull HttpServletRequest req, LdapUser user) { 222d7630e3aSVladimir Kotal req.getSession().setAttribute(getSessionAttrName(), user); 22366cf937cSVladimir Kotal } 22466cf937cSVladimir Kotal getSessionAttrName(Integer instance)225d7630e3aSVladimir Kotal static String getSessionAttrName(Integer instance) { 22666cf937cSVladimir Kotal return (SESSION_ATTR + (instance != null ? instance.toString() : "")); 227b28a5538SAdam Hornacek } 228b28a5538SAdam Hornacek getSessionAttrName()229d7630e3aSVladimir Kotal private String getSessionAttrName() { 23082105c7dSVladimir Kotal return getSessionAttrName(instanceNum); 231d7630e3aSVladimir Kotal } 232d7630e3aSVladimir Kotal checkUser(@otNull HttpServletRequest request)233*d630fdc8SVladimir Kotal private boolean checkUser(@NotNull HttpServletRequest request) { 234*d630fdc8SVladimir Kotal LdapUser ldapUser = (LdapUser) request.getSession().getAttribute(getSessionAttrName()); 235*d630fdc8SVladimir Kotal return ldapUser != null && ldapUser.getAttribute(NEGATIVE_CACHE_ATTR) == null; 236*d630fdc8SVladimir Kotal } 237*d630fdc8SVladimir Kotal 238b28a5538SAdam Hornacek @Override checkEntity(HttpServletRequest request, Project project)239b28a5538SAdam Hornacek public boolean checkEntity(HttpServletRequest request, Project project) { 240*d630fdc8SVladimir Kotal return checkUser(request); 241b28a5538SAdam Hornacek } 242b28a5538SAdam Hornacek 243b28a5538SAdam Hornacek @Override checkEntity(HttpServletRequest request, Group group)244b28a5538SAdam Hornacek public boolean checkEntity(HttpServletRequest request, Group group) { 245*d630fdc8SVladimir Kotal return checkUser(request); 246b28a5538SAdam Hornacek } 247b28a5538SAdam Hornacek } 248