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.util.Arrays; 26 import java.util.Collections; 27 import java.util.HashMap; 28 import java.util.HashSet; 29 import java.util.Map; 30 import java.util.Set; 31 import java.util.logging.Level; 32 import java.util.logging.Logger; 33 34 import jakarta.servlet.http.HttpServletRequest; 35 import opengrok.auth.entity.LdapUser; 36 import opengrok.auth.plugin.entity.User; 37 import opengrok.auth.plugin.ldap.AbstractLdapProvider; 38 import opengrok.auth.plugin.ldap.LdapException; 39 import org.jetbrains.annotations.NotNull; 40 import org.opengrok.indexer.authorization.AuthorizationException; 41 import org.opengrok.indexer.configuration.Group; 42 import org.opengrok.indexer.configuration.Project; 43 44 import static opengrok.auth.plugin.util.FilterUtil.expandUserFilter; 45 46 /** 47 * Authorization plug-in to extract user's LDAP attributes. 48 * The attributes can be then used by the other LDAP plugins down the stack. 49 * 50 * @author Krystof Tulinger 51 */ 52 public class LdapUserPlugin extends AbstractLdapPlugin { 53 54 private static final Logger LOGGER = Logger.getLogger(LdapUserPlugin.class.getName()); 55 56 static final String SESSION_ATTR = "opengrok-ldap-plugin-user"; 57 static final String NEGATIVE_CACHE_ATTR = "opengrok-ldap-plugin-user-invalid-user"; 58 59 /** 60 * List of configuration names. 61 * <ul> 62 * <li><code>filter</code> is LDAP filter used for searching (optional)</li> 63 * <li><code>useDN</code> boolean value indicating if User.username should be treated as Distinguished Name 64 * (optional, default is false)</li> 65 * <li><code>attributes</code> is comma separated list of LDAP attributes to be produced (mandatory)</li> 66 * <li><code>instance</code> integer that can be used to identify instance of this plugin by other LDAP plugins 67 * (optional, default empty)</li> 68 * </ul> 69 */ 70 static final String LDAP_FILTER = "filter"; 71 static final String ATTRIBUTES = "attributes"; 72 static final String USE_DN = "useDN"; 73 static final String INSTANCE = "instance"; 74 75 private String ldapFilter; 76 private Boolean useDN; 77 private Set<String> attrSet; 78 private Integer instanceNum; 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 String attributesVal; 96 if ((attributesVal = (String) parameters.get(ATTRIBUTES)) == null) { 97 throw new NullPointerException("Missing configuration parameter [" + ATTRIBUTES + "] in the setup"); 98 } 99 attrSet = new HashSet<>(Arrays.asList(attributesVal.split(","))); 100 101 ldapFilter = (String) parameters.get(LDAP_FILTER); 102 103 if ((useDN = (Boolean) parameters.get(USE_DN)) == null) { 104 useDN = false; 105 } 106 107 String instanceParam = (String) parameters.get(INSTANCE); 108 if (instanceParam != null) { 109 instanceNum = Integer.parseInt(instanceParam); 110 } 111 112 LOGGER.log(Level.FINE, "LdapUser plugin loaded with filter={0}, " + 113 "attributes={1}, useDN={2}, instance={3}", 114 new Object[]{ldapFilter, attrSet, useDN, instanceNum}); 115 } 116 117 /** 118 * Check if the session exists and contains all necessary fields required by 119 * this plug-in. 120 * 121 * @param req the HTTP request 122 * @return true if it does; false otherwise 123 */ 124 @Override sessionExists(HttpServletRequest req)125 protected boolean sessionExists(HttpServletRequest req) { 126 return super.sessionExists(req) && req.getSession().getAttribute(getSessionAttrName()) != null; 127 } 128 129 /** 130 * Expand {@code User} object attribute values into the filter. 131 * 132 * @see opengrok.auth.plugin.util.FilterUtil 133 * 134 * Use \% for printing the '%' character. 135 * 136 * @param user User object from the request (created by {@code UserPlugin}) 137 * @return replaced result 138 */ expandFilter(User user)139 String expandFilter(User user) { 140 String filter = ldapFilter; 141 filter = expandUserFilter(user, filter); 142 filter = filter.replace("\\%", "%"); 143 144 return filter; 145 } 146 147 @Override fillSession(HttpServletRequest req, User user)148 public void fillSession(HttpServletRequest req, User user) { 149 Map<String, Set<String>> records; 150 151 updateSession(req, null); 152 153 if (getLdapProvider() == null) { 154 LOGGER.log(Level.WARNING, "cannot get LDAP provider"); 155 return; 156 } 157 158 String dn = null; 159 if (Boolean.TRUE.equals(useDN)) { 160 dn = user.getUsername(); 161 LOGGER.log(Level.FINEST, "using DN ''{0}'' for user {1}", new Object[]{dn, user}); 162 } 163 164 String expandedFilter = null; 165 if (ldapFilter != null) { 166 expandedFilter = expandFilter(user); 167 LOGGER.log(Level.FINEST, "expanded filter for user {0} into ''{1}''", 168 new Object[]{user, expandedFilter}); 169 } 170 171 AbstractLdapProvider ldapProvider = getLdapProvider(); 172 try { 173 AbstractLdapProvider.LdapSearchResult<Map<String, Set<String>>> res; 174 if ((res = ldapProvider.lookupLdapContent(dn, expandedFilter, attrSet.toArray(new String[0]))) == null) { 175 LOGGER.log(Level.WARNING, "failed to get LDAP attributes ''{2}'' for user {0} " + 176 "with filter ''{1}'' from LDAP provider {3}", 177 new Object[]{user, expandedFilter, attrSet, getLdapProvider()}); 178 LdapUser ldapUser = new LdapUser(dn, null); 179 ldapUser.setAttribute(NEGATIVE_CACHE_ATTR, Collections.singleton(null)); 180 updateSession(req, ldapUser); 181 return; 182 } 183 184 records = res.getAttrs(); 185 if (Boolean.FALSE.equals(useDN)) { 186 dn = res.getDN(); 187 LOGGER.log(Level.FINEST, "got DN ''{0}'' for user {1}", new Object[]{dn, user}); 188 } 189 } catch (LdapException ex) { 190 throw new AuthorizationException(ex); 191 } 192 193 if (records.isEmpty()) { 194 LOGGER.log(Level.WARNING, "LDAP records for user {0} are empty on {1}", 195 new Object[]{user, ldapProvider}); 196 return; 197 } 198 199 for (String attrName : attrSet) { 200 if (!records.containsKey(attrName) || records.get(attrName) == null || records.get(attrName).isEmpty()) { 201 LOGGER.log(Level.WARNING, "''{0}'' record for user {1} is not present or empty on {2}", 202 new Object[]{attrName, user, ldapProvider}); 203 } 204 } 205 206 Map<String, Set<String>> userAttrSet = new HashMap<>(); 207 for (String attrName : this.attrSet) { 208 userAttrSet.put(attrName, records.get(attrName)); 209 } 210 211 LOGGER.log(Level.FINEST, "DN for user {0} is ''{1}'' on {2}", new Object[]{user, dn, ldapProvider}); 212 updateSession(req, new LdapUser(dn, userAttrSet)); 213 } 214 215 /** 216 * Add a new user value into the session. 217 * 218 * @param req the request 219 * @param user the new value for user 220 */ updateSession(@otNull HttpServletRequest req, LdapUser user)221 void updateSession(@NotNull HttpServletRequest req, LdapUser user) { 222 req.getSession().setAttribute(getSessionAttrName(), user); 223 } 224 getSessionAttrName(Integer instance)225 static String getSessionAttrName(Integer instance) { 226 return (SESSION_ATTR + (instance != null ? instance.toString() : "")); 227 } 228 getSessionAttrName()229 private String getSessionAttrName() { 230 return getSessionAttrName(instanceNum); 231 } 232 checkUser(@otNull HttpServletRequest request)233 private boolean checkUser(@NotNull HttpServletRequest request) { 234 LdapUser ldapUser = (LdapUser) request.getSession().getAttribute(getSessionAttrName()); 235 return ldapUser != null && ldapUser.getAttribute(NEGATIVE_CACHE_ATTR) == null; 236 } 237 238 @Override checkEntity(HttpServletRequest request, Project project)239 public boolean checkEntity(HttpServletRequest request, Project project) { 240 return checkUser(request); 241 } 242 243 @Override checkEntity(HttpServletRequest request, Group group)244 public boolean checkEntity(HttpServletRequest request, Group group) { 245 return checkUser(request); 246 } 247 } 248