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 /* 2153c33ae5SVladimir Kotal * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved. 22b28a5538SAdam Hornacek */ 23b28a5538SAdam Hornacek package opengrok.auth.plugin; 24b28a5538SAdam Hornacek 2553c33ae5SVladimir Kotal import java.util.Arrays; 26b28a5538SAdam Hornacek import java.util.HashMap; 2753c33ae5SVladimir Kotal import java.util.HashSet; 28b28a5538SAdam Hornacek import java.util.Map; 29b28a5538SAdam Hornacek import java.util.Set; 30b28a5538SAdam Hornacek import java.util.logging.Level; 31b28a5538SAdam Hornacek import java.util.logging.Logger; 32b28a5538SAdam Hornacek import javax.servlet.http.HttpServletRequest; 33b28a5538SAdam Hornacek import opengrok.auth.entity.LdapUser; 34b28a5538SAdam Hornacek import opengrok.auth.plugin.entity.User; 3517deb9edSVladimir Kotal import opengrok.auth.plugin.ldap.AbstractLdapProvider; 36b28a5538SAdam Hornacek import opengrok.auth.plugin.ldap.LdapException; 37b28a5538SAdam Hornacek import org.opengrok.indexer.authorization.AuthorizationException; 38b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Group; 39b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Project; 40b28a5538SAdam Hornacek 41f305899cSVladimir Kotal import static opengrok.auth.plugin.util.FilterUtil.expandUserFilter; 42f305899cSVladimir Kotal 43b28a5538SAdam Hornacek /** 44b28a5538SAdam Hornacek * Authorization plug-in to extract user's LDAP attributes. 45b28a5538SAdam Hornacek * The attributes can be then used by the other LDAP plugins down the stack. 46b28a5538SAdam Hornacek * 47b28a5538SAdam Hornacek * @author Krystof Tulinger 48b28a5538SAdam Hornacek */ 49b28a5538SAdam Hornacek public class LdapUserPlugin extends AbstractLdapPlugin { 50b28a5538SAdam Hornacek 51b28a5538SAdam Hornacek private static final Logger LOGGER = Logger.getLogger(LdapUserPlugin.class.getName()); 52b28a5538SAdam Hornacek 53bf4d2fd4SVladimir Kotal static final String SESSION_ATTR = "opengrok-ldap-plugin-user"; 54b28a5538SAdam Hornacek 5553c33ae5SVladimir Kotal /** 560e7ff20bSVladimir Kotal * List of configuration names. 5753c33ae5SVladimir Kotal * <ul> 5853c33ae5SVladimir Kotal * <li><code>filter</code> is LDAP filter used for searching (optional)</li> 5902df4614SVladimir Kotal * <li><code>useDN</code> boolean value indicating if User.username should be treated as Distinguished Name (optional, default is false)</li> 6053c33ae5SVladimir Kotal * <li><code>attributes</code> is comma separated list of LDAP attributes to be produced (mandatory)</li> 6166cf937cSVladimir Kotal * <li><code>instance</code> integer that can be used to identify instance of this plugin by other LDAP plugins (optional, default empty)</li> 6253c33ae5SVladimir Kotal * </ul> 6353c33ae5SVladimir Kotal */ 64bf4d2fd4SVladimir Kotal static final String LDAP_FILTER = "filter"; 65bf4d2fd4SVladimir Kotal static final String ATTRIBUTES = "attributes"; 66bf4d2fd4SVladimir Kotal static final String USE_DN = "useDN"; 67bf4d2fd4SVladimir Kotal static final String INSTANCE = "instance"; 68b28a5538SAdam Hornacek 6953c33ae5SVladimir Kotal private String ldapFilter; 7053c33ae5SVladimir Kotal private Boolean useDN; 7153c33ae5SVladimir Kotal private Set<String> attributes; 7266cf937cSVladimir Kotal private Integer instance; 73b28a5538SAdam Hornacek 743c16dad8SVladimir Kotal // for testing load(Map<String, Object> parameters, AbstractLdapProvider provider)753c16dad8SVladimir Kotal void load(Map<String, Object> parameters, AbstractLdapProvider provider) { 763c16dad8SVladimir Kotal super.load(provider); 773c16dad8SVladimir Kotal 783c16dad8SVladimir Kotal init(parameters); 793c16dad8SVladimir Kotal } 803c16dad8SVladimir Kotal 81b28a5538SAdam Hornacek @Override load(Map<String, Object> parameters)82b28a5538SAdam Hornacek public void load(Map<String, Object> parameters) { 83b28a5538SAdam Hornacek super.load(parameters); 84b28a5538SAdam Hornacek 853c16dad8SVladimir Kotal init(parameters); 863c16dad8SVladimir Kotal } 873c16dad8SVladimir Kotal init(Map<String, Object> parameters)883c16dad8SVladimir Kotal private void init(Map<String, Object> parameters) { 89b28a5538SAdam Hornacek String attributesVal; 90b28a5538SAdam Hornacek if ((attributesVal = (String) parameters.get(ATTRIBUTES)) == null) { 9153c33ae5SVladimir Kotal throw new NullPointerException("Missing configuration parameter [" + ATTRIBUTES + 92b28a5538SAdam Hornacek "] in the setup"); 93b28a5538SAdam Hornacek } 9453c33ae5SVladimir Kotal attributes = new HashSet<>(Arrays.asList(attributesVal.split(","))); 95b28a5538SAdam Hornacek 9653c33ae5SVladimir Kotal ldapFilter = (String) parameters.get(LDAP_FILTER); 9753c33ae5SVladimir Kotal 9853c33ae5SVladimir Kotal if ((useDN = (Boolean) parameters.get(USE_DN)) == null) { 9953c33ae5SVladimir Kotal useDN = false; 10053c33ae5SVladimir Kotal } 10153c33ae5SVladimir Kotal 102e173e36dSVladimir Kotal String instance_param = (String) parameters.get(INSTANCE); 103e173e36dSVladimir Kotal if (instance_param != null) { 104e173e36dSVladimir Kotal instance = Integer.parseInt(instance_param); 105e173e36dSVladimir Kotal } 10666cf937cSVladimir Kotal 10753c33ae5SVladimir Kotal LOGGER.log(Level.FINE, "LdapUser plugin loaded with filter={0}, " + 10866cf937cSVladimir Kotal "attributes={1}, useDN={2}, instance={3}", 10966cf937cSVladimir Kotal new Object[]{ldapFilter, attributes, useDN, instance}); 110b28a5538SAdam Hornacek } 111b28a5538SAdam Hornacek 112b28a5538SAdam Hornacek /** 113b28a5538SAdam Hornacek * Check if the session exists and contains all necessary fields required by 114b28a5538SAdam Hornacek * this plug-in. 115b28a5538SAdam Hornacek * 116b28a5538SAdam Hornacek * @param req the HTTP request 117b28a5538SAdam Hornacek * @return true if it does; false otherwise 118b28a5538SAdam Hornacek */ 119b28a5538SAdam Hornacek @Override sessionExists(HttpServletRequest req)120b28a5538SAdam Hornacek protected boolean sessionExists(HttpServletRequest req) { 121b28a5538SAdam Hornacek return super.sessionExists(req) 122d7630e3aSVladimir Kotal && req.getSession().getAttribute(getSessionAttrName()) != null; 123b28a5538SAdam Hornacek } 124b28a5538SAdam Hornacek 12553c33ae5SVladimir Kotal /** 126f305899cSVladimir Kotal * Expand {@code User} object attribute values into the filter. 12753c33ae5SVladimir Kotal * 128f305899cSVladimir Kotal * @see opengrok.auth.plugin.util.FilterUtil 12953c33ae5SVladimir Kotal * 13053c33ae5SVladimir Kotal * Use \% for printing the '%' character. 13153c33ae5SVladimir Kotal * 132750d880bSVladimir Kotal * @param user User object from the request (created by {@code UserPlugin}) 13353c33ae5SVladimir Kotal * @return replaced result 13453c33ae5SVladimir Kotal */ expandFilter(User user)135bf4d2fd4SVladimir Kotal String expandFilter(User user) { 13653c33ae5SVladimir Kotal String filter = ldapFilter; 137b28a5538SAdam Hornacek 138f305899cSVladimir Kotal filter = expandUserFilter(user, filter); 139b28a5538SAdam Hornacek 14053c33ae5SVladimir Kotal filter = filter.replaceAll("\\\\%", "%"); 14153c33ae5SVladimir Kotal 14253c33ae5SVladimir Kotal return filter; 143b28a5538SAdam Hornacek } 144b28a5538SAdam Hornacek 145b28a5538SAdam Hornacek @Override fillSession(HttpServletRequest req, User user)146b28a5538SAdam Hornacek public void fillSession(HttpServletRequest req, User user) { 147b28a5538SAdam Hornacek Map<String, Set<String>> records; 148b28a5538SAdam Hornacek 149b28a5538SAdam Hornacek updateSession(req, null); 150b28a5538SAdam Hornacek 151b28a5538SAdam Hornacek if (getLdapProvider() == null) { 15253c33ae5SVladimir Kotal LOGGER.log(Level.WARNING, "cannot get LDAP provider"); 153b28a5538SAdam Hornacek return; 154b28a5538SAdam Hornacek } 155b28a5538SAdam Hornacek 15653c33ae5SVladimir Kotal String expandedFilter = null; 15702df4614SVladimir Kotal String dn = null; 15802df4614SVladimir Kotal if (useDN) { 15902df4614SVladimir Kotal dn = user.getUsername(); 16002df4614SVladimir Kotal } 16153c33ae5SVladimir Kotal if (ldapFilter != null) { 16253c33ae5SVladimir Kotal expandedFilter = expandFilter(user); 16353c33ae5SVladimir Kotal } 164b28a5538SAdam Hornacek try { 16517deb9edSVladimir Kotal AbstractLdapProvider.LdapSearchResult<Map<String, Set<String>>> res; 16602df4614SVladimir Kotal if ((res = getLdapProvider().lookupLdapContent(dn, expandedFilter, 167b91b7b82SVladimir Kotal attributes.toArray(new String[0]))) == null) { 168afd2f2f7SVladimir Kotal LOGGER.log(Level.WARNING, "failed to get LDAP attributes ''{2}'' for user {0} " + 169*e5567996SVladimir Kotal "with filter ''{1}'' from LDAP provider {3}", 170*e5567996SVladimir Kotal new Object[]{user, expandedFilter, attributes, getLdapProvider()}); 171b28a5538SAdam Hornacek return; 172b28a5538SAdam Hornacek } 17317deb9edSVladimir Kotal 17417deb9edSVladimir Kotal records = res.getAttrs(); 17502df4614SVladimir Kotal if (!useDN) { 17617deb9edSVladimir Kotal dn = res.getDN(); 17702df4614SVladimir Kotal } 178b28a5538SAdam Hornacek } catch (LdapException ex) { 179b28a5538SAdam Hornacek throw new AuthorizationException(ex); 180b28a5538SAdam Hornacek } 181b28a5538SAdam Hornacek 182b28a5538SAdam Hornacek if (records.isEmpty()) { 183b28a5538SAdam Hornacek LOGGER.log(Level.WARNING, "LDAP records for user {0} are empty", 184b28a5538SAdam Hornacek user); 185b28a5538SAdam Hornacek return; 186b28a5538SAdam Hornacek } 187b28a5538SAdam Hornacek 188b28a5538SAdam Hornacek for (String attrName : attributes) { 18902df4614SVladimir Kotal if (!records.containsKey(attrName) || records.get(attrName) == null || records.get(attrName).isEmpty()) { 19053c33ae5SVladimir Kotal LOGGER.log(Level.WARNING, "''{0}'' record for user {1} is not present or empty (LDAP provider: {2})", 19153c33ae5SVladimir Kotal new Object[]{attrName, user, getLdapProvider()}); 192b28a5538SAdam Hornacek } 193b28a5538SAdam Hornacek } 194b28a5538SAdam Hornacek 195b28a5538SAdam Hornacek Map<String, Set<String>> attrSet = new HashMap<>(); 196b28a5538SAdam Hornacek for (String attrName : attributes) { 197b28a5538SAdam Hornacek attrSet.put(attrName, records.get(attrName)); 198b28a5538SAdam Hornacek } 199b28a5538SAdam Hornacek 20002df4614SVladimir Kotal LOGGER.log(Level.FINEST, "DN for user {0}: {1}", new Object[]{user, dn}); 20102df4614SVladimir Kotal updateSession(req, new LdapUser(dn, attrSet)); 202b28a5538SAdam Hornacek } 203b28a5538SAdam Hornacek 204b28a5538SAdam Hornacek /** 205b28a5538SAdam Hornacek * Add a new user value into the session. 206b28a5538SAdam Hornacek * 207b28a5538SAdam Hornacek * @param req the request 208b28a5538SAdam Hornacek * @param user the new value for user 209b28a5538SAdam Hornacek */ updateSession(HttpServletRequest req, LdapUser user)210d7630e3aSVladimir Kotal void updateSession(HttpServletRequest req, LdapUser user) { 211d7630e3aSVladimir Kotal req.getSession().setAttribute(getSessionAttrName(), user); 21266cf937cSVladimir Kotal } 21366cf937cSVladimir Kotal getSessionAttrName(Integer instance)214d7630e3aSVladimir Kotal static String getSessionAttrName(Integer instance) { 21566cf937cSVladimir Kotal return (SESSION_ATTR + (instance != null ? instance.toString() : "")); 216b28a5538SAdam Hornacek } 217b28a5538SAdam Hornacek getSessionAttrName()218d7630e3aSVladimir Kotal private String getSessionAttrName() { 219d7630e3aSVladimir Kotal return getSessionAttrName(instance); 220d7630e3aSVladimir Kotal } 221d7630e3aSVladimir Kotal 222b28a5538SAdam Hornacek @Override checkEntity(HttpServletRequest request, Project project)223b28a5538SAdam Hornacek public boolean checkEntity(HttpServletRequest request, Project project) { 224d7630e3aSVladimir Kotal return request.getSession().getAttribute(getSessionAttrName()) != null; 225b28a5538SAdam Hornacek } 226b28a5538SAdam Hornacek 227b28a5538SAdam Hornacek @Override checkEntity(HttpServletRequest request, Group group)228b28a5538SAdam Hornacek public boolean checkEntity(HttpServletRequest request, Group group) { 229d7630e3aSVladimir Kotal return request.getSession().getAttribute(getSessionAttrName()) != null; 230b28a5538SAdam Hornacek } 231b28a5538SAdam Hornacek } 232