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, 2019 Oracle and/or its affiliates. All rights reserved.
22  */
23 package opengrok.auth.plugin;
24 
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.Set;
28 import java.util.TreeMap;
29 import java.util.logging.Level;
30 import java.util.logging.Logger;
31 import java.util.regex.PatternSyntaxException;
32 import javax.servlet.http.HttpServletRequest;
33 import opengrok.auth.entity.LdapUser;
34 import opengrok.auth.plugin.entity.User;
35 import opengrok.auth.plugin.ldap.LdapException;
36 import opengrok.auth.plugin.util.FilterUtil;
37 import org.opengrok.indexer.authorization.AuthorizationException;
38 import org.opengrok.indexer.configuration.Group;
39 import org.opengrok.indexer.configuration.Project;
40 
41 import static opengrok.auth.plugin.util.FilterUtil.expandUserFilter;
42 import static opengrok.auth.plugin.util.FilterUtil.replace;
43 
44 /**
45  * Authorization plug-in to check if given user matches configured LDAP filter.
46  *
47  * @author Krystof Tulinger
48  */
49 public class LdapFilterPlugin extends AbstractLdapPlugin {
50 
51     private static final Logger LOGGER = Logger.getLogger(LdapFilterPlugin.class.getName());
52 
53     protected static final String FILTER_PARAM = "filter";
54     protected static final String TRANSFORMS_PARAM = "transforms";
55     private static final String SESSION_ALLOWED_PREFIX = "opengrok-filter-plugin-allowed";
56     private static final String INSTANCE = "instance";
57     private String sessionAllowed = SESSION_ALLOWED_PREFIX;
58 
59     /**
60      * List of configuration names.
61      * <ul>
62      * <li><code>filter</code> is LDAP filter used for searching (mandatory)</li>
63      * <li><code>instance</code> is number of <code>LdapUserInstance</code> plugin to use (optional)</li>
64      * <li><code>transforms</code> are comma separated string transforms, where each transform is name:value pair,
65      * allowed values: <code>toLowerCase</code>, <code>toUpperCase</code></li>
66      * </ul>
67      */
68     private String ldapFilter;
69     private Integer ldapUserInstance;
70     private Map<String, String> transforms;
71 
LdapFilterPlugin()72     public LdapFilterPlugin() {
73         sessionAllowed += "-" + nextId++;
74     }
75 
76     @Override
load(Map<String, Object> parameters)77     public void load(Map<String, Object> parameters) {
78         super.load(parameters);
79 
80         if ((ldapFilter = (String) parameters.get(FILTER_PARAM)) == null) {
81             throw new NullPointerException("Missing param [" + FILTER_PARAM + "] in the setup");
82         }
83 
84         String instance = (String) parameters.get(INSTANCE);
85         if (instance != null) {
86             ldapUserInstance = Integer.parseInt(instance);
87         }
88 
89         String transformsString = (String) parameters.get(TRANSFORMS_PARAM);
90         if (transformsString != null) {
91             loadTransforms(transformsString);
92         }
93 
94         LOGGER.log(Level.FINE, "LdapFilter plugin loaded with filter={0}, instance={1}, transforms={2}",
95                 new Object[]{ldapFilter, ldapUserInstance, transforms});
96     }
97 
loadTransforms(String transformsString)98     void loadTransforms(String transformsString) throws NullPointerException {
99         transforms = new TreeMap<>();
100         String[] transformsArray = transformsString.split(",");
101         for (String elem: transformsArray) {
102             String[] tran = elem.split(":");
103             transforms.put(tran[0], tran[1]);
104         }
105         FilterUtil.checkTransforms(transforms);
106     }
107 
108     @Override
sessionExists(HttpServletRequest req)109     protected boolean sessionExists(HttpServletRequest req) {
110         return super.sessionExists(req)
111                 && req.getSession().getAttribute(sessionAllowed) != null;
112     }
113 
getSessionAttr()114     private String getSessionAttr() {
115         return (LdapUserPlugin.SESSION_ATTR + (ldapUserInstance != null ? ldapUserInstance.toString() : ""));
116     }
117 
118     @Override
fillSession(HttpServletRequest req, User user)119     public void fillSession(HttpServletRequest req, User user) {
120         LdapUser ldapUser;
121 
122         updateSession(req, false);
123 
124         if ((ldapUser = (LdapUser) req.getSession().getAttribute(getSessionAttr())) == null) {
125             LOGGER.log(Level.FINER, "failed to get LDAP attribute " + LdapUserPlugin.SESSION_ATTR);
126             return;
127         }
128 
129         String expandedFilter = expandFilter(ldapFilter, ldapUser, user);
130         LOGGER.log(Level.FINER, "expanded filter for user {0} and LDAP user {1} into ''{2}''",
131                 new Object[]{user, ldapUser, expandedFilter});
132         try {
133             if ((getLdapProvider().lookupLdapContent(null, expandedFilter)) == null) {
134                 LOGGER.log(Level.FINER, "failed to get content for user from LDAP server");
135                 return;
136             }
137         } catch (LdapException ex) {
138             throw new AuthorizationException(ex);
139         }
140 
141         updateSession(req, true);
142     }
143 
144     /**
145      * Expand {@code LdapUser} / {@code User} object attribute values into the filter.
146      *
147      * @see opengrok.auth.plugin.util.FilterUtil
148      *
149      * Use \% for printing the '%' character.
150      *
151      * @param filter basic filter containing the special values
152      * @param ldapUser user from LDAP
153      * @param user user from the request
154      * @return the filter with replacements
155      */
expandFilter(String filter, LdapUser ldapUser, User user)156     String expandFilter(String filter, LdapUser ldapUser, User user) {
157 
158         filter = expandUserFilter(user, filter, transforms);
159 
160         for (Entry<String, Set<String>> entry : ldapUser.getAttributes().entrySet()) {
161             if (entry.getValue().size() == 1) {
162                 try {
163                     filter = replace(filter, entry.getKey(),
164                             entry.getValue().iterator().next(), transforms);
165                 } catch (PatternSyntaxException ex) {
166                     LOGGER.log(Level.WARNING, "The pattern for expanding is not valid", ex);
167                 }
168             }
169         }
170 
171         filter = filter.replaceAll("\\\\%", "%");
172 
173         return filter;
174     }
175 
176     /**
177      * Add a new allowed value into the session.
178      *
179      * @param req the request
180      * @param allowed the new value
181      */
updateSession(HttpServletRequest req, boolean allowed)182     protected void updateSession(HttpServletRequest req, boolean allowed) {
183         req.getSession().setAttribute(sessionAllowed, allowed);
184     }
185 
186     @Override
checkEntity(HttpServletRequest request, Project project)187     public boolean checkEntity(HttpServletRequest request, Project project) {
188         return ((Boolean) request.getSession().getAttribute(sessionAllowed));
189     }
190 
191     @Override
checkEntity(HttpServletRequest request, Group group)192     public boolean checkEntity(HttpServletRequest request, Group group) {
193         return ((Boolean) request.getSession().getAttribute(sessionAllowed));
194     }
195 }
196