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) 2017, 2021, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2018, Chris Fraire <cfraire@me.com>. 23 */ 24 package org.opengrok.indexer.authorization; 25 26 import java.util.Locale; 27 import java.util.Map; 28 import java.util.TreeMap; 29 import java.util.logging.Level; 30 import java.util.logging.Logger; 31 import org.opengrok.indexer.configuration.Group; 32 import org.opengrok.indexer.configuration.Nameable; 33 import org.opengrok.indexer.configuration.Project; 34 import org.opengrok.indexer.logger.LoggerFactory; 35 36 /** 37 * This is a subclass of {@link AuthorizationEntity} and is a wrapper to a 38 * {@link IAuthorizationPlugin} delegating the decision methods to the contained 39 * plugin. 40 * 41 * @author Krystof Tulinger 42 */ 43 public class AuthorizationPlugin extends AuthorizationStack { 44 45 private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationPlugin.class); 46 private static final long serialVersionUID = 2L; 47 48 private transient IAuthorizationPlugin plugin; 49 AuthorizationPlugin()50 public AuthorizationPlugin() { 51 } 52 53 /** 54 * Clones the plugin. Performs: 55 * <ul> 56 * <li>copy the superclass {@link AuthorizationStack}</li> 57 * <li>sets the plugin to {@code null}</li> 58 * </ul> 59 * 60 * @param x the plugin to be copied 61 */ AuthorizationPlugin(AuthorizationPlugin x)62 public AuthorizationPlugin(AuthorizationPlugin x) { 63 super(x); 64 plugin = null; 65 } 66 AuthorizationPlugin(AuthControlFlag flag, IAuthorizationPlugin plugin)67 public AuthorizationPlugin(AuthControlFlag flag, IAuthorizationPlugin plugin) { 68 this(flag, plugin.getClass().getCanonicalName() == null ? plugin.getClass().getName() : plugin.getClass().getCanonicalName(), plugin); 69 } 70 AuthorizationPlugin(AuthControlFlag flag, String name)71 public AuthorizationPlugin(AuthControlFlag flag, String name) { 72 this(flag, name, null); 73 } 74 AuthorizationPlugin(AuthControlFlag flag, String name, IAuthorizationPlugin plugin)75 public AuthorizationPlugin(AuthControlFlag flag, String name, IAuthorizationPlugin plugin) { 76 super(flag, name); 77 this.plugin = plugin; 78 } 79 80 /** 81 * Call the load method on the underlying plugin if the plugin exists. Note 82 * that the load method can throw any throwable from its body and it should 83 * not stop the application. 84 * <p> 85 * <p>If the method is unable to load the plugin because of any reason (mostly 86 * the class is not found, not instantiable or the load method throws an 87 * exception) then any authorization check should fail for this plugin in 88 * the future. 89 * 90 * @param parameters parameters given in the configuration 91 * 92 * @see IAuthorizationPlugin#load(java.util.Map) 93 */ 94 @Override load(Map<String, Object> parameters)95 public synchronized void load(Map<String, Object> parameters) { 96 // fill properly the "forGroups" and "forProjects" fields 97 processTargetGroupsAndProjects(); 98 99 if (!hasPlugin()) { 100 LOGGER.log(Level.SEVERE, "Configured plugin \"{0}\" has not been loaded into JVM (missing file?). " 101 + "This can cause the authorization to fail always.", 102 getName()); 103 setFailed(); 104 LOGGER.log(Level.INFO, "[{0}] Plugin \"{1}\" {2} and is {3}.", 105 new Object[]{ 106 getFlag().toString().toUpperCase(Locale.ROOT), 107 getName(), 108 hasPlugin() ? "found" : "not found", 109 isWorking() ? "working" : "failed"}); 110 return; 111 } 112 113 setCurrentSetup(new TreeMap<>()); 114 getCurrentSetup().putAll(parameters); 115 getCurrentSetup().putAll(getSetup()); 116 117 try { 118 plugin.load(getCurrentSetup()); 119 setWorking(); 120 } catch (Throwable ex) { 121 LOGGER.log(Level.SEVERE, "Plugin \"" + getName() + "\" has failed while loading with exception:", ex); 122 setFailed(); 123 } 124 125 LOGGER.log(Level.INFO, "[{0}] Plugin \"{1}\" {2} and is {3}.", 126 new Object[]{ 127 getFlag().toString().toUpperCase(Locale.ROOT), 128 getName(), 129 hasPlugin() ? "found" : "not found", 130 isWorking() ? "working" : "failed"}); 131 } 132 133 /** 134 * Call the unload method on the underlying plugin if the plugin exists. 135 * Note that the unload method can throw any throwable from its body and it 136 * should not stop the application. 137 * 138 * @see IAuthorizationPlugin#unload() 139 */ 140 @Override unload()141 public synchronized void unload() { 142 if (hasPlugin()) { 143 try { 144 plugin.unload(); 145 plugin = null; 146 } catch (Throwable ex) { 147 LOGGER.log(Level.SEVERE, "Plugin \"" + getName() + "\" has failed while unloading with exception:", ex); 148 } 149 } 150 } 151 152 /** 153 * Test the underlying plugin with the predicate if and only if the plugin 154 * is not marked as failed. 155 * 156 * @param entity the given entity - this is either group or project and is 157 * passed just for the logging purposes. 158 * @param pluginPredicate predicate returning true or false for the given 159 * entity which determines if the authorization for such entity is 160 * successful or failed for particular request and plugin 161 * @param skippingPredicate predicate returning true if this authorization 162 * entity should be omitted from the authorization process 163 * @return true if the plugin is not failed and the project is allowed; 164 * false otherwise 165 * 166 * @see #isFailed() 167 * @see IAuthorizationPlugin#isAllowed(jakarta.servlet.http.HttpServletRequest, Project) 168 * @see IAuthorizationPlugin#isAllowed(jakarta.servlet.http.HttpServletRequest, Group) 169 */ 170 @Override isAllowed(Nameable entity, AuthorizationEntity.PluginDecisionPredicate pluginPredicate, AuthorizationEntity.PluginSkippingPredicate skippingPredicate)171 public boolean isAllowed(Nameable entity, 172 AuthorizationEntity.PluginDecisionPredicate pluginPredicate, 173 AuthorizationEntity.PluginSkippingPredicate skippingPredicate) { 174 /** 175 * We don't check the skippingPredicate here as this instance is 176 * <b>always</b> a part of some stack (may be the default stack) and the 177 * stack checks the skipping predicate before invoking this method. 178 * 179 * @see AuthorizationStack#processStack 180 */ 181 182 if (isFailed()) { 183 return false; 184 } 185 186 return pluginPredicate.decision(this.plugin); 187 } 188 189 /** 190 * Set the plugin to this entity if this entity requires this plugin class 191 * in the configuration. This creates a new instance of the plugin for each 192 * class which needs it. 193 * 194 * @param plugin the new instance of a plugin 195 * @return true if there is the class names are equal and the plugin is not 196 * null; false otherwise 197 */ 198 @Override setPlugin(IAuthorizationPlugin plugin)199 public boolean setPlugin(IAuthorizationPlugin plugin) { 200 if (!getName().equals(plugin.getClass().getCanonicalName()) 201 || !getName().equals(plugin.getClass().getName())) { 202 return false; 203 } 204 if (hasPlugin()) { 205 unload(); 206 } 207 try { 208 /** 209 * The exception should not happen here as we already have an 210 * instance of IAuthorizationPlugin. But it is required by the 211 * compiler. 212 * 213 * NOTE: If we were to add a throws clause here we would interrupt 214 * the whole stack walk through and prevent the other authorization 215 * entities to work properly. 216 */ 217 this.plugin = plugin.getClass().getDeclaredConstructor().newInstance(); 218 return true; 219 } catch (InstantiationException ex) { 220 LOGGER.log(Level.INFO, "Class could not be instantiated: ", ex); 221 } catch (IllegalAccessException ex) { 222 LOGGER.log(Level.INFO, "Class loader threw an exception: ", ex); 223 } catch (Throwable ex) { 224 LOGGER.log(Level.INFO, "Class loader threw an unknown error: ", ex); 225 } 226 return false; 227 } 228 229 /** 230 * Get the authorization plugin. 231 * 232 * @return the underlying plugin 233 */ getPlugin()234 protected IAuthorizationPlugin getPlugin() { 235 return plugin; 236 } 237 238 /** 239 * Check if the plugin exists and has not failed while loading. 240 * 241 * @return true if working, false otherwise 242 */ 243 @Override isWorking()244 public boolean isWorking() { 245 return working && hasPlugin(); 246 } 247 248 /** 249 * Check if the plugin class was found for this plugin. 250 * 251 * @return true if was; false otherwise 252 */ hasPlugin()253 public boolean hasPlugin() { 254 return plugin != null; 255 } 256 257 /** 258 * Clones the plugin. Performs: 259 * <ul> 260 * <li>copy the superclass {@link AuthorizationStack}</li> 261 * <li>sets the plugin to {@code null}</li> 262 * </ul> 263 * 264 * @return new instance of {@link AuthorizationPlugin} 265 */ 266 @Override clone()267 public AuthorizationPlugin clone() { 268 return new AuthorizationPlugin(this); 269 } 270 271 /** 272 * Print the plugin info. 273 * 274 * @param prefix this prefix should be prepended to every line produced by 275 * this stack 276 * @param colorElement a possible element where any occurrence of %color% 277 * will be replaced with a HTML HEX color representing this entity state. 278 * @return the string containing this stack representation 279 */ 280 @Override hierarchyToString(String prefix, String colorElement)281 public String hierarchyToString(String prefix, String colorElement) { 282 return prefix + colorToString(colorElement) + 283 infoToString(prefix) + 284 " (class " + (isWorking() ? "loaded" : "missing/failed") + ")" + 285 "\n" + 286 setupToString(prefix) + 287 targetsToString(prefix); 288 } 289 } 290