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 /* 21b28a5538SAdam Hornacek * Copyright (c) 2016, 2018 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 * 46b28a5538SAdam Hornacek * @author Krystof Tulinger 47b28a5538SAdam Hornacek */ 48b28a5538SAdam Hornacek public class LdapAttrPlugin extends AbstractLdapPlugin { 49b28a5538SAdam Hornacek 50b28a5538SAdam Hornacek private static final Logger LOGGER = Logger.getLogger(LdapAttrPlugin.class.getName()); 51b28a5538SAdam Hornacek 52b28a5538SAdam Hornacek protected static final String ATTR_PARAM = "attribute"; // LDAP attribute name to check 53b28a5538SAdam Hornacek protected static final String FILE_PARAM = "file"; 54b28a5538SAdam Hornacek 55b28a5538SAdam Hornacek private static final String SESSION_ALLOWED_PREFIX = "opengrok-attr-plugin-allowed"; 56b28a5538SAdam Hornacek private String sessionAllowed = SESSION_ALLOWED_PREFIX; 57b28a5538SAdam Hornacek 58b28a5538SAdam Hornacek private String ldapAttr; 59b28a5538SAdam Hornacek private final Set<String> whitelist = new TreeSet<>(); 60b28a5538SAdam Hornacek LdapAttrPlugin()61b28a5538SAdam Hornacek public LdapAttrPlugin() { 62b28a5538SAdam Hornacek sessionAllowed += "-" + nextId++; 63b28a5538SAdam Hornacek } 64b28a5538SAdam Hornacek 65b28a5538SAdam Hornacek @Override load(Map<String, Object> parameters)66b28a5538SAdam Hornacek public void load(Map<String, Object> parameters) { 67b28a5538SAdam Hornacek super.load(parameters); 68b28a5538SAdam Hornacek String filePath; 69b28a5538SAdam Hornacek 70b28a5538SAdam Hornacek if ((ldapAttr = (String) parameters.get(ATTR_PARAM)) == null) { 71b28a5538SAdam Hornacek throw new NullPointerException("Missing param [" + ATTR_PARAM + "] in the setup"); 72b28a5538SAdam Hornacek } 73b28a5538SAdam Hornacek 74b28a5538SAdam Hornacek if ((filePath = (String) parameters.get(FILE_PARAM)) == null) { 75b28a5538SAdam Hornacek throw new NullPointerException("Missing param [" + FILE_PARAM + "] in the setup"); 76b28a5538SAdam Hornacek } 77b28a5538SAdam Hornacek 78b28a5538SAdam Hornacek try (Stream<String> stream = Files.lines(Paths.get(filePath))) { 79b28a5538SAdam Hornacek stream.forEach(whitelist::add); 80b28a5538SAdam Hornacek } catch (IOException e) { 81b28a5538SAdam Hornacek throw new IllegalArgumentException(String.format("Unable to read the file \"%s\"", filePath), e); 82b28a5538SAdam Hornacek } 83b28a5538SAdam Hornacek } 84b28a5538SAdam Hornacek 85b28a5538SAdam Hornacek @Override sessionExists(HttpServletRequest req)86b28a5538SAdam Hornacek protected boolean sessionExists(HttpServletRequest req) { 87b28a5538SAdam Hornacek return super.sessionExists(req) 88b28a5538SAdam Hornacek && req.getSession().getAttribute(sessionAllowed) != null; 89b28a5538SAdam Hornacek } 90b28a5538SAdam Hornacek 91b28a5538SAdam Hornacek @SuppressWarnings("unchecked") 92b28a5538SAdam Hornacek @Override fillSession(HttpServletRequest req, User user)93b28a5538SAdam Hornacek public void fillSession(HttpServletRequest req, User user) { 944b613dedSAdam Hornacek boolean sessionAllowed = false; 95b28a5538SAdam Hornacek LdapUser ldapUser; 9653c33ae5SVladimir Kotal Map<String, Set<String>> records = null; 97b28a5538SAdam Hornacek Set<String> attributeValues; 98b28a5538SAdam Hornacek 99b28a5538SAdam Hornacek updateSession(req, sessionAllowed); 100b28a5538SAdam Hornacek 101b28a5538SAdam Hornacek if ((ldapUser = (LdapUser) req.getSession().getAttribute(LdapUserPlugin.SESSION_ATTR)) == null) { 102b28a5538SAdam Hornacek LOGGER.log(Level.WARNING, "cannot get {0} attribute", LdapUserPlugin.SESSION_ATTR); 103b28a5538SAdam Hornacek return; 104b28a5538SAdam Hornacek } 105b28a5538SAdam Hornacek 106b28a5538SAdam Hornacek // Check attributes cached in LDAP user object first, then query LDAP server 107b28a5538SAdam Hornacek // (and if found, cache the result in the LDAP user object). 108b28a5538SAdam Hornacek attributeValues = ldapUser.getAttribute(ldapAttr); 109b28a5538SAdam Hornacek if (attributeValues != null) { 110b28a5538SAdam Hornacek sessionAllowed = attributeValues.stream().anyMatch((t) -> whitelist.contains(t)); 111b28a5538SAdam Hornacek } else { 112b28a5538SAdam Hornacek try { 113*02df4614SVladimir Kotal String dn = ldapUser.getDn(); 11417deb9edSVladimir Kotal if (dn != null) { 11553c33ae5SVladimir Kotal LOGGER.log(Level.FINEST, "searching with dn={0}", dn); 11617deb9edSVladimir Kotal AbstractLdapProvider.LdapSearchResult<Map<String, Set<String>>> res; 11717deb9edSVladimir Kotal if ((res = getLdapProvider().lookupLdapContent(dn, new String[]{ldapAttr})) == null) { 11853c33ae5SVladimir Kotal LOGGER.log(Level.WARNING, "cannot lookup attributes {0} for user {1} (LDAP provider: {2})", 11953c33ae5SVladimir Kotal new Object[]{ldapAttr, user, getLdapProvider()}); 1204126de0dSVladimir Kotal return; 12153c33ae5SVladimir Kotal } 12253c33ae5SVladimir Kotal 12317deb9edSVladimir Kotal records = res.getAttrs(); 124*02df4614SVladimir Kotal } else { 125*02df4614SVladimir Kotal LOGGER.log(Level.FINE, "no DN for user {0}", user); 126b28a5538SAdam Hornacek } 127b28a5538SAdam Hornacek } catch (LdapException ex) { 128b28a5538SAdam Hornacek throw new AuthorizationException(ex); 129b28a5538SAdam Hornacek } 130b28a5538SAdam Hornacek 13153c33ae5SVladimir Kotal if (records == null || records.isEmpty() || (attributeValues = records.get(ldapAttr)) == null) { 132b28a5538SAdam Hornacek LOGGER.log(Level.WARNING, "empty records or attribute values {0} for user {1}", 133b28a5538SAdam Hornacek new Object[]{ldapAttr, user}); 134b28a5538SAdam Hornacek return; 135b28a5538SAdam Hornacek } 136b28a5538SAdam Hornacek 137b28a5538SAdam Hornacek ldapUser.setAttribute(ldapAttr, attributeValues); 138b28a5538SAdam Hornacek sessionAllowed = attributeValues.stream().anyMatch((t) -> whitelist.contains(t)); 139b28a5538SAdam Hornacek } 140b28a5538SAdam Hornacek 141b28a5538SAdam Hornacek updateSession(req, sessionAllowed); 142b28a5538SAdam Hornacek } 143b28a5538SAdam Hornacek 144b28a5538SAdam Hornacek /** 145b28a5538SAdam Hornacek * Add a new allowed value into the session. 146b28a5538SAdam Hornacek * 147b28a5538SAdam Hornacek * @param req the request 148b28a5538SAdam Hornacek * @param allowed the new value 149b28a5538SAdam Hornacek */ updateSession(HttpServletRequest req, boolean allowed)150b28a5538SAdam Hornacek protected void updateSession(HttpServletRequest req, boolean allowed) { 151b28a5538SAdam Hornacek req.getSession().setAttribute(sessionAllowed, allowed); 152b28a5538SAdam Hornacek } 153b28a5538SAdam Hornacek 154b28a5538SAdam Hornacek @Override checkEntity(HttpServletRequest request, Project project)155b28a5538SAdam Hornacek public boolean checkEntity(HttpServletRequest request, Project project) { 156b28a5538SAdam Hornacek return ((Boolean) request.getSession().getAttribute(sessionAllowed)); 157b28a5538SAdam Hornacek } 158b28a5538SAdam Hornacek 159b28a5538SAdam Hornacek @Override checkEntity(HttpServletRequest request, Group group)160b28a5538SAdam Hornacek public boolean checkEntity(HttpServletRequest request, Group group) { 161b28a5538SAdam Hornacek return ((Boolean) request.getSession().getAttribute(sessionAllowed)); 162b28a5538SAdam Hornacek } 163b28a5538SAdam Hornacek } 164