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