Skip to content

Commit 555fbbc

Browse files
author
Vladimir Kotal
authored
LDAP uid case (in)sensitivity fix for LdapFilter plugin (#2949)
fixes #2946
1 parent 658703f commit 555fbbc

File tree

4 files changed

+175
-9
lines changed

4 files changed

+175
-9
lines changed

plugins/src/main/java/opengrok/auth/plugin/LdapFilterPlugin.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,21 @@
2525
import java.util.Map;
2626
import java.util.Map.Entry;
2727
import java.util.Set;
28+
import java.util.TreeMap;
2829
import java.util.logging.Level;
2930
import java.util.logging.Logger;
3031
import java.util.regex.PatternSyntaxException;
3132
import javax.servlet.http.HttpServletRequest;
3233
import opengrok.auth.entity.LdapUser;
3334
import opengrok.auth.plugin.entity.User;
3435
import opengrok.auth.plugin.ldap.LdapException;
36+
import opengrok.auth.plugin.util.FilterUtil;
3537
import org.opengrok.indexer.authorization.AuthorizationException;
3638
import org.opengrok.indexer.configuration.Group;
3739
import org.opengrok.indexer.configuration.Project;
3840

3941
import static opengrok.auth.plugin.util.FilterUtil.expandUserFilter;
42+
import static opengrok.auth.plugin.util.FilterUtil.replace;
4043

4144
/**
4245
* Authorization plug-in to check if given user matches configured LDAP filter.
@@ -48,6 +51,7 @@ public class LdapFilterPlugin extends AbstractLdapPlugin {
4851
private static final Logger LOGGER = Logger.getLogger(LdapFilterPlugin.class.getName());
4952

5053
protected static final String FILTER_PARAM = "filter";
54+
protected static final String TRANSFORMS_PARAM = "transforms";
5155
private static final String SESSION_ALLOWED_PREFIX = "opengrok-filter-plugin-allowed";
5256
private static final String INSTANCE = "instance";
5357
private String sessionAllowed = SESSION_ALLOWED_PREFIX;
@@ -57,10 +61,13 @@ public class LdapFilterPlugin extends AbstractLdapPlugin {
5761
* <ul>
5862
* <li><code>filter</code> is LDAP filter used for searching (mandatory)</li>
5963
* <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>
6066
* </ul>
6167
*/
6268
private String ldapFilter;
6369
private Integer ldapUserInstance;
70+
private Map<String, String> transforms;
6471

6572
public LdapFilterPlugin() {
6673
sessionAllowed += "-" + nextId++;
@@ -79,8 +86,23 @@ public void load(Map<String, Object> parameters) {
7986
ldapUserInstance = Integer.parseInt(instance);
8087
}
8188

82-
LOGGER.log(Level.FINE, "LdapFilter plugin loaded with filter={0}, instance={1}",
83-
new Object[]{ldapFilter, ldapUserInstance});
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+
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);
84106
}
85107

86108
@Override
@@ -133,14 +155,13 @@ public void fillSession(HttpServletRequest req, User user) {
133155
*/
134156
String expandFilter(String filter, LdapUser ldapUser, User user) {
135157

136-
filter = expandUserFilter(user, filter);
158+
filter = expandUserFilter(user, filter, transforms);
137159

138160
for (Entry<String, Set<String>> entry : ldapUser.getAttributes().entrySet()) {
139161
if (entry.getValue().size() == 1) {
140162
try {
141-
filter = filter.replaceAll(
142-
"(?<!\\\\)%" + entry.getKey() + "(?<!\\\\)%",
143-
entry.getValue().iterator().next());
163+
filter = replace(filter, entry.getKey(),
164+
entry.getValue().iterator().next(), transforms);
144165
} catch (PatternSyntaxException ex) {
145166
LOGGER.log(Level.WARNING, "The pattern for expanding is not valid", ex);
146167
}

plugins/src/main/java/opengrok/auth/plugin/util/FilterUtil.java

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,76 @@
2525

2626
import opengrok.auth.plugin.entity.User;
2727

28+
import java.util.Arrays;
29+
import java.util.HashSet;
30+
import java.util.Locale;
31+
import java.util.Map;
32+
import java.util.Set;
33+
2834
public class FilterUtil {
2935

3036
private FilterUtil() {
3137
// utility class
3238
}
3339

40+
private static final String LOWER_CASE = "toLowerCase";
41+
private static final String UPPER_CASE = "toUpperCase";
42+
43+
/**
44+
* Verify the names of the transforms in the map.
45+
* @param transforms map of attribute transforms. Valid values: <code>toLowerCase, toUpperCase</code>
46+
* @throws UnsupportedOperationException in case of invalid transform name
47+
*/
48+
public static void checkTransforms(Map<String, String> transforms) {
49+
Set<String> possibleTransforms = new HashSet<>(Arrays.asList(LOWER_CASE, UPPER_CASE));
50+
for (String transform : transforms.values()) {
51+
if (!possibleTransforms.contains(transform)) {
52+
throw new UnsupportedOperationException(String.format("invalid transform: %s", transform));
53+
}
54+
}
55+
}
56+
57+
static String doTransform(String value, String transform) {
58+
switch (transform) {
59+
case LOWER_CASE:
60+
return value.toLowerCase(Locale.ROOT);
61+
case UPPER_CASE:
62+
return value.toUpperCase(Locale.ROOT);
63+
default:
64+
throw new UnsupportedOperationException(String.format("transform '%s' is unsupported", transform));
65+
}
66+
}
67+
68+
/**
69+
* Expand attributes in filter string.
70+
* @param filter input string
71+
* @param name attribute name
72+
* @param value value to replace
73+
* @param transforms map of transformations to be potentially applied on the value
74+
* @return new value of the string
75+
*/
76+
public static String replace(String filter, String name, String value, Map<String, String> transforms) {
77+
if (transforms != null) {
78+
String transform;
79+
if ((transform = transforms.get(name)) != null) {
80+
value = doTransform(value, transform);
81+
}
82+
}
83+
84+
return filter.replaceAll("(?<!\\\\)%" + name + "(?<!\\\\)%", value);
85+
}
86+
87+
/**
88+
* Replace attribute names with values in filter string.
89+
* @param user User object
90+
* @param filter filter string
91+
* @return filter with the values replaced
92+
* @see #expandUserFilter(User, String, Map)
93+
*/
94+
public static String expandUserFilter(User user, String filter) {
95+
return expandUserFilter(user, filter, null);
96+
}
97+
3498
/**
3599
* Expand {@code User} object attribute values into the filter.
36100
*
@@ -41,14 +105,16 @@ private FilterUtil() {
41105
* </ul>
42106
*
43107
* @param user User object from the request (created by {@code UserPlugin})
108+
* @param filter filter
109+
* @param transforms map of transforms
44110
* @return replaced result
45111
*/
46-
public static String expandUserFilter(User user, String filter) {
112+
public static String expandUserFilter(User user, String filter, Map<String, String> transforms) {
47113
if (user.getUsername() != null) {
48-
filter = filter.replaceAll("(?<!\\\\)%username(?<!\\\\)%", user.getUsername());
114+
filter = replace(filter, "username", user.getUsername(), transforms);
49115
}
50116
if (user.getId() != null) {
51-
filter = filter.replaceAll("(?<!\\\\)%guid(?<!\\\\)%", user.getId());
117+
filter = replace(filter, "guid", user.getId(), transforms);
52118
}
53119

54120
return filter;

plugins/src/test/java/opengrok/auth/plugin/LdapFilterPluginTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,14 @@ public void expandFilterTest2() {
9797
assertEquals("(objectclass=%%%%)",
9898
plugin.expandFilter("(objectclass=\\%%\\%\\%)", ldapUser, user));
9999
}
100+
101+
@Test
102+
public void testLoadTransforms() {
103+
plugin.loadTransforms("foo:toUpperCase,bar:toLowerCase");
104+
}
105+
106+
@Test(expected = UnsupportedOperationException.class)
107+
public void testLoadTransformsNegative() {
108+
plugin.loadTransforms("foo:toUpperCase,ugly:nice");
109+
}
100110
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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) 2019 Oracle and/or its affiliates. All rights reserved.
22+
*/
23+
24+
package opengrok.auth.plugin.util;
25+
26+
import org.junit.Test;
27+
28+
import java.io.UnsupportedEncodingException;
29+
import java.util.Map;
30+
import java.util.TreeMap;
31+
32+
import static opengrok.auth.plugin.util.FilterUtil.doTransform;
33+
import static org.junit.Assert.assertEquals;
34+
35+
public class FilterUtilTest {
36+
@Test
37+
public void testTransforms() {
38+
assertEquals("FOO", doTransform("foo", "toUpperCase"));
39+
assertEquals("foo", doTransform("FOO", "toLowerCase"));
40+
}
41+
42+
@Test
43+
public void testTransformsUTF() throws UnsupportedEncodingException {
44+
assertEquals(new String("ČUČKAŘ".getBytes("UTF-8"), "UTF-8"),
45+
doTransform(new String("čučkař".getBytes("UTF-8"), "UTF-8"), "toUpperCase"));
46+
assertEquals(new String("čučkař".getBytes("UTF-8"), "UTF-8"),
47+
doTransform(new String("ČUČKAŘ".getBytes("UTF-8"), "UTF-8"), "toLowerCase"));
48+
}
49+
50+
@Test(expected = UnsupportedOperationException.class)
51+
public void testInvalidTransform() {
52+
doTransform("foo", "bar");
53+
}
54+
55+
@Test
56+
public void testReplace() {
57+
Map<String, String> transforms = new TreeMap<>();
58+
transforms.put("uid", "toUpperCase");
59+
assertEquals("fooUSERbar",
60+
FilterUtil.replace("foo%uid%bar", "uid", "user", transforms));
61+
}
62+
63+
@Test(expected = UnsupportedOperationException.class)
64+
public void testCheckTransforms() {
65+
Map<String, String> transforms = new TreeMap<>();
66+
transforms.put("uid", "xxx");
67+
FilterUtil.checkTransforms(transforms);
68+
}
69+
}

0 commit comments

Comments
 (0)