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 /* 21cbd2aafbSVladimir Kotal * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved. 22b28a5538SAdam Hornacek */ 23b28a5538SAdam Hornacek package opengrok.auth.plugin; 24b28a5538SAdam Hornacek 25b28a5538SAdam Hornacek import java.io.IOException; 26b28a5538SAdam Hornacek import java.nio.file.Files; 27b28a5538SAdam Hornacek import java.nio.file.Paths; 28b28a5538SAdam Hornacek import java.util.Map; 29b28a5538SAdam Hornacek import java.util.Set; 30b28a5538SAdam Hornacek import java.util.TreeSet; 31b28a5538SAdam Hornacek import java.util.logging.Level; 32b28a5538SAdam Hornacek import java.util.logging.Logger; 33b28a5538SAdam Hornacek import java.util.stream.Stream; 34b28a5538SAdam Hornacek import javax.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; 39b28a5538SAdam Hornacek import org.opengrok.indexer.authorization.AuthorizationException; 40b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Group; 41b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Project; 42b28a5538SAdam Hornacek 43b28a5538SAdam Hornacek /** 44b28a5538SAdam Hornacek * Authorization plug-in to check user's LDAP attribute against whitelist. 45b28a5538SAdam Hornacek * 4666cf937cSVladimir Kotal * This plugin heavily relies on the presence of the {@code LdapUserPlugin} in the stack above it, 4766cf937cSVladimir Kotal * since it is using the Distinguished Name of the {@code LdapUser} to perform the LDAP lookup. 4866cf937cSVladimir Kotal * 49b28a5538SAdam Hornacek * @author Krystof Tulinger 50b28a5538SAdam Hornacek */ 51b28a5538SAdam Hornacek public class LdapAttrPlugin extends AbstractLdapPlugin { 52b28a5538SAdam Hornacek 53b28a5538SAdam Hornacek private static final Logger LOGGER = Logger.getLogger(LdapAttrPlugin.class.getName()); 54b28a5538SAdam Hornacek 5566cf937cSVladimir Kotal /** 5666cf937cSVladimir Kotal * List of configuration names. 5766cf937cSVladimir Kotal * <ul> 5866cf937cSVladimir Kotal * <li><code>attribute</code> is LDAP attribute to check (mandatory)</li> 5966cf937cSVladimir Kotal * <li><code>file</code> whitelist file (mandatory)</li> 6066cf937cSVladimir Kotal * <li><code>instance</code> is number of <code>LdapUserInstance</code> plugin to use (optional)</li> 6166cf937cSVladimir Kotal * </ul> 6266cf937cSVladimir Kotal */ 631fc76d71SVladimir Kotal static final String ATTR_PARAM = "attribute"; 641fc76d71SVladimir Kotal static final String FILE_PARAM = "file"; 651fc76d71SVladimir Kotal static final String INSTANCE_PARAM = "instance"; 661fc76d71SVladimir Kotal 671fc76d71SVladimir Kotal private static final String SESSION_ALLOWED_PREFIX = "opengrok-ldap-attr-plugin-allowed"; 681fc76d71SVladimir Kotal private String sessionAllowed = SESSION_ALLOWED_PREFIX; 691fc76d71SVladimir Kotal 70b28a5538SAdam Hornacek private String ldapAttr; 71b28a5538SAdam Hornacek private final Set<String> whitelist = new TreeSet<>(); 7266cf937cSVladimir Kotal private Integer ldapUserInstance; 73cbd2aafbSVladimir Kotal private String filePath; 74b28a5538SAdam Hornacek LdapAttrPlugin()75b28a5538SAdam Hornacek public LdapAttrPlugin() { 76b28a5538SAdam Hornacek sessionAllowed += "-" + nextId++; 77b28a5538SAdam Hornacek } 78b28a5538SAdam Hornacek 7941d15cd7SVladimir Kotal // for testing load(Map<String, Object> parameters, AbstractLdapProvider provider)8041d15cd7SVladimir Kotal void load(Map<String, Object> parameters, AbstractLdapProvider provider) { 8141d15cd7SVladimir Kotal super.load(provider); 8241d15cd7SVladimir Kotal 8341d15cd7SVladimir Kotal init(parameters); 8441d15cd7SVladimir Kotal } 8541d15cd7SVladimir Kotal 86b28a5538SAdam Hornacek @Override load(Map<String, Object> parameters)87b28a5538SAdam Hornacek public void load(Map<String, Object> parameters) { 88b28a5538SAdam Hornacek super.load(parameters); 89b28a5538SAdam Hornacek 9041d15cd7SVladimir Kotal init(parameters); 9141d15cd7SVladimir Kotal } 9241d15cd7SVladimir Kotal init(Map<String, Object> parameters)9331639808SVladimir Kotal private void init(Map<String, Object> parameters) { 94b28a5538SAdam Hornacek if ((ldapAttr = (String) parameters.get(ATTR_PARAM)) == null) { 95b28a5538SAdam Hornacek throw new NullPointerException("Missing param [" + ATTR_PARAM + "] in the setup"); 96b28a5538SAdam Hornacek } 97b28a5538SAdam Hornacek 98b28a5538SAdam Hornacek if ((filePath = (String) parameters.get(FILE_PARAM)) == null) { 99b28a5538SAdam Hornacek throw new NullPointerException("Missing param [" + FILE_PARAM + "] in the setup"); 100b28a5538SAdam Hornacek } 101b28a5538SAdam Hornacek 102f7f40d76SVladimir Kotal String instance = (String) parameters.get(INSTANCE_PARAM); 10366cf937cSVladimir Kotal if (instance != null) { 10466cf937cSVladimir Kotal ldapUserInstance = Integer.parseInt(instance); 10566cf937cSVladimir Kotal } 10666cf937cSVladimir Kotal 107b28a5538SAdam Hornacek try (Stream<String> stream = Files.lines(Paths.get(filePath))) { 108b28a5538SAdam Hornacek stream.forEach(whitelist::add); 109b28a5538SAdam Hornacek } catch (IOException e) { 110b28a5538SAdam Hornacek throw new IllegalArgumentException(String.format("Unable to read the file \"%s\"", filePath), e); 111b28a5538SAdam Hornacek } 11266cf937cSVladimir Kotal 11366cf937cSVladimir Kotal LOGGER.log(Level.FINE, "LdapAttrPlugin plugin loaded with attr={0}, whitelist={1}, instance={2}", 11466cf937cSVladimir Kotal new Object[]{ldapAttr, filePath, ldapUserInstance}); 115b28a5538SAdam Hornacek } 116b28a5538SAdam Hornacek 117b28a5538SAdam Hornacek @Override sessionExists(HttpServletRequest req)118b28a5538SAdam Hornacek protected boolean sessionExists(HttpServletRequest req) { 119b28a5538SAdam Hornacek return super.sessionExists(req) 120b28a5538SAdam Hornacek && req.getSession().getAttribute(sessionAllowed) != null; 121b28a5538SAdam Hornacek } 122b28a5538SAdam Hornacek getSessionAllowedAttrName()123f7f40d76SVladimir Kotal String getSessionAllowedAttrName() { 124f7f40d76SVladimir Kotal return sessionAllowed; 125f7f40d76SVladimir Kotal } 126f7f40d76SVladimir Kotal 127b28a5538SAdam Hornacek @Override fillSession(HttpServletRequest req, User user)128b28a5538SAdam Hornacek public void fillSession(HttpServletRequest req, User user) { 12931639808SVladimir Kotal boolean sessionAllowed; 130b28a5538SAdam Hornacek LdapUser ldapUser; 13153c33ae5SVladimir Kotal Map<String, Set<String>> records = null; 132b28a5538SAdam Hornacek Set<String> attributeValues; 133b28a5538SAdam Hornacek 13417eebd12SVladimir Kotal updateSession(req, false); 135b28a5538SAdam Hornacek 136d7630e3aSVladimir Kotal if ((ldapUser = (LdapUser) req.getSession(). 137d7630e3aSVladimir Kotal getAttribute(LdapUserPlugin.getSessionAttrName(ldapUserInstance))) == null) { 138*e467a492SVladimir Kotal LOGGER.log(Level.WARNING, "cannot get {0} attribute from {1}", 139*e467a492SVladimir Kotal new Object[]{LdapUserPlugin.SESSION_ATTR, user}); 140b28a5538SAdam Hornacek return; 141b28a5538SAdam Hornacek } 142b28a5538SAdam Hornacek 143b28a5538SAdam Hornacek // Check attributes cached in LDAP user object first, then query LDAP server 144b28a5538SAdam Hornacek // (and if found, cache the result in the LDAP user object). 145b28a5538SAdam Hornacek attributeValues = ldapUser.getAttribute(ldapAttr); 146cbd2aafbSVladimir Kotal if (attributeValues == null) { 147cbd2aafbSVladimir Kotal AbstractLdapProvider ldapProvider = getLdapProvider(); 148b28a5538SAdam Hornacek try { 14902df4614SVladimir Kotal String dn = ldapUser.getDn(); 15017deb9edSVladimir Kotal if (dn != null) { 151cbd2aafbSVladimir Kotal LOGGER.log(Level.FINEST, "searching with dn={0} on {1}", 152cbd2aafbSVladimir Kotal new Object[]{dn, ldapProvider}); 15317deb9edSVladimir Kotal AbstractLdapProvider.LdapSearchResult<Map<String, Set<String>>> res; 154cbd2aafbSVladimir Kotal if ((res = ldapProvider.lookupLdapContent(dn, new String[]{ldapAttr})) == null) { 155cbd2aafbSVladimir Kotal LOGGER.log(Level.WARNING, "cannot lookup attributes {0} for user {1} on {2})", 156*e467a492SVladimir Kotal new Object[]{ldapAttr, ldapUser, ldapProvider}); 1574126de0dSVladimir Kotal return; 15853c33ae5SVladimir Kotal } 15953c33ae5SVladimir Kotal 16017deb9edSVladimir Kotal records = res.getAttrs(); 16102df4614SVladimir Kotal } else { 162cbd2aafbSVladimir Kotal LOGGER.log(Level.FINE, "no DN for user {0} on {1}", 163*e467a492SVladimir Kotal new Object[]{ldapUser, ldapProvider}); 164b28a5538SAdam Hornacek } 165b28a5538SAdam Hornacek } catch (LdapException ex) { 166b28a5538SAdam Hornacek throw new AuthorizationException(ex); 167b28a5538SAdam Hornacek } 168b28a5538SAdam Hornacek 16953c33ae5SVladimir Kotal if (records == null || records.isEmpty() || (attributeValues = records.get(ldapAttr)) == null) { 170cbd2aafbSVladimir Kotal LOGGER.log(Level.WARNING, "empty records or attribute values {0} for user {1} on {2}", 171*e467a492SVladimir Kotal new Object[]{ldapAttr, ldapUser, ldapProvider}); 172b28a5538SAdam Hornacek return; 173b28a5538SAdam Hornacek } 174b28a5538SAdam Hornacek 175b28a5538SAdam Hornacek ldapUser.setAttribute(ldapAttr, attributeValues); 176b28a5538SAdam Hornacek } 177b28a5538SAdam Hornacek 178cbd2aafbSVladimir Kotal sessionAllowed = attributeValues.stream().anyMatch(whitelist::contains); 179cbd2aafbSVladimir Kotal LOGGER.log(Level.FINEST, "LDAP user {0} {1} against {2}", 180cbd2aafbSVladimir Kotal new Object[]{ldapUser, sessionAllowed ? "allowed" : "denied", filePath}); 181b28a5538SAdam Hornacek updateSession(req, sessionAllowed); 182b28a5538SAdam Hornacek } 183b28a5538SAdam Hornacek 184b28a5538SAdam Hornacek /** 185b28a5538SAdam Hornacek * Add a new allowed value into the session. 186b28a5538SAdam Hornacek * 187b28a5538SAdam Hornacek * @param req the request 188b28a5538SAdam Hornacek * @param allowed the new value 189b28a5538SAdam Hornacek */ updateSession(HttpServletRequest req, boolean allowed)19031639808SVladimir Kotal private void updateSession(HttpServletRequest req, boolean allowed) { 191b28a5538SAdam Hornacek req.getSession().setAttribute(sessionAllowed, allowed); 192b28a5538SAdam Hornacek } 193b28a5538SAdam Hornacek 194b28a5538SAdam Hornacek @Override checkEntity(HttpServletRequest request, Project project)195b28a5538SAdam Hornacek public boolean checkEntity(HttpServletRequest request, Project project) { 196b28a5538SAdam Hornacek return ((Boolean) request.getSession().getAttribute(sessionAllowed)); 197b28a5538SAdam Hornacek } 198b28a5538SAdam Hornacek 199b28a5538SAdam Hornacek @Override checkEntity(HttpServletRequest request, Group group)200b28a5538SAdam Hornacek public boolean checkEntity(HttpServletRequest request, Group group) { 201b28a5538SAdam Hornacek return ((Boolean) request.getSession().getAttribute(sessionAllowed)); 202b28a5538SAdam Hornacek } 203b28a5538SAdam Hornacek } 204