xref: /OpenGrok/plugins/src/main/java/opengrok/auth/plugin/LdapAttrPlugin.java (revision 02df4614666b6055a3171d67c56ef7cb8c356c1c)
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