xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationPlugin.java (revision c6f0939b1c668e9f8e1e276424439c3106b3a029)
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