xref: /OpenGrok/plugins/src/main/java/opengrok/auth/plugin/LdapUserPlugin.java (revision b28a55383a9dd002a5369a73f8ba9c943d6dc214)
1*b28a5538SAdam Hornacek /*
2*b28a5538SAdam Hornacek  * CDDL HEADER START
3*b28a5538SAdam Hornacek  *
4*b28a5538SAdam Hornacek  * The contents of this file are subject to the terms of the
5*b28a5538SAdam Hornacek  * Common Development and Distribution License (the "License").
6*b28a5538SAdam Hornacek  * You may not use this file except in compliance with the License.
7*b28a5538SAdam Hornacek  *
8*b28a5538SAdam Hornacek  * See LICENSE.txt included in this distribution for the specific
9*b28a5538SAdam Hornacek  * language governing permissions and limitations under the License.
10*b28a5538SAdam Hornacek  *
11*b28a5538SAdam Hornacek  * When distributing Covered Code, include this CDDL HEADER in each
12*b28a5538SAdam Hornacek  * file and include the License file at LICENSE.txt.
13*b28a5538SAdam Hornacek  * If applicable, add the following below this CDDL HEADER, with the
14*b28a5538SAdam Hornacek  * fields enclosed by brackets "[]" replaced with your own identifying
15*b28a5538SAdam Hornacek  * information: Portions Copyright [yyyy] [name of copyright owner]
16*b28a5538SAdam Hornacek  *
17*b28a5538SAdam Hornacek  * CDDL HEADER END
18*b28a5538SAdam Hornacek  */
19*b28a5538SAdam Hornacek 
20*b28a5538SAdam Hornacek /*
21*b28a5538SAdam Hornacek  * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
22*b28a5538SAdam Hornacek  */
23*b28a5538SAdam Hornacek package opengrok.auth.plugin;
24*b28a5538SAdam Hornacek 
25*b28a5538SAdam Hornacek import java.util.HashMap;
26*b28a5538SAdam Hornacek import java.util.Map;
27*b28a5538SAdam Hornacek import java.util.Set;
28*b28a5538SAdam Hornacek import java.util.logging.Level;
29*b28a5538SAdam Hornacek import java.util.logging.Logger;
30*b28a5538SAdam Hornacek import java.util.regex.Matcher;
31*b28a5538SAdam Hornacek import java.util.regex.Pattern;
32*b28a5538SAdam Hornacek import javax.servlet.http.HttpServletRequest;
33*b28a5538SAdam Hornacek import opengrok.auth.entity.LdapUser;
34*b28a5538SAdam Hornacek import opengrok.auth.plugin.entity.User;
35*b28a5538SAdam Hornacek import opengrok.auth.plugin.ldap.LdapException;
36*b28a5538SAdam Hornacek import org.opengrok.indexer.authorization.AuthorizationException;
37*b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Group;
38*b28a5538SAdam Hornacek import org.opengrok.indexer.configuration.Project;
39*b28a5538SAdam Hornacek import org.opengrok.indexer.util.StringUtils;
40*b28a5538SAdam Hornacek 
41*b28a5538SAdam Hornacek /**
42*b28a5538SAdam Hornacek  * Authorization plug-in to extract user's LDAP attributes.
43*b28a5538SAdam Hornacek  * The attributes can be then used by the other LDAP plugins down the stack.
44*b28a5538SAdam Hornacek  *
45*b28a5538SAdam Hornacek  * @author Krystof Tulinger
46*b28a5538SAdam Hornacek  */
47*b28a5538SAdam Hornacek public class LdapUserPlugin extends AbstractLdapPlugin {
48*b28a5538SAdam Hornacek 
49*b28a5538SAdam Hornacek     private static final Logger LOGGER = Logger.getLogger(LdapUserPlugin.class.getName());
50*b28a5538SAdam Hornacek 
51*b28a5538SAdam Hornacek     public static final String SESSION_ATTR = "opengrok-ldap-plugin-user";
52*b28a5538SAdam Hornacek 
53*b28a5538SAdam Hornacek     /**
54*b28a5538SAdam Hornacek      * configuration names
55*b28a5538SAdam Hornacek      * <ul>
56*b28a5538SAdam Hornacek      * <li><code>objectclass</code> is LDAP object class</li>
57*b28a5538SAdam Hornacek      * <li><code>attributes</code> is comma separated list of LDAP attributes</li>
58*b28a5538SAdam Hornacek      * </ul>
59*b28a5538SAdam Hornacek      */
60*b28a5538SAdam Hornacek     protected static final String OBJECT_CLASS = "objectclass";
61*b28a5538SAdam Hornacek     protected static final String ATTRIBUTES = "attributes";
62*b28a5538SAdam Hornacek 
63*b28a5538SAdam Hornacek     private String objectClass;
64*b28a5538SAdam Hornacek     private String[] attributes;
65*b28a5538SAdam Hornacek     private final Pattern usernameCnPattern = Pattern.compile("(cn=[a-zA-Z0-9_-]+)");
66*b28a5538SAdam Hornacek 
67*b28a5538SAdam Hornacek     @Override
load(Map<String, Object> parameters)68*b28a5538SAdam Hornacek     public void load(Map<String, Object> parameters) {
69*b28a5538SAdam Hornacek         super.load(parameters);
70*b28a5538SAdam Hornacek 
71*b28a5538SAdam Hornacek         if ((objectClass = (String) parameters.get(OBJECT_CLASS)) == null) {
72*b28a5538SAdam Hornacek             throw new NullPointerException("Missing param [" + OBJECT_CLASS +
73*b28a5538SAdam Hornacek                     "] in the setup");
74*b28a5538SAdam Hornacek         }
75*b28a5538SAdam Hornacek 
76*b28a5538SAdam Hornacek         if (!StringUtils.isAlphanumeric(objectClass)) {
77*b28a5538SAdam Hornacek             throw new NullPointerException("object class '" + objectClass +
78*b28a5538SAdam Hornacek                     "' contains non-alphanumeric characters");
79*b28a5538SAdam Hornacek         }
80*b28a5538SAdam Hornacek 
81*b28a5538SAdam Hornacek         String attributesVal;
82*b28a5538SAdam Hornacek         if ((attributesVal = (String) parameters.get(ATTRIBUTES)) == null) {
83*b28a5538SAdam Hornacek             throw new NullPointerException("Missing param [" + ATTRIBUTES +
84*b28a5538SAdam Hornacek                     "] in the setup");
85*b28a5538SAdam Hornacek         }
86*b28a5538SAdam Hornacek         attributes = attributesVal.split(",");
87*b28a5538SAdam Hornacek 
88*b28a5538SAdam Hornacek         LOGGER.log(Level.FINE, "LdapUser plugin loaded with objectclass={0}, " +
89*b28a5538SAdam Hornacek                         "attributes={1}",
90*b28a5538SAdam Hornacek                 new Object[]{objectClass, String.join(", ", attributes)});
91*b28a5538SAdam Hornacek     }
92*b28a5538SAdam Hornacek 
93*b28a5538SAdam Hornacek     /**
94*b28a5538SAdam Hornacek      * Check if the session exists and contains all necessary fields required by
95*b28a5538SAdam Hornacek      * this plug-in.
96*b28a5538SAdam Hornacek      *
97*b28a5538SAdam Hornacek      * @param req the HTTP request
98*b28a5538SAdam Hornacek      * @return true if it does; false otherwise
99*b28a5538SAdam Hornacek      */
100*b28a5538SAdam Hornacek     @Override
sessionExists(HttpServletRequest req)101*b28a5538SAdam Hornacek     protected boolean sessionExists(HttpServletRequest req) {
102*b28a5538SAdam Hornacek         return super.sessionExists(req)
103*b28a5538SAdam Hornacek                 && req.getSession().getAttribute(SESSION_ATTR) != null;
104*b28a5538SAdam Hornacek     }
105*b28a5538SAdam Hornacek 
getFilter(User user)106*b28a5538SAdam Hornacek     protected String getFilter(User user) {
107*b28a5538SAdam Hornacek         String commonName;
108*b28a5538SAdam Hornacek 
109*b28a5538SAdam Hornacek         Matcher matcher = usernameCnPattern.matcher(user.getUsername());
110*b28a5538SAdam Hornacek         if (matcher.find()) {
111*b28a5538SAdam Hornacek             commonName = matcher.group(1);
112*b28a5538SAdam Hornacek             LOGGER.log(Level.FINEST, "extracted common name {0} from {1}",
113*b28a5538SAdam Hornacek                 new Object[]{commonName, user.getUsername()});
114*b28a5538SAdam Hornacek         } else {
115*b28a5538SAdam Hornacek             throw new AuthorizationException(String.format("cannot get common name out of %s",
116*b28a5538SAdam Hornacek                     user.getUsername()));
117*b28a5538SAdam Hornacek         }
118*b28a5538SAdam Hornacek 
119*b28a5538SAdam Hornacek         return "(&(objectclass=" + this.objectClass + ")(" + commonName + "))";
120*b28a5538SAdam Hornacek     }
121*b28a5538SAdam Hornacek 
122*b28a5538SAdam Hornacek     @Override
fillSession(HttpServletRequest req, User user)123*b28a5538SAdam Hornacek     public void fillSession(HttpServletRequest req, User user) {
124*b28a5538SAdam Hornacek         Map<String, Set<String>> records;
125*b28a5538SAdam Hornacek 
126*b28a5538SAdam Hornacek         updateSession(req, null);
127*b28a5538SAdam Hornacek 
128*b28a5538SAdam Hornacek         if (getLdapProvider() == null) {
129*b28a5538SAdam Hornacek             LOGGER.log(Level.WARNING, "cannot get LDAP provider for LdapUser plugin");
130*b28a5538SAdam Hornacek             return;
131*b28a5538SAdam Hornacek         }
132*b28a5538SAdam Hornacek 
133*b28a5538SAdam Hornacek         String filter = getFilter(user);
134*b28a5538SAdam Hornacek         try {
135*b28a5538SAdam Hornacek             if ((records = getLdapProvider().lookupLdapContent(null, filter, attributes)) == null) {
136*b28a5538SAdam Hornacek                 LOGGER.log(Level.WARNING, "failed to get LDAP attributes ''{3}'' for user ''{0}'' " +
137*b28a5538SAdam Hornacek                                 "with filter ''{1}''",
138*b28a5538SAdam Hornacek                         new Object[]{user, filter, String.join(", ", attributes)});
139*b28a5538SAdam Hornacek                 return;
140*b28a5538SAdam Hornacek             }
141*b28a5538SAdam Hornacek         } catch (LdapException ex) {
142*b28a5538SAdam Hornacek             throw new AuthorizationException(ex);
143*b28a5538SAdam Hornacek         }
144*b28a5538SAdam Hornacek 
145*b28a5538SAdam Hornacek         if (records.isEmpty()) {
146*b28a5538SAdam Hornacek             LOGGER.log(Level.WARNING, "LDAP records for user {0} are empty",
147*b28a5538SAdam Hornacek                     user);
148*b28a5538SAdam Hornacek             return;
149*b28a5538SAdam Hornacek         }
150*b28a5538SAdam Hornacek 
151*b28a5538SAdam Hornacek         for (String attrName : attributes) {
152*b28a5538SAdam Hornacek             if (!records.containsKey(attrName) || records.get(attrName).isEmpty()) {
153*b28a5538SAdam Hornacek                 LOGGER.log(Level.WARNING, "{0} record for user {1} is not present or empty",
154*b28a5538SAdam Hornacek                         new Object[]{attrName, user});
155*b28a5538SAdam Hornacek             }
156*b28a5538SAdam Hornacek         }
157*b28a5538SAdam Hornacek 
158*b28a5538SAdam Hornacek         Map<String, Set<String>> attrSet = new HashMap<>();
159*b28a5538SAdam Hornacek         for (String attrName : attributes) {
160*b28a5538SAdam Hornacek             attrSet.put(attrName, records.get(attrName));
161*b28a5538SAdam Hornacek         }
162*b28a5538SAdam Hornacek 
163*b28a5538SAdam Hornacek         updateSession(req, new LdapUser(attrSet));
164*b28a5538SAdam Hornacek     }
165*b28a5538SAdam Hornacek 
166*b28a5538SAdam Hornacek     /**
167*b28a5538SAdam Hornacek      * Add a new user value into the session.
168*b28a5538SAdam Hornacek      *
169*b28a5538SAdam Hornacek      * @param req the request
170*b28a5538SAdam Hornacek      * @param user the new value for user
171*b28a5538SAdam Hornacek      */
updateSession(HttpServletRequest req, LdapUser user)172*b28a5538SAdam Hornacek     protected void updateSession(HttpServletRequest req, LdapUser user) {
173*b28a5538SAdam Hornacek         req.getSession().setAttribute(SESSION_ATTR, user);
174*b28a5538SAdam Hornacek     }
175*b28a5538SAdam Hornacek 
176*b28a5538SAdam Hornacek     @Override
checkEntity(HttpServletRequest request, Project project)177*b28a5538SAdam Hornacek     public boolean checkEntity(HttpServletRequest request, Project project) {
178*b28a5538SAdam Hornacek         return request.getSession().getAttribute(SESSION_ATTR) != null;
179*b28a5538SAdam Hornacek     }
180*b28a5538SAdam Hornacek 
181*b28a5538SAdam Hornacek     @Override
checkEntity(HttpServletRequest request, Group group)182*b28a5538SAdam Hornacek     public boolean checkEntity(HttpServletRequest request, Group group) {
183*b28a5538SAdam Hornacek         return request.getSession().getAttribute(SESSION_ATTR) != null;
184*b28a5538SAdam Hornacek     }
185*b28a5538SAdam Hornacek }
186