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.io.IOException; 26 import java.nio.file.Files; 27 import java.nio.file.Paths; 28 import java.util.Map; 29 import java.util.Set; 30 import java.util.TreeSet; 31 import java.util.logging.Level; 32 import java.util.logging.Logger; 33 import java.util.stream.Stream; 34 35 import jakarta.servlet.http.HttpServletRequest; 36 import opengrok.auth.entity.LdapUser; 37 import opengrok.auth.plugin.entity.User; 38 import opengrok.auth.plugin.ldap.AbstractLdapProvider; 39 import opengrok.auth.plugin.ldap.LdapException; 40 import org.opengrok.indexer.authorization.AuthorizationException; 41 import org.opengrok.indexer.configuration.Group; 42 import org.opengrok.indexer.configuration.Project; 43 44 /** 45 * Authorization plug-in to check user's LDAP attribute against whitelist. 46 * 47 * This plugin heavily relies on the presence of the {@code LdapUserPlugin} in the stack above it, 48 * since it is using the Distinguished Name of the {@code LdapUser} to perform the LDAP lookup. 49 * 50 * @author Krystof Tulinger 51 */ 52 public class LdapAttrPlugin extends AbstractLdapPlugin { 53 54 private static final Logger LOGGER = Logger.getLogger(LdapAttrPlugin.class.getName()); 55 56 /** 57 * List of configuration names. 58 * <ul> 59 * <li><code>attribute</code> is LDAP attribute to check (mandatory)</li> 60 * <li><code>file</code> whitelist file (mandatory)</li> 61 * <li><code>instance</code> is number of <code>LdapUserInstance</code> plugin to use (optional)</li> 62 * </ul> 63 */ 64 static final String ATTR_PARAM = "attribute"; 65 static final String FILE_PARAM = "file"; 66 static final String INSTANCE_PARAM = "instance"; 67 68 private static final String SESSION_ALLOWED_PREFIX = "opengrok-ldap-attr-plugin-allowed"; 69 private String sessionAllowed = SESSION_ALLOWED_PREFIX; 70 71 private String ldapAttr; 72 private final Set<String> whitelist = new TreeSet<>(); 73 private Integer ldapUserInstance; 74 private String filePath; 75 LdapAttrPlugin()76 public LdapAttrPlugin() { 77 sessionAllowed += "-" + nextId++; 78 } 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 if ((ldapAttr = (String) parameters.get(ATTR_PARAM)) == null) { 96 throw new NullPointerException("Missing param [" + ATTR_PARAM + "] in the setup"); 97 } 98 99 if ((filePath = (String) parameters.get(FILE_PARAM)) == null) { 100 throw new NullPointerException("Missing param [" + FILE_PARAM + "] in the setup"); 101 } 102 103 String instance = (String) parameters.get(INSTANCE_PARAM); 104 if (instance != null) { 105 ldapUserInstance = Integer.parseInt(instance); 106 } 107 108 try (Stream<String> stream = Files.lines(Paths.get(filePath))) { 109 stream.forEach(whitelist::add); 110 } catch (IOException e) { 111 throw new IllegalArgumentException(String.format("Unable to read the file \"%s\"", filePath), e); 112 } 113 114 LOGGER.log(Level.FINE, "LdapAttrPlugin plugin loaded with attr={0}, whitelist={1} ({2} entries), " + 115 "instance={3}", new Object[]{ldapAttr, filePath, whitelist.size(), ldapUserInstance}); 116 } 117 118 @Override sessionExists(HttpServletRequest req)119 protected boolean sessionExists(HttpServletRequest req) { 120 return super.sessionExists(req) 121 && req.getSession().getAttribute(sessionAllowed) != null; 122 } 123 getSessionAllowedAttrName()124 String getSessionAllowedAttrName() { 125 return sessionAllowed; 126 } 127 128 @Override fillSession(HttpServletRequest req, User user)129 public void fillSession(HttpServletRequest req, User user) { 130 updateSession(req, false); 131 132 LdapUser ldapUser = (LdapUser) req.getSession().getAttribute(LdapUserPlugin.getSessionAttrName(ldapUserInstance)); 133 if (ldapUser == null) { 134 LOGGER.log(Level.WARNING, "cannot get {0} attribute from {1}", 135 new Object[]{LdapUserPlugin.SESSION_ATTR, user}); 136 return; 137 } 138 139 // Check attributes cached in LDAP user object first, then query LDAP server 140 // (and if found, cache the result in the LDAP user object). 141 Set<String> attributeValues = ldapUser.getAttribute(ldapAttr); 142 if (attributeValues == null) { 143 Map<String, Set<String>> records = null; 144 AbstractLdapProvider ldapProvider = getLdapProvider(); 145 try { 146 String dn = ldapUser.getDn(); 147 if (dn != null) { 148 LOGGER.log(Level.FINEST, "searching with dn={0} on {1}", 149 new Object[]{dn, ldapProvider}); 150 AbstractLdapProvider.LdapSearchResult<Map<String, Set<String>>> res; 151 if ((res = ldapProvider.lookupLdapContent(dn, new String[]{ldapAttr})) == null) { 152 LOGGER.log(Level.WARNING, "cannot lookup attributes {0} for user {1} on {2})", 153 new Object[]{ldapAttr, ldapUser, ldapProvider}); 154 return; 155 } 156 157 records = res.getAttrs(); 158 } else { 159 LOGGER.log(Level.FINE, "no DN for LDAP user {0} on {1}", 160 new Object[]{ldapUser, ldapProvider}); 161 } 162 } catch (LdapException ex) { 163 throw new AuthorizationException(ex); 164 } 165 166 if (records == null || records.isEmpty() || (attributeValues = records.get(ldapAttr)) == null) { 167 LOGGER.log(Level.WARNING, "empty records or attribute values {0} for user {1} on {2}", 168 new Object[]{ldapAttr, ldapUser, ldapProvider}); 169 return; 170 } 171 172 ldapUser.setAttribute(ldapAttr, attributeValues); 173 } 174 175 boolean isAttrInWhitelist = attributeValues.stream().anyMatch(whitelist::contains); 176 LOGGER.log(Level.FINEST, "LDAP user {0} {1} against {2}", 177 new Object[]{ldapUser, isAttrInWhitelist ? "allowed" : "denied", filePath}); 178 updateSession(req, isAttrInWhitelist); 179 } 180 181 /** 182 * Add a new allowed value into the session. 183 * 184 * @param req the request 185 * @param allowed the new value 186 */ updateSession(HttpServletRequest req, boolean allowed)187 private void updateSession(HttpServletRequest req, boolean allowed) { 188 req.getSession().setAttribute(sessionAllowed, allowed); 189 } 190 191 @Override checkEntity(HttpServletRequest request, Project project)192 public boolean checkEntity(HttpServletRequest request, Project project) { 193 return ((Boolean) request.getSession().getAttribute(sessionAllowed)); 194 } 195 196 @Override checkEntity(HttpServletRequest request, Group group)197 public boolean checkEntity(HttpServletRequest request, Group group) { 198 return ((Boolean) request.getSession().getAttribute(sessionAllowed)); 199 } 200 } 201