xref: /OpenGrok/plugins/src/main/java/opengrok/auth/plugin/LdapUserPlugin.java (revision 750d880b706e45f9dc910f25127a0402a967654d)
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 
41b28a5538SAdam Hornacek /**
42b28a5538SAdam Hornacek  * Authorization plug-in to extract user's LDAP attributes.
43b28a5538SAdam Hornacek  * The attributes can be then used by the other LDAP plugins down the stack.
44b28a5538SAdam Hornacek  *
45b28a5538SAdam Hornacek  * @author Krystof Tulinger
46b28a5538SAdam Hornacek  */
47b28a5538SAdam Hornacek public class LdapUserPlugin extends AbstractLdapPlugin {
48b28a5538SAdam Hornacek 
49b28a5538SAdam Hornacek     private static final Logger LOGGER = Logger.getLogger(LdapUserPlugin.class.getName());
50b28a5538SAdam Hornacek 
51b28a5538SAdam Hornacek     public static final String SESSION_ATTR = "opengrok-ldap-plugin-user";
52b28a5538SAdam Hornacek 
5353c33ae5SVladimir Kotal     /**
5453c33ae5SVladimir Kotal      * configuration names
5553c33ae5SVladimir Kotal      * <ul>
5653c33ae5SVladimir Kotal      * <li><code>filter</code> is LDAP filter used for searching (optional)</li>
5753c33ae5SVladimir Kotal      * <li><code>useDN</code> boolean value indicating if User.username should be used as search DN</li>
5853c33ae5SVladimir Kotal      * <li><code>attributes</code> is comma separated list of LDAP attributes to be produced (mandatory)</li>
5953c33ae5SVladimir Kotal      * </ul>
6053c33ae5SVladimir Kotal      */
6153c33ae5SVladimir Kotal     protected static final String LDAP_FILTER = "filter";
62b28a5538SAdam Hornacek     protected static final String ATTRIBUTES = "attributes";
6353c33ae5SVladimir Kotal     protected static final String USE_DN = "useDN";
64b28a5538SAdam Hornacek 
6553c33ae5SVladimir Kotal     private String ldapFilter;
6653c33ae5SVladimir Kotal     private Boolean useDN;
6753c33ae5SVladimir Kotal     private Set<String> attributes;
68b28a5538SAdam Hornacek 
69b28a5538SAdam Hornacek     @Override
load(Map<String, Object> parameters)70b28a5538SAdam Hornacek     public void load(Map<String, Object> parameters) {
71b28a5538SAdam Hornacek         super.load(parameters);
72b28a5538SAdam Hornacek 
73b28a5538SAdam Hornacek         String attributesVal;
74b28a5538SAdam Hornacek         if ((attributesVal = (String) parameters.get(ATTRIBUTES)) == null) {
7553c33ae5SVladimir Kotal             throw new NullPointerException("Missing configuration parameter [" + ATTRIBUTES +
76b28a5538SAdam Hornacek                     "] in the setup");
77b28a5538SAdam Hornacek         }
7853c33ae5SVladimir Kotal         attributes = new HashSet<>(Arrays.asList(attributesVal.split(",")));
79b28a5538SAdam Hornacek 
8053c33ae5SVladimir Kotal         ldapFilter = (String) parameters.get(LDAP_FILTER);
8153c33ae5SVladimir Kotal 
8253c33ae5SVladimir Kotal         if ((useDN = (Boolean) parameters.get(USE_DN)) == null) {
8353c33ae5SVladimir Kotal             useDN = false;
8453c33ae5SVladimir Kotal         }
8553c33ae5SVladimir Kotal 
8653c33ae5SVladimir Kotal         LOGGER.log(Level.FINE, "LdapUser plugin loaded with filter={0}, " +
8753c33ae5SVladimir Kotal                         "attributes={1}, useDN={2}",
8853c33ae5SVladimir 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 
10453c33ae5SVladimir Kotal     /**
10553c33ae5SVladimir Kotal      * Expand User attribute values into the filter.
10653c33ae5SVladimir Kotal      *
10753c33ae5SVladimir Kotal      * Special values are:
10853c33ae5SVladimir Kotal      * <ul>
10953c33ae5SVladimir Kotal      * <li>%username% - to be replaced with username value from the User object</li>
11053c33ae5SVladimir Kotal      * <li>%guid% - to be replaced with guid value from the User object</li>
11153c33ae5SVladimir Kotal      * </ul>
11253c33ae5SVladimir Kotal      *
11353c33ae5SVladimir Kotal      * Use \% for printing the '%' character.
11453c33ae5SVladimir Kotal      *
115*750d880bSVladimir Kotal      * @param user User object from the request (created by {@code UserPlugin})
11653c33ae5SVladimir Kotal      * @return replaced result
11753c33ae5SVladimir Kotal      */
expandFilter(User user)11853c33ae5SVladimir Kotal     protected String expandFilter(User user) {
11953c33ae5SVladimir Kotal         String filter = ldapFilter;
120b28a5538SAdam Hornacek 
12153c33ae5SVladimir Kotal         filter = filter.replaceAll("(?<!\\\\)%username(?<!\\\\)%", user.getUsername());
12253c33ae5SVladimir Kotal         filter = filter.replaceAll("(?<!\\\\)%guid(?<!\\\\)%", user.getId());
123b28a5538SAdam Hornacek 
12453c33ae5SVladimir Kotal         filter = filter.replaceAll("\\\\%", "%");
12553c33ae5SVladimir Kotal 
12653c33ae5SVladimir 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) {
13653c33ae5SVladimir Kotal             LOGGER.log(Level.WARNING, "cannot get LDAP provider");
137b28a5538SAdam Hornacek             return;
138b28a5538SAdam Hornacek         }
139b28a5538SAdam Hornacek 
14053c33ae5SVladimir Kotal         String expandedFilter = null;
14117deb9edSVladimir Kotal         String dn;
14253c33ae5SVladimir Kotal         if (ldapFilter != null) {
14353c33ae5SVladimir Kotal             expandedFilter = expandFilter(user);
14453c33ae5SVladimir Kotal         }
145b28a5538SAdam Hornacek         try {
14617deb9edSVladimir Kotal             AbstractLdapProvider.LdapSearchResult<Map<String, Set<String>>> res;
14717deb9edSVladimir Kotal             if ((res = getLdapProvider().lookupLdapContent(useDN ? user.getUsername() : null,
14853c33ae5SVladimir Kotal                     expandedFilter, attributes.toArray(new String[attributes.size()]))) == null) {
149b28a5538SAdam Hornacek                 LOGGER.log(Level.WARNING, "failed to get LDAP attributes ''{3}'' for user ''{0}'' " +
150b28a5538SAdam Hornacek                                 "with filter ''{1}''",
15153c33ae5SVladimir Kotal                         new Object[]{user, expandedFilter, String.join(", ", attributes)});
152b28a5538SAdam Hornacek                 return;
153b28a5538SAdam Hornacek             }
15417deb9edSVladimir Kotal 
15517deb9edSVladimir Kotal             records = res.getAttrs();
15617deb9edSVladimir Kotal             dn = res.getDN();
157b28a5538SAdam Hornacek         } catch (LdapException ex) {
158b28a5538SAdam Hornacek             throw new AuthorizationException(ex);
159b28a5538SAdam Hornacek         }
160b28a5538SAdam Hornacek 
161b28a5538SAdam Hornacek         if (records.isEmpty()) {
162b28a5538SAdam Hornacek             LOGGER.log(Level.WARNING, "LDAP records for user {0} are empty",
163b28a5538SAdam Hornacek                     user);
164b28a5538SAdam Hornacek             return;
165b28a5538SAdam Hornacek         }
166b28a5538SAdam Hornacek 
167b28a5538SAdam Hornacek         for (String attrName : attributes) {
168b28a5538SAdam Hornacek             if (!records.containsKey(attrName) || records.get(attrName).isEmpty()) {
16953c33ae5SVladimir Kotal                 LOGGER.log(Level.WARNING, "''{0}'' record for user {1} is not present or empty (LDAP provider: {2})",
17053c33ae5SVladimir Kotal                         new Object[]{attrName, user, getLdapProvider()});
171b28a5538SAdam Hornacek             }
172b28a5538SAdam Hornacek         }
173b28a5538SAdam Hornacek 
174b28a5538SAdam Hornacek         Map<String, Set<String>> attrSet = new HashMap<>();
175b28a5538SAdam Hornacek         for (String attrName : attributes) {
176b28a5538SAdam Hornacek             attrSet.put(attrName, records.get(attrName));
177b28a5538SAdam Hornacek         }
178b28a5538SAdam Hornacek 
179c17762fcSVladimir Kotal         updateSession(req, new LdapUser(useDN ? user.getUsername() : dn, attrSet));
180b28a5538SAdam Hornacek     }
181b28a5538SAdam Hornacek 
182b28a5538SAdam Hornacek     /**
183b28a5538SAdam Hornacek      * Add a new user value into the session.
184b28a5538SAdam Hornacek      *
185b28a5538SAdam Hornacek      * @param req the request
186b28a5538SAdam Hornacek      * @param user the new value for user
187b28a5538SAdam Hornacek      */
updateSession(HttpServletRequest req, LdapUser user)188b28a5538SAdam Hornacek     protected void updateSession(HttpServletRequest req, LdapUser user) {
189b28a5538SAdam Hornacek         req.getSession().setAttribute(SESSION_ATTR, user);
190b28a5538SAdam Hornacek     }
191b28a5538SAdam Hornacek 
192b28a5538SAdam Hornacek     @Override
checkEntity(HttpServletRequest request, Project project)193b28a5538SAdam Hornacek     public boolean checkEntity(HttpServletRequest request, Project project) {
194b28a5538SAdam Hornacek         return request.getSession().getAttribute(SESSION_ATTR) != null;
195b28a5538SAdam Hornacek     }
196b28a5538SAdam Hornacek 
197b28a5538SAdam Hornacek     @Override
checkEntity(HttpServletRequest request, Group group)198b28a5538SAdam Hornacek     public boolean checkEntity(HttpServletRequest request, Group group) {
199b28a5538SAdam Hornacek         return request.getSession().getAttribute(SESSION_ATTR) != null;
200b28a5538SAdam Hornacek     }
201b28a5538SAdam Hornacek }
202