xref: /OpenGrok/plugins/src/main/java/opengrok/auth/plugin/LdapAttrPlugin.java (revision 7b43edc3e135be8ba18592d2f0676003f24ebf44)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
22  */
23 package opengrok.auth.plugin;
24 
25 import java.io.IOException;
26 import java.nio.file.Files;
27 import java.nio.file.Paths;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.TreeSet;
31 import java.util.logging.Level;
32 import java.util.logging.Logger;
33 import java.util.stream.Stream;
34 
35 import jakarta.servlet.http.HttpServletRequest;
36 import opengrok.auth.entity.LdapUser;
37 import opengrok.auth.plugin.entity.User;
38 import opengrok.auth.plugin.ldap.AbstractLdapProvider;
39 import opengrok.auth.plugin.ldap.LdapException;
40 import org.opengrok.indexer.authorization.AuthorizationException;
41 import org.opengrok.indexer.configuration.Group;
42 import org.opengrok.indexer.configuration.Project;
43 
44 /**
45  * Authorization plug-in to check user's LDAP attribute against whitelist.
46  *
47  * This plugin heavily relies on the presence of the {@code LdapUserPlugin} in the stack above it,
48  * since it is using the Distinguished Name of the {@code LdapUser} to perform the LDAP lookup.
49  *
50  * @author Krystof Tulinger
51  */
52 public class LdapAttrPlugin extends AbstractLdapPlugin {
53 
54     private static final Logger LOGGER = Logger.getLogger(LdapAttrPlugin.class.getName());
55 
56     /**
57      * List of configuration names.
58      * <ul>
59      * <li><code>attribute</code> is LDAP attribute to check (mandatory)</li>
60      * <li><code>file</code> whitelist file (mandatory)</li>
61      * <li><code>instance</code> is number of <code>LdapUserInstance</code> plugin to use (optional)</li>
62      * </ul>
63      */
64     static final String ATTR_PARAM = "attribute";
65     static final String FILE_PARAM = "file";
66     static final String INSTANCE_PARAM = "instance";
67 
68     private static final String SESSION_ALLOWED_PREFIX = "opengrok-ldap-attr-plugin-allowed";
69     private String sessionAllowed = SESSION_ALLOWED_PREFIX;
70 
71     private String ldapAttr;
72     private final Set<String> whitelist = new TreeSet<>();
73     private Integer ldapUserInstance;
74     private String filePath;
75 
LdapAttrPlugin()76     public LdapAttrPlugin() {
77         sessionAllowed += "-" + nextId++;
78     }
79 
80     // for testing
load(Map<String, Object> parameters, AbstractLdapProvider provider)81     void load(Map<String, Object> parameters, AbstractLdapProvider provider) {
82         super.load(provider);
83 
84         init(parameters);
85     }
86 
87     @Override
load(Map<String, Object> parameters)88     public void load(Map<String, Object> parameters) {
89         super.load(parameters);
90 
91         init(parameters);
92     }
93 
init(Map<String, Object> parameters)94     private void init(Map<String, Object> parameters) {
95         if ((ldapAttr = (String) parameters.get(ATTR_PARAM)) == null) {
96             throw new NullPointerException("Missing param [" + ATTR_PARAM + "] in the setup");
97         }
98 
99         if ((filePath = (String) parameters.get(FILE_PARAM)) == null) {
100             throw new NullPointerException("Missing param [" + FILE_PARAM + "] in the setup");
101         }
102 
103         String instance = (String) parameters.get(INSTANCE_PARAM);
104         if (instance != null) {
105             ldapUserInstance = Integer.parseInt(instance);
106         }
107 
108         try (Stream<String> stream = Files.lines(Paths.get(filePath))) {
109             stream.forEach(whitelist::add);
110         } catch (IOException e) {
111             throw new IllegalArgumentException(String.format("Unable to read the file \"%s\"", filePath), e);
112         }
113 
114         LOGGER.log(Level.FINE, "LdapAttrPlugin plugin loaded with attr={0}, whitelist={1} ({2} entries), " +
115                         "instance={3}", new Object[]{ldapAttr, filePath, whitelist.size(), ldapUserInstance});
116     }
117 
118     @Override
sessionExists(HttpServletRequest req)119     protected boolean sessionExists(HttpServletRequest req) {
120         return super.sessionExists(req)
121                 && req.getSession().getAttribute(sessionAllowed) != null;
122     }
123 
getSessionAllowedAttrName()124     String getSessionAllowedAttrName() {
125         return sessionAllowed;
126     }
127 
128     @Override
fillSession(HttpServletRequest req, User user)129     public void fillSession(HttpServletRequest req, User user) {
130         updateSession(req, false);
131 
132         LdapUser ldapUser = (LdapUser) req.getSession().getAttribute(LdapUserPlugin.getSessionAttrName(ldapUserInstance));
133         if (ldapUser == null) {
134             LOGGER.log(Level.WARNING, "cannot get {0} attribute from {1}",
135                     new Object[]{LdapUserPlugin.SESSION_ATTR, user});
136             return;
137         }
138 
139         // Check attributes cached in LDAP user object first, then query LDAP server
140         // (and if found, cache the result in the LDAP user object).
141         Set<String> attributeValues = ldapUser.getAttribute(ldapAttr);
142         if (attributeValues == null) {
143             Map<String, Set<String>> records = null;
144             AbstractLdapProvider ldapProvider = getLdapProvider();
145             try {
146                 String dn = ldapUser.getDn();
147                 if (dn != null) {
148                     LOGGER.log(Level.FINEST, "searching with dn={0} on {1}",
149                             new Object[]{dn, ldapProvider});
150                     AbstractLdapProvider.LdapSearchResult<Map<String, Set<String>>> res;
151                     if ((res = ldapProvider.lookupLdapContent(dn, new String[]{ldapAttr})) == null) {
152                         LOGGER.log(Level.WARNING, "cannot lookup attributes {0} for user {1} on {2})",
153                                 new Object[]{ldapAttr, ldapUser, ldapProvider});
154                         return;
155                     }
156 
157                     records = res.getAttrs();
158                 } else {
159                     LOGGER.log(Level.FINE, "no DN for LDAP user {0} on {1}",
160                             new Object[]{ldapUser, ldapProvider});
161                 }
162             } catch (LdapException ex) {
163                 throw new AuthorizationException(ex);
164             }
165 
166             if (records == null || records.isEmpty() || (attributeValues = records.get(ldapAttr)) == null) {
167                 LOGGER.log(Level.WARNING, "empty records or attribute values {0} for user {1} on {2}",
168                         new Object[]{ldapAttr, ldapUser, ldapProvider});
169                 return;
170             }
171 
172             ldapUser.setAttribute(ldapAttr, attributeValues);
173         }
174 
175         boolean isAttrInWhitelist = attributeValues.stream().anyMatch(whitelist::contains);
176         LOGGER.log(Level.FINEST, "LDAP user {0} {1} against {2}",
177                 new Object[]{ldapUser, isAttrInWhitelist ? "allowed" : "denied", filePath});
178         updateSession(req, isAttrInWhitelist);
179     }
180 
181     /**
182      * Add a new allowed value into the session.
183      *
184      * @param req the request
185      * @param allowed the new value
186      */
updateSession(HttpServletRequest req, boolean allowed)187     private void updateSession(HttpServletRequest req, boolean allowed) {
188         req.getSession().setAttribute(sessionAllowed, allowed);
189     }
190 
191     @Override
checkEntity(HttpServletRequest request, Project project)192     public boolean checkEntity(HttpServletRequest request, Project project) {
193         return ((Boolean) request.getSession().getAttribute(sessionAllowed));
194     }
195 
196     @Override
checkEntity(HttpServletRequest request, Group group)197     public boolean checkEntity(HttpServletRequest request, Group group) {
198         return ((Boolean) request.getSession().getAttribute(sessionAllowed));
199     }
200 }
201