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 /* 21*53c33ae5SVladimir Kotal * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved. 22b28a5538SAdam Hornacek */ 23b28a5538SAdam Hornacek package opengrok.auth.plugin; 24b28a5538SAdam Hornacek 25*53c33ae5SVladimir Kotal import java.util.Arrays; 26b28a5538SAdam Hornacek import java.util.HashMap; 27*53c33ae5SVladimir 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; 35b28a5538SAdam Hornacek import opengrok.auth.plugin.ldap.LdapException; 36b28a5538SAdam Hornacek import org.opengrok.indexer.authorization.AuthorizationException; 37b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Group; 38b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Project; 39b28a5538SAdam Hornacek 40b28a5538SAdam Hornacek /** 41b28a5538SAdam Hornacek * Authorization plug-in to extract user's LDAP attributes. 42b28a5538SAdam Hornacek * The attributes can be then used by the other LDAP plugins down the stack. 43b28a5538SAdam Hornacek * 44b28a5538SAdam Hornacek * @author Krystof Tulinger 45b28a5538SAdam Hornacek */ 46b28a5538SAdam Hornacek public class LdapUserPlugin extends AbstractLdapPlugin { 47b28a5538SAdam Hornacek 48b28a5538SAdam Hornacek private static final Logger LOGGER = Logger.getLogger(LdapUserPlugin.class.getName()); 49b28a5538SAdam Hornacek 50b28a5538SAdam Hornacek public static final String SESSION_ATTR = "opengrok-ldap-plugin-user"; 51b28a5538SAdam Hornacek 52*53c33ae5SVladimir Kotal /** 53*53c33ae5SVladimir Kotal * configuration names 54*53c33ae5SVladimir Kotal * <ul> 55*53c33ae5SVladimir Kotal * <li><code>filter</code> is LDAP filter used for searching (optional)</li> 56*53c33ae5SVladimir Kotal * <li><code>useDN</code> boolean value indicating if User.username should be used as search DN</li> 57*53c33ae5SVladimir Kotal * <li><code>attributes</code> is comma separated list of LDAP attributes to be produced (mandatory)</li> 58*53c33ae5SVladimir Kotal * </ul> 59*53c33ae5SVladimir Kotal */ 60*53c33ae5SVladimir Kotal protected static final String LDAP_FILTER = "filter"; 61b28a5538SAdam Hornacek protected static final String ATTRIBUTES = "attributes"; 62*53c33ae5SVladimir Kotal protected static final String USE_DN = "useDN"; 63b28a5538SAdam Hornacek 64*53c33ae5SVladimir Kotal private String ldapFilter; 65*53c33ae5SVladimir Kotal private Boolean useDN; 66*53c33ae5SVladimir Kotal private Set<String> attributes; 67b28a5538SAdam Hornacek 68b28a5538SAdam Hornacek @Override load(Map<String, Object> parameters)69b28a5538SAdam Hornacek public void load(Map<String, Object> parameters) { 70b28a5538SAdam Hornacek super.load(parameters); 71b28a5538SAdam Hornacek 72b28a5538SAdam Hornacek String attributesVal; 73b28a5538SAdam Hornacek if ((attributesVal = (String) parameters.get(ATTRIBUTES)) == null) { 74*53c33ae5SVladimir Kotal throw new NullPointerException("Missing configuration parameter [" + ATTRIBUTES + 75b28a5538SAdam Hornacek "] in the setup"); 76b28a5538SAdam Hornacek } 77*53c33ae5SVladimir Kotal attributes = new HashSet<>(Arrays.asList(attributesVal.split(","))); 78*53c33ae5SVladimir Kotal attributes.add("dn"); 79b28a5538SAdam Hornacek 80*53c33ae5SVladimir Kotal ldapFilter = (String) parameters.get(LDAP_FILTER); 81*53c33ae5SVladimir Kotal 82*53c33ae5SVladimir Kotal if ((useDN = (Boolean) parameters.get(USE_DN)) == null) { 83*53c33ae5SVladimir Kotal useDN = false; 84*53c33ae5SVladimir Kotal } 85*53c33ae5SVladimir Kotal 86*53c33ae5SVladimir Kotal LOGGER.log(Level.FINE, "LdapUser plugin loaded with filter={0}, " + 87*53c33ae5SVladimir Kotal "attributes={1}, useDN={2}", 88*53c33ae5SVladimir Kotal new Object[]{ldapFilter, String.join(", ", attributes), useDN}); 89b28a5538SAdam Hornacek } 90b28a5538SAdam Hornacek 91b28a5538SAdam Hornacek /** 92b28a5538SAdam Hornacek * Check if the session exists and contains all necessary fields required by 93b28a5538SAdam Hornacek * this plug-in. 94b28a5538SAdam Hornacek * 95b28a5538SAdam Hornacek * @param req the HTTP request 96b28a5538SAdam Hornacek * @return true if it does; false otherwise 97b28a5538SAdam Hornacek */ 98b28a5538SAdam Hornacek @Override sessionExists(HttpServletRequest req)99b28a5538SAdam Hornacek protected boolean sessionExists(HttpServletRequest req) { 100b28a5538SAdam Hornacek return super.sessionExists(req) 101b28a5538SAdam Hornacek && req.getSession().getAttribute(SESSION_ATTR) != null; 102b28a5538SAdam Hornacek } 103b28a5538SAdam Hornacek 104*53c33ae5SVladimir Kotal /** 105*53c33ae5SVladimir Kotal * Expand User attribute values into the filter. 106*53c33ae5SVladimir Kotal * 107*53c33ae5SVladimir Kotal * Special values are: 108*53c33ae5SVladimir Kotal * <ul> 109*53c33ae5SVladimir Kotal * <li>%username% - to be replaced with username value from the User object</li> 110*53c33ae5SVladimir Kotal * <li>%guid% - to be replaced with guid value from the User object</li> 111*53c33ae5SVladimir Kotal * </ul> 112*53c33ae5SVladimir Kotal * 113*53c33ae5SVladimir Kotal * Use \% for printing the '%' character. 114*53c33ae5SVladimir Kotal * 115*53c33ae5SVladimir Kotal * @param user User object from the request (created by {@see UserPlugin}) 116*53c33ae5SVladimir Kotal * @return replaced result 117*53c33ae5SVladimir Kotal */ expandFilter(User user)118*53c33ae5SVladimir Kotal protected String expandFilter(User user) { 119*53c33ae5SVladimir Kotal String filter = ldapFilter; 120b28a5538SAdam Hornacek 121*53c33ae5SVladimir Kotal filter = filter.replaceAll("(?<!\\\\)%username(?<!\\\\)%", user.getUsername()); 122*53c33ae5SVladimir Kotal filter = filter.replaceAll("(?<!\\\\)%guid(?<!\\\\)%", user.getId()); 123b28a5538SAdam Hornacek 124*53c33ae5SVladimir Kotal filter = filter.replaceAll("\\\\%", "%"); 125*53c33ae5SVladimir Kotal 126*53c33ae5SVladimir Kotal return filter; 127b28a5538SAdam Hornacek } 128b28a5538SAdam Hornacek 129b28a5538SAdam Hornacek @Override fillSession(HttpServletRequest req, User user)130b28a5538SAdam Hornacek public void fillSession(HttpServletRequest req, User user) { 131b28a5538SAdam Hornacek Map<String, Set<String>> records; 132b28a5538SAdam Hornacek 133b28a5538SAdam Hornacek updateSession(req, null); 134b28a5538SAdam Hornacek 135b28a5538SAdam Hornacek if (getLdapProvider() == null) { 136*53c33ae5SVladimir Kotal LOGGER.log(Level.WARNING, "cannot get LDAP provider"); 137b28a5538SAdam Hornacek return; 138b28a5538SAdam Hornacek } 139b28a5538SAdam Hornacek 140*53c33ae5SVladimir Kotal String expandedFilter = null; 141*53c33ae5SVladimir Kotal if (ldapFilter != null) { 142*53c33ae5SVladimir Kotal expandedFilter = expandFilter(user); 143*53c33ae5SVladimir Kotal } 144b28a5538SAdam Hornacek try { 145*53c33ae5SVladimir Kotal if ((records = getLdapProvider().lookupLdapContent(useDN ? user.getUsername() : null, 146*53c33ae5SVladimir Kotal expandedFilter, attributes.toArray(new String[attributes.size()]))) == null) { 147b28a5538SAdam Hornacek LOGGER.log(Level.WARNING, "failed to get LDAP attributes ''{3}'' for user ''{0}'' " + 148b28a5538SAdam Hornacek "with filter ''{1}''", 149*53c33ae5SVladimir Kotal new Object[]{user, expandedFilter, String.join(", ", attributes)}); 150b28a5538SAdam Hornacek return; 151b28a5538SAdam Hornacek } 152b28a5538SAdam Hornacek } catch (LdapException ex) { 153b28a5538SAdam Hornacek throw new AuthorizationException(ex); 154b28a5538SAdam Hornacek } 155b28a5538SAdam Hornacek 156b28a5538SAdam Hornacek if (records.isEmpty()) { 157b28a5538SAdam Hornacek LOGGER.log(Level.WARNING, "LDAP records for user {0} are empty", 158b28a5538SAdam Hornacek user); 159b28a5538SAdam Hornacek return; 160b28a5538SAdam Hornacek } 161b28a5538SAdam Hornacek 162b28a5538SAdam Hornacek for (String attrName : attributes) { 163b28a5538SAdam Hornacek if (!records.containsKey(attrName) || records.get(attrName).isEmpty()) { 164*53c33ae5SVladimir Kotal LOGGER.log(Level.WARNING, "''{0}'' record for user {1} is not present or empty (LDAP provider: {2})", 165*53c33ae5SVladimir Kotal new Object[]{attrName, user, getLdapProvider()}); 166b28a5538SAdam Hornacek } 167b28a5538SAdam Hornacek } 168b28a5538SAdam Hornacek 169b28a5538SAdam Hornacek Map<String, Set<String>> attrSet = new HashMap<>(); 170b28a5538SAdam Hornacek for (String attrName : attributes) { 171b28a5538SAdam Hornacek attrSet.put(attrName, records.get(attrName)); 172b28a5538SAdam Hornacek } 173b28a5538SAdam Hornacek 174b28a5538SAdam Hornacek updateSession(req, new LdapUser(attrSet)); 175b28a5538SAdam Hornacek } 176b28a5538SAdam Hornacek 177b28a5538SAdam Hornacek /** 178b28a5538SAdam Hornacek * Add a new user value into the session. 179b28a5538SAdam Hornacek * 180b28a5538SAdam Hornacek * @param req the request 181b28a5538SAdam Hornacek * @param user the new value for user 182b28a5538SAdam Hornacek */ updateSession(HttpServletRequest req, LdapUser user)183b28a5538SAdam Hornacek protected void updateSession(HttpServletRequest req, LdapUser user) { 184b28a5538SAdam Hornacek req.getSession().setAttribute(SESSION_ATTR, user); 185b28a5538SAdam Hornacek } 186b28a5538SAdam Hornacek 187b28a5538SAdam Hornacek @Override checkEntity(HttpServletRequest request, Project project)188b28a5538SAdam Hornacek public boolean checkEntity(HttpServletRequest request, Project project) { 189b28a5538SAdam Hornacek return request.getSession().getAttribute(SESSION_ATTR) != null; 190b28a5538SAdam Hornacek } 191b28a5538SAdam Hornacek 192b28a5538SAdam Hornacek @Override checkEntity(HttpServletRequest request, Group group)193b28a5538SAdam Hornacek public boolean checkEntity(HttpServletRequest request, Group group) { 194b28a5538SAdam Hornacek return request.getSession().getAttribute(SESSION_ATTR) != null; 195b28a5538SAdam Hornacek } 196b28a5538SAdam Hornacek } 197