mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 17:13:21 +00:00 
			
		
		
		
	GUACAMOLE-364: Merge changes restoring extension support for event listeners.
This commit is contained in:
		| @@ -19,7 +19,6 @@ | ||||
|  | ||||
| package org.apache.guacamole.extension; | ||||
|  | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| import java.util.UUID; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.net.auth.AuthenticatedUser; | ||||
| @@ -66,58 +65,8 @@ public class AuthenticationProviderFacade implements AuthenticationProvider { | ||||
|      *     The AuthenticationProvider subclass to instantiate. | ||||
|      */ | ||||
|     public AuthenticationProviderFacade(Class<? extends AuthenticationProvider> authProviderClass) { | ||||
|  | ||||
|         AuthenticationProvider instance = null; | ||||
|          | ||||
|         try { | ||||
|             // Attempt to instantiate the authentication provider | ||||
|             instance = authProviderClass.getConstructor().newInstance(); | ||||
|         } | ||||
|         catch (NoSuchMethodException e) { | ||||
|             logger.error("The authentication extension in use is not properly defined. " | ||||
|                        + "Please contact the developers of the extension or, if you " | ||||
|                        + "are the developer, turn on debug-level logging."); | ||||
|             logger.debug("AuthenticationProvider is missing a default constructor.", e); | ||||
|         } | ||||
|         catch (SecurityException e) { | ||||
|             logger.error("The Java security mananager is preventing authentication extensions " | ||||
|                        + "from being loaded. Please check the configuration of Java or your " | ||||
|                        + "servlet container."); | ||||
|             logger.debug("Creation of AuthenticationProvider disallowed by security manager.", e); | ||||
|         } | ||||
|         catch (InstantiationException e) { | ||||
|             logger.error("The authentication extension in use is not properly defined. " | ||||
|                        + "Please contact the developers of the extension or, if you " | ||||
|                        + "are the developer, turn on debug-level logging."); | ||||
|             logger.debug("AuthenticationProvider cannot be instantiated.", e); | ||||
|         } | ||||
|         catch (IllegalAccessException e) { | ||||
|             logger.error("The authentication extension in use is not properly defined. " | ||||
|                        + "Please contact the developers of the extension or, if you " | ||||
|                        + "are the developer, turn on debug-level logging."); | ||||
|             logger.debug("Default constructor of AuthenticationProvider is not public.", e); | ||||
|         } | ||||
|         catch (IllegalArgumentException e) { | ||||
|             logger.error("The authentication extension in use is not properly defined. " | ||||
|                        + "Please contact the developers of the extension or, if you " | ||||
|                        + "are the developer, turn on debug-level logging."); | ||||
|             logger.debug("Default constructor of AuthenticationProvider cannot accept zero arguments.", e); | ||||
|         }  | ||||
|         catch (InvocationTargetException e) { | ||||
|  | ||||
|             // Obtain causing error - create relatively-informative stub error if cause is unknown | ||||
|             Throwable cause = e.getCause(); | ||||
|             if (cause == null) | ||||
|                 cause = new GuacamoleException("Error encountered during initialization."); | ||||
|              | ||||
|             logger.error("Authentication extension failed to start: {}", cause.getMessage()); | ||||
|             logger.debug("AuthenticationProvider instantiation failed.", e); | ||||
|  | ||||
|         } | ||||
|         | ||||
|         // Associate instance, if any | ||||
|         authProvider = instance; | ||||
|  | ||||
|         authProvider = ProviderFactory.newInstance("authentication provider", | ||||
|             authProviderClass); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -35,6 +35,7 @@ import java.util.Map; | ||||
| import java.util.zip.ZipEntry; | ||||
| import java.util.zip.ZipException; | ||||
| import java.util.zip.ZipFile; | ||||
| import org.apache.guacamole.net.event.listener.Listener; | ||||
| import org.codehaus.jackson.JsonParseException; | ||||
| import org.codehaus.jackson.map.ObjectMapper; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| @@ -109,6 +110,11 @@ public class Extension { | ||||
|      */ | ||||
|     private final Collection<Class<AuthenticationProvider>> authenticationProviderClasses; | ||||
|  | ||||
|     /** | ||||
|      * The collection of all Listener classes defined within the extension. | ||||
|      */ | ||||
|     private final Collection<Class<?>> listenerClasses; | ||||
|  | ||||
|     /** | ||||
|      * The resource for the small favicon for the extension. If provided, this | ||||
|      * will replace the default Guacamole icon. | ||||
| @@ -265,6 +271,80 @@ public class Extension { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Retrieve the Listener subclass having the given name. If | ||||
|      * the class having the given name does not exist or isn't actually a | ||||
|      * subclass of Listener, an exception will be thrown. | ||||
|      * | ||||
|      * @param name | ||||
|      *     The name of the Listener class to retrieve. | ||||
|      * | ||||
|      * @return | ||||
|      *     The subclass of Listener having the given name. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If no such class exists, or if the class with the given name is not | ||||
|      *     a subclass of Listener. | ||||
|      */ | ||||
|     @SuppressWarnings("unchecked") // We check this ourselves with isAssignableFrom() | ||||
|     private Class<Listener> getListenerClass(String name) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         try { | ||||
|  | ||||
|             // Get listener class | ||||
|             Class<?> listenerClass = classLoader.loadClass(name); | ||||
|  | ||||
|             // Verify the located class is actually a subclass of Listener | ||||
|             if (!Listener.class.isAssignableFrom(listenerClass)) | ||||
|                 throw new GuacamoleServerException("Listeners MUST implement a Listener subclass."); | ||||
|  | ||||
|             // Return located class | ||||
|             return (Class<Listener>) listenerClass; | ||||
|  | ||||
|         } | ||||
|         catch (ClassNotFoundException e) { | ||||
|             throw new GuacamoleException("Listener class not found.", e); | ||||
|         } | ||||
|         catch (LinkageError e) { | ||||
|             throw new GuacamoleException("Listener class cannot be loaded (wrong version of API?).", e); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns a new collection of all Listener subclasses having the given names. | ||||
|      * If any class does not exist or isn't actually subclass of Listener, an | ||||
|      * exception will be thrown, an no further Listener classes will be loaded. | ||||
|      * | ||||
|      * @param names | ||||
|      *     The names of the AuthenticationProvider classes to retrieve. | ||||
|      * | ||||
|      * @return | ||||
|      *     A new collection of all AuthenticationProvider subclasses having the | ||||
|      *     given names. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If any given class does not exist, or if any given class is not a | ||||
|      *     subclass of AuthenticationProvider. | ||||
|      */ | ||||
|     private Collection<Class<?>> getListenerClasses(Collection<String> names) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         // If no classnames are provided, just return an empty list | ||||
|         if (names == null) | ||||
|             return Collections.<Class<?>>emptyList(); | ||||
|  | ||||
|         // Define all auth provider classes | ||||
|         Collection<Class<?>> classes = new ArrayList<Class<?>>(names.size()); | ||||
|         for (String name : names) | ||||
|             classes.add(getListenerClass(name)); | ||||
|  | ||||
|         // Callers should not rely on modifying the result | ||||
|         return Collections.unmodifiableCollection(classes); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Loads the given file as an extension, which must be a .jar containing | ||||
|      * a guac-manifest.json file describing its contents. | ||||
| @@ -363,6 +443,9 @@ public class Extension { | ||||
|         // Define authentication providers | ||||
|         authenticationProviderClasses = getAuthenticationProviderClasses(manifest.getAuthProviders()); | ||||
|  | ||||
|         // Define listeners | ||||
|         listenerClasses = getListenerClasses(manifest.getListeners()); | ||||
|  | ||||
|         // Get small icon resource if provided | ||||
|         if (manifest.getSmallIcon() != null) | ||||
|             smallIcon = new ClassPathResource(classLoader, "image/png", manifest.getSmallIcon()); | ||||
| @@ -488,6 +571,17 @@ public class Extension { | ||||
|         return authenticationProviderClasses; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns all declared listener classes associated wit this extension. Listeners are | ||||
|      * declared within the extension manifest. | ||||
|      * | ||||
|      * @return | ||||
|      *     All declared listener classes with this extension. | ||||
|      */ | ||||
|     public Collection<Class<?>> getListenerClasses() { | ||||
|         return listenerClasses; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the resource for the small favicon for the extension. If | ||||
|      * provided, this will replace the default Guacamole icon. | ||||
|   | ||||
| @@ -87,6 +87,11 @@ public class ExtensionManifest { | ||||
|      */ | ||||
|     private Collection<String> authProviders; | ||||
|  | ||||
|     /** | ||||
|      * The names of all listener classes within this extension, if any. | ||||
|      */ | ||||
|     private Collection<String> listeners; | ||||
|  | ||||
|     /** | ||||
|      * The path to the small favicon. If provided, this will replace the default | ||||
|      * Guacamole icon. | ||||
| @@ -355,6 +360,32 @@ public class ExtensionManifest { | ||||
|         this.authProviders = authProviders; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the classnames of all listener classes within the extension. | ||||
|      * These classnames are defined within the manifest by the "listeners" | ||||
|      * property as an array of strings, where each string is a listener | ||||
|      * class name. | ||||
|      * | ||||
|      * @return | ||||
|      *      A collection of classnames for all listeners within the extension. | ||||
|      */ | ||||
|     public Collection<String> getListeners() { | ||||
|         return listeners; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sets the classnames of all listener classes within the extension. | ||||
|      * These classnames are defined within the manifest by the "listeners" | ||||
|      * property as an array of strings, where each string is a listener | ||||
|      * class name. | ||||
|      * | ||||
|      * @param listeners | ||||
|      *      A collection of classnames for all listeners within the extension. | ||||
|      */ | ||||
|     public void setListeners(Collection<String> listeners) { | ||||
|         this.listeners = listeners; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the path to the small favicon, relative to the root of the | ||||
|      * extension. | ||||
|   | ||||
| @@ -34,6 +34,7 @@ import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
| import org.apache.guacamole.environment.Environment; | ||||
| import org.apache.guacamole.net.auth.AuthenticationProvider; | ||||
| import org.apache.guacamole.net.event.listener.Listener; | ||||
| import org.apache.guacamole.resource.Resource; | ||||
| import org.apache.guacamole.resource.ResourceServlet; | ||||
| import org.apache.guacamole.resource.SequenceResource; | ||||
| @@ -91,6 +92,12 @@ public class ExtensionModule extends ServletModule { | ||||
|     private final List<AuthenticationProvider> boundAuthenticationProviders = | ||||
|             new ArrayList<AuthenticationProvider>(); | ||||
|  | ||||
|     /** | ||||
|      * All currently-bound authentication providers, if any. | ||||
|      */ | ||||
|     private final List<Listener> boundListeners = | ||||
|             new ArrayList<Listener>(); | ||||
|  | ||||
|     /** | ||||
|      * Service for adding and retrieving language resources. | ||||
|      */ | ||||
| @@ -187,6 +194,49 @@ public class ExtensionModule extends ServletModule { | ||||
|         return Collections.unmodifiableList(boundAuthenticationProviders); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Binds the given provider class such that a listener is bound for each | ||||
|      * listener interface implemented by the provider and such that all bound | ||||
|      * listener instances can be obtained via injection. | ||||
|      * | ||||
|      * @param providerClass | ||||
|      *     The listener class to bind. | ||||
|      */ | ||||
|     private void bindListener(Class<?> providerClass) { | ||||
|  | ||||
|         logger.debug("[{}] Binding listener \"{}\".", | ||||
|                 boundListeners.size(), providerClass.getName()); | ||||
|         boundListeners.addAll(ListenerFactory.createListeners(providerClass)); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Binds each of the the given Listener classes such that any | ||||
|      * service requiring access to the Listener can obtain it via | ||||
|      * injection. | ||||
|      * | ||||
|      * @param listeners | ||||
|      *     The Listener classes to bind. | ||||
|      */ | ||||
|     private void bindListeners(Collection<Class<?>> listeners) { | ||||
|  | ||||
|         // Bind each listener within extension | ||||
|         for (Class<?> listener : listeners) | ||||
|             bindListener(listener); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns a list of all currently-bound Listener instances. | ||||
|      * | ||||
|      * @return | ||||
|      *     A List of all currently-bound Listener instances. The List is | ||||
|      *     not modifiable. | ||||
|      */ | ||||
|     @Provides | ||||
|     public List<Listener> getListeners() { | ||||
|         return Collections.unmodifiableList(boundListeners); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Serves each of the given resources as a language resource. Language | ||||
|      * resources are served from within the "/translations" directory as JSON | ||||
| @@ -327,6 +377,9 @@ public class ExtensionModule extends ServletModule { | ||||
|                 // Attempt to load all authentication providers | ||||
|                 bindAuthenticationProviders(extension.getAuthenticationProviderClasses()); | ||||
|  | ||||
|                 // Attempt to load all listeners | ||||
|                 bindListeners(extension.getListenerClasses()); | ||||
|  | ||||
|                 // Add any translation resources | ||||
|                 serveLanguageResources(extension.getTranslationResources()); | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,278 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  *  Unless required by applicable law or agreed to in writing, | ||||
|  *  software distributed under the License is distributed on an | ||||
|  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  *  KIND, either express or implied.  See the License for the | ||||
|  *  specific language governing permissions and limitations | ||||
|  *  under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.extension; | ||||
|  | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleSecurityException; | ||||
| import org.apache.guacamole.net.event.AuthenticationFailureEvent; | ||||
| import org.apache.guacamole.net.event.AuthenticationSuccessEvent; | ||||
| import org.apache.guacamole.net.event.TunnelCloseEvent; | ||||
| import org.apache.guacamole.net.event.TunnelConnectEvent; | ||||
| import org.apache.guacamole.net.event.listener.*; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * A factory that reflectively instantiates Listener objects for a given | ||||
|  * provider class. | ||||
|  */ | ||||
| class ListenerFactory { | ||||
|  | ||||
|     /** | ||||
|      * Creates all listeners represented by an instance of the given provider class. | ||||
|      * <p> | ||||
|      * If a provider class implements the simple Listener interface, that is the | ||||
|      * only listener type that will be returned. Otherwise, a list of Listener | ||||
|      * objects that adapt the legacy listener interfaces will be returned. | ||||
|      * | ||||
|      * @param providerClass | ||||
|      *      A class that represents a listener. | ||||
|      * | ||||
|      * @return | ||||
|      *      The list of listeners represented by the given provider class. | ||||
|      */ | ||||
|     static List<Listener> createListeners(Class<?> providerClass) { | ||||
|  | ||||
|         Object provider = ProviderFactory.newInstance("listener", providerClass); | ||||
|  | ||||
|         if (provider instanceof Listener) { | ||||
|             return Collections.singletonList((Listener) provider); | ||||
|         } | ||||
|  | ||||
|         return createListenerAdapters(provider); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a list of adapters for the given object, based on the legacy | ||||
|      * listener interfaces it implements. | ||||
|      * | ||||
|      * @param provider | ||||
|      *      An object that implements zero or more legacy listener interfaces. | ||||
|      * | ||||
|      * @return | ||||
|      *      The list of listeners represented by the given provider class. | ||||
|      */ | ||||
|     @SuppressWarnings("deprecation") | ||||
|     private static List<Listener> createListenerAdapters(Object provider) { | ||||
|  | ||||
|         final List<Listener> listeners = new ArrayList<Listener>(); | ||||
|  | ||||
|         if (provider instanceof AuthenticationSuccessListener) { | ||||
|             listeners.add(new AuthenticationSuccessListenerAdapter( | ||||
|                     (AuthenticationSuccessListener) provider)); | ||||
|         } | ||||
|  | ||||
|         if (provider instanceof AuthenticationFailureListener) { | ||||
|             listeners.add(new AuthenticationFailureListenerAdapter( | ||||
|                     (AuthenticationFailureListener) provider)); | ||||
|         } | ||||
|  | ||||
|         if (provider instanceof TunnelConnectListener) { | ||||
|             listeners.add(new TunnelConnectListenerAdapter( | ||||
|                     (TunnelConnectListener) provider)); | ||||
|         } | ||||
|  | ||||
|         if (provider instanceof TunnelCloseListener) { | ||||
|             listeners.add(new TunnelCloseListenerAdapter( | ||||
|                     (TunnelCloseListener) provider)); | ||||
|         } | ||||
|  | ||||
|         return listeners; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * An adapter the allows an AuthenticationSuccessListener to be used | ||||
|      * as an ordinary Listener. | ||||
|      */ | ||||
|     @SuppressWarnings("deprecation") | ||||
|     private static class AuthenticationSuccessListenerAdapter implements Listener { | ||||
|  | ||||
|         /** | ||||
|          * The delegate listener for this adapter. | ||||
|          */ | ||||
|         private final AuthenticationSuccessListener delegate; | ||||
|  | ||||
|         /** | ||||
|          * Constructs a new adapter that delivers events to the given delegate. | ||||
|          * | ||||
|          * @param delegate | ||||
|          *      The delegate listener. | ||||
|          */ | ||||
|         AuthenticationSuccessListenerAdapter(AuthenticationSuccessListener delegate) { | ||||
|             this.delegate = delegate; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Handles an AuthenticationSuccessEvent by passing the event to the delegate | ||||
|          * listener. If the delegate returns false, the adapter throws a GuacamoleException | ||||
|          * to veto the authentication success event. All other event types are ignored. | ||||
|          * | ||||
|          * @param event | ||||
|          *     An object that describes the event that occurred. | ||||
|          * | ||||
|          * @throws GuacamoleException | ||||
|          *      If thrown by the delegate listener. | ||||
|          */ | ||||
|         @Override | ||||
|         public void handleEvent(Object event) throws GuacamoleException { | ||||
|             if (event instanceof AuthenticationSuccessEvent) { | ||||
|                 if (!delegate.authenticationSucceeded((AuthenticationSuccessEvent) event)) { | ||||
|                     throw new GuacamoleSecurityException( | ||||
|                         "listener vetoed successful authentication"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * An adapter the allows an AuthenticationFailureListener to be used | ||||
|      * as an ordinary Listener. | ||||
|      */ | ||||
|     @SuppressWarnings("deprecation") | ||||
|     private static class AuthenticationFailureListenerAdapter implements Listener { | ||||
|  | ||||
|         /** | ||||
|          * The delegate listener for this adapter. | ||||
|          */ | ||||
|         private final AuthenticationFailureListener delegate; | ||||
|  | ||||
|         /** | ||||
|          * Constructs a new adapter that delivers events to the given delegate. | ||||
|          * | ||||
|          * @param delegate | ||||
|          *      The delegate listener. | ||||
|          */ | ||||
|         AuthenticationFailureListenerAdapter(AuthenticationFailureListener delegate) { | ||||
|             this.delegate = delegate; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Handles an AuthenticationFailureEvent by passing the event to the delegate | ||||
|          * listener. All other event types are ignored. | ||||
|          * | ||||
|          * @param event | ||||
|          *     An object that describes the event that occurred. | ||||
|          * | ||||
|          * @throws GuacamoleException | ||||
|          *      If thrown by the delegate listener. | ||||
|          */ | ||||
|         @Override | ||||
|         public void handleEvent(Object event) throws GuacamoleException { | ||||
|             if (event instanceof AuthenticationFailureEvent) { | ||||
|                 delegate.authenticationFailed((AuthenticationFailureEvent) event); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * An adapter the allows a TunnelConnectListener to be used as an ordinary | ||||
|      * Listener. | ||||
|      */ | ||||
|     @SuppressWarnings("deprecation") | ||||
|     private static class TunnelConnectListenerAdapter implements Listener { | ||||
|  | ||||
|         /** | ||||
|          * The delegate listener for this adapter. | ||||
|          */ | ||||
|         private final TunnelConnectListener delegate; | ||||
|  | ||||
|         /** | ||||
|          * Constructs a new adapter that delivers events to the given delegate. | ||||
|          * | ||||
|          * @param delegate | ||||
|          *      The delegate listener. | ||||
|          */ | ||||
|         TunnelConnectListenerAdapter(TunnelConnectListener delegate) { | ||||
|             this.delegate = delegate; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Handles a TunnelConnectEvent by passing the event to the delegate listener. | ||||
|          * If the delegate returns false, the adapter throws a GuacamoleException | ||||
|          * to veto the tunnel connect event. All other event types are ignored. | ||||
|          * | ||||
|          * @param event | ||||
|          *     An object that describes the event that occurred. | ||||
|          * | ||||
|          * @throws GuacamoleException | ||||
|          *      If thrown by the delegate listener. | ||||
|          */ | ||||
|         @Override | ||||
|         public void handleEvent(Object event) throws GuacamoleException { | ||||
|             if (event instanceof TunnelConnectEvent) { | ||||
|                 if (!delegate.tunnelConnected((TunnelConnectEvent) event)) { | ||||
|                     throw new GuacamoleException("listener vetoed tunnel connection"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * An adapter the allows a TunnelCloseListener to be used as an ordinary | ||||
|      * Listener. | ||||
|      */ | ||||
|     @SuppressWarnings("deprecation") | ||||
|     private static class TunnelCloseListenerAdapter implements Listener { | ||||
|  | ||||
|         /** | ||||
|          * The delegate listener for this adapter. | ||||
|          */ | ||||
|         private final TunnelCloseListener delegate; | ||||
|  | ||||
|         /** | ||||
|          * Constructs a new adapter that delivers events to the given delegate. | ||||
|          * | ||||
|          * @param delegate | ||||
|          *      The delegate listener. | ||||
|          */ | ||||
|         TunnelCloseListenerAdapter(TunnelCloseListener delegate) { | ||||
|             this.delegate = delegate; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Handles a TunnelCloseEvent by passing the event to the delegate listener. | ||||
|          * If the delegate returns false, the adapter throws a GuacamoleException | ||||
|          * to veto the tunnel connect event. All other event types are ignored. | ||||
|          * | ||||
|          * @param event | ||||
|          *     An object that describes the event that occurred. | ||||
|          * | ||||
|          * @throws GuacamoleException | ||||
|          *      If thrown by the delegate listener. | ||||
|          */ | ||||
|         @Override | ||||
|         public void handleEvent(Object event) throws GuacamoleException { | ||||
|             if (event instanceof TunnelCloseEvent) { | ||||
|                 if (!delegate.tunnelClosed((TunnelCloseEvent) event)) { | ||||
|                     throw new GuacamoleException("listener vetoed tunnel close request"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,107 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  *  Unless required by applicable law or agreed to in writing, | ||||
|  *  software distributed under the License is distributed on an | ||||
|  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  *  KIND, either express or implied.  See the License for the | ||||
|  *  specific language governing permissions and limitations | ||||
|  *  under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.extension; | ||||
|  | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
|  | ||||
| /** | ||||
|  * A utility for creating provider instances and logging unexpected outcomes | ||||
|  * with sufficient detail to allow debugging. | ||||
|  */ | ||||
| class ProviderFactory { | ||||
|  | ||||
|     /** | ||||
|      * Logger used to log unexpected outcomes. | ||||
|      */ | ||||
|     private static final Logger logger = LoggerFactory.getLogger(ProviderFactory.class); | ||||
|  | ||||
|     /** | ||||
|      * Creates an instance of the specified provider class using the no-arg constructor. | ||||
|      * | ||||
|      * @param typeName | ||||
|      *      The provider type name used for log messages; e.g. "authentication provider". | ||||
|      * | ||||
|      * @param providerClass | ||||
|      *      The provider class to instantiate. | ||||
|      * | ||||
|      * @param <T> | ||||
|      *      The provider type. | ||||
|      * | ||||
|      * @return | ||||
|      *      A provider instance or null if no instance was created due to error. | ||||
|      */ | ||||
|     static <T> T newInstance(String typeName, Class<? extends T> providerClass) { | ||||
|         T instance = null; | ||||
|  | ||||
|         try { | ||||
|             // Attempt to instantiate the provider | ||||
|             instance = providerClass.getConstructor().newInstance(); | ||||
|         } | ||||
|         catch (NoSuchMethodException e) { | ||||
|             logger.error("The {} extension in use is not properly defined. " | ||||
|                     + "Please contact the developers of the extension or, if you " | ||||
|                     + "are the developer, turn on debug-level logging.", typeName); | ||||
|             logger.debug("{} is missing a default constructor.", | ||||
|                     providerClass.getName(), e); | ||||
|         } | ||||
|         catch (SecurityException e) { | ||||
|             logger.error("The Java security manager is preventing extensions " | ||||
|                     + "from being loaded. Please check the configuration of Java or your " | ||||
|                     + "servlet container."); | ||||
|             logger.debug("Creation of {} disallowed by security manager.", | ||||
|                     providerClass.getName(), e); | ||||
|         } | ||||
|         catch (InstantiationException e) { | ||||
|             logger.error("The {} extension in use is not properly defined. " | ||||
|                     + "Please contact the developers of the extension or, if you " | ||||
|                     + "are the developer, turn on debug-level logging.", typeName); | ||||
|             logger.debug("{} cannot be instantiated.", providerClass.getName(), e); | ||||
|         } | ||||
|         catch (IllegalAccessException e) { | ||||
|             logger.error("The {} extension in use is not properly defined. " | ||||
|                     + "Please contact the developers of the extension or, if you " | ||||
|                     + "are the developer, turn on debug-level logging."); | ||||
|             logger.debug("Default constructor of {} is not public.", typeName, e); | ||||
|         } | ||||
|         catch (IllegalArgumentException e) { | ||||
|             logger.error("The {} extension in use is not properly defined. " | ||||
|                     + "Please contact the developers of the extension or, if you " | ||||
|                     + "are the developer, turn on debug-level logging.", typeName); | ||||
|             logger.debug("Default constructor of {} cannot accept zero arguments.", | ||||
|                     providerClass.getName(), e); | ||||
|         } | ||||
|         catch (InvocationTargetException e) { | ||||
|             // Obtain causing error - create relatively-informative stub error if cause is unknown | ||||
|             Throwable cause = e.getCause(); | ||||
|             if (cause == null) | ||||
|                 cause = new GuacamoleException("Error encountered during initialization."); | ||||
|  | ||||
|             logger.error("{} extension failed to start: {}", typeName, cause.getMessage()); | ||||
|             logger.debug("{} instantiation failed.", providerClass.getName(), e); | ||||
|         } | ||||
|  | ||||
|         return instance; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -19,6 +19,7 @@ | ||||
|  | ||||
| package org.apache.guacamole.rest; | ||||
|  | ||||
| import org.apache.guacamole.rest.event.ListenerService; | ||||
| import org.apache.guacamole.rest.session.UserContextResourceFactory; | ||||
| import org.apache.guacamole.rest.session.SessionRESTService; | ||||
| import com.google.inject.Scopes; | ||||
| @@ -76,6 +77,7 @@ public class RESTServiceModule extends ServletModule { | ||||
|         bind(TokenSessionMap.class).toInstance(tokenSessionMap); | ||||
|  | ||||
|         // Bind low-level services | ||||
|         bind(ListenerService.class); | ||||
|         bind(AuthenticationService.class); | ||||
|         bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class); | ||||
|  | ||||
|   | ||||
| @@ -24,9 +24,11 @@ import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.regex.Pattern; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleSecurityException; | ||||
| import org.apache.guacamole.GuacamoleUnauthorizedException; | ||||
| import org.apache.guacamole.GuacamoleSession; | ||||
| import org.apache.guacamole.environment.Environment; | ||||
| import org.apache.guacamole.net.auth.AuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.AuthenticationProvider; | ||||
| @@ -35,7 +37,9 @@ import org.apache.guacamole.net.auth.UserContext; | ||||
| import org.apache.guacamole.net.auth.credentials.CredentialsInfo; | ||||
| import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException; | ||||
| import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; | ||||
| import org.apache.guacamole.GuacamoleSession; | ||||
| import org.apache.guacamole.net.event.AuthenticationFailureEvent; | ||||
| import org.apache.guacamole.net.event.AuthenticationSuccessEvent; | ||||
| import org.apache.guacamole.rest.event.ListenerService; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| @@ -74,6 +78,12 @@ public class AuthenticationService { | ||||
|     @Inject | ||||
|     private AuthTokenGenerator authTokenGenerator; | ||||
|  | ||||
|     /** | ||||
|      * The service to use to notify registered authentication listeners. | ||||
|      */ | ||||
|     @Inject | ||||
|     private ListenerService listenerService; | ||||
|  | ||||
|     /** | ||||
|      * Regular expression which matches any IPv4 address. | ||||
|      */ | ||||
| @@ -207,6 +217,47 @@ public class AuthenticationService { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Notify all bound listeners that a successful authentication | ||||
|      * has occurred. | ||||
|      * | ||||
|      * @param authenticatedUser | ||||
|      *      The user that was successfully authenticated. | ||||
|      * | ||||
|      * @param session | ||||
|      *      The existing session for the user (if any). | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *      If thrown by a listener. | ||||
|      */ | ||||
|     private void fireAuthenticationSuccessEvent( | ||||
|             AuthenticatedUser authenticatedUser, GuacamoleSession session) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         UserContext userContext = null; | ||||
|         if (session != null) { | ||||
|             userContext = session.getUserContext( | ||||
|                 authenticatedUser.getAuthenticationProvider().getIdentifier()); | ||||
|         } | ||||
|  | ||||
|         listenerService.handleEvent(new AuthenticationSuccessEvent( | ||||
|             userContext, authenticatedUser.getCredentials())); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Notify all bound listeners that an authentication attempt has failed. | ||||
|      * | ||||
|      * @param credentials | ||||
|      *      The credentials that failed to authenticate. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *      If thrown by a listener. | ||||
|      */ | ||||
|     private void fireAuthenticationFailedEvent(Credentials credentials) | ||||
|             throws GuacamoleException { | ||||
|         listenerService.handleEvent(new AuthenticationFailureEvent(credentials)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the AuthenticatedUser associated with the given session and | ||||
|      * credentials, performing a fresh authentication and creating a new | ||||
| @@ -232,11 +283,17 @@ public class AuthenticationService { | ||||
|         try { | ||||
|  | ||||
|             // Re-authenticate user if session exists | ||||
|             if (existingSession != null) | ||||
|                 return updateAuthenticatedUser(existingSession.getAuthenticatedUser(), credentials); | ||||
|             if (existingSession != null) { | ||||
|                 AuthenticatedUser updatedUser = updateAuthenticatedUser( | ||||
|                         existingSession.getAuthenticatedUser(), credentials); | ||||
|                 fireAuthenticationSuccessEvent(updatedUser, existingSession); | ||||
|                 return updatedUser; | ||||
|             } | ||||
|  | ||||
|             // Otherwise, attempt authentication as a new user | ||||
|             AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials); | ||||
|             fireAuthenticationSuccessEvent(authenticatedUser, null); | ||||
|  | ||||
|             if (logger.isInfoEnabled()) | ||||
|                 logger.info("User \"{}\" successfully authenticated from {}.", | ||||
|                         authenticatedUser.getIdentifier(), | ||||
| @@ -249,6 +306,8 @@ public class AuthenticationService { | ||||
|         // Log and rethrow any authentication errors | ||||
|         catch (GuacamoleException e) { | ||||
|  | ||||
|             fireAuthenticationFailedEvent(credentials); | ||||
|  | ||||
|             // Get request and username for sake of logging | ||||
|             HttpServletRequest request = credentials.getRequest(); | ||||
|             String username = credentials.getUsername(); | ||||
|   | ||||
| @@ -0,0 +1,57 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  *  Unless required by applicable law or agreed to in writing, | ||||
|  *  software distributed under the License is distributed on an | ||||
|  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  *  KIND, either express or implied.  See the License for the | ||||
|  *  specific language governing permissions and limitations | ||||
|  *  under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.rest.event; | ||||
|  | ||||
| import java.util.List; | ||||
| import com.google.inject.Inject; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.net.event.listener.Listener; | ||||
|  | ||||
| /** | ||||
|  * A service used to notify listeners registered by extensions when events of | ||||
|  * interest occur. | ||||
|  */ | ||||
| public class ListenerService implements Listener { | ||||
|  | ||||
|     /** | ||||
|      * The collection of registered listeners. | ||||
|      */ | ||||
|     @Inject | ||||
|     private List<Listener> listeners; | ||||
|  | ||||
|     /** | ||||
|      * Notifies registered listeners than an event has occurred. Notification continues | ||||
|      * until a given listener throws a GuacamoleException or other runtime exception, or | ||||
|      * until all listeners have been notified. | ||||
|      * | ||||
|      * @param event | ||||
|      *      An object that describes the event that has occurred. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *      If a registered listener throws a GuacamoleException. | ||||
|      */ | ||||
|     @Override | ||||
|     public void handleEvent(Object event) throws GuacamoleException { | ||||
|         for (final Listener listener : listeners) { | ||||
|             listener.handleEvent(event); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -29,10 +29,14 @@ import org.apache.guacamole.GuacamoleUnauthorizedException; | ||||
| import org.apache.guacamole.net.GuacamoleTunnel; | ||||
| import org.apache.guacamole.net.auth.Connection; | ||||
| import org.apache.guacamole.net.auth.ConnectionGroup; | ||||
| import org.apache.guacamole.net.auth.Credentials; | ||||
| import org.apache.guacamole.net.auth.Directory; | ||||
| import org.apache.guacamole.net.auth.UserContext; | ||||
| import org.apache.guacamole.net.event.TunnelCloseEvent; | ||||
| import org.apache.guacamole.net.event.TunnelConnectEvent; | ||||
| import org.apache.guacamole.rest.auth.AuthenticationService; | ||||
| import org.apache.guacamole.protocol.GuacamoleClientInformation; | ||||
| import org.apache.guacamole.rest.event.ListenerService; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| @@ -57,6 +61,58 @@ public class TunnelRequestService { | ||||
|     @Inject | ||||
|     private AuthenticationService authenticationService; | ||||
|  | ||||
|     /** | ||||
|      * A service for notifying listeners about tunnel connect/closed events. | ||||
|      */ | ||||
|     @Inject | ||||
|     private ListenerService listenerService; | ||||
|  | ||||
|     /** | ||||
|      * Notifies bound listeners that a new tunnel has been connected. | ||||
|      * Listeners may veto a connected tunnel by throwing any GuacamoleException. | ||||
|      * | ||||
|      * @param userContext | ||||
|      *      The UserContext associated with the user for whom the tunnel is | ||||
|      *      being created. | ||||
|      * | ||||
|      * @param credentials | ||||
|      *      Credentials that authenticate the user. | ||||
|      * | ||||
|      * @param tunnel | ||||
|      *      The tunnel that was connected. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If thrown by a listener or if any listener vetoes the connected tunnel. | ||||
|      */ | ||||
|     private void fireTunnelConnectEvent(UserContext userContext, | ||||
|             Credentials credentials, GuacamoleTunnel tunnel) throws GuacamoleException { | ||||
|         listenerService.handleEvent(new TunnelConnectEvent(userContext, credentials, tunnel)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Notifies bound listeners that a tunnel is to be closed. | ||||
|      * Listeners are allowed to veto a request to close a tunnel by throwing any | ||||
|      * GuacamoleException. | ||||
|      * | ||||
|      * @param userContext | ||||
|      *      The UserContext associated with the user for whom the tunnel is | ||||
|      *      being created. | ||||
|      * | ||||
|      * @param credentials | ||||
|      *      Credentials that authenticate the user. | ||||
|      * | ||||
|      * @param tunnel | ||||
|      *      The tunnel that was connected. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If thrown by a listener. | ||||
|      */ | ||||
|     private void fireTunnelClosedEvent(UserContext userContext, | ||||
|             Credentials credentials, GuacamoleTunnel tunnel) | ||||
|             throws GuacamoleException { | ||||
|         listenerService.handleEvent(new TunnelCloseEvent(userContext, credentials, tunnel)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Reads and returns the client information provided within the given | ||||
|      * request. | ||||
| @@ -226,7 +282,7 @@ public class TunnelRequestService { | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs while obtaining the tunnel. | ||||
|      */ | ||||
|     protected GuacamoleTunnel createAssociatedTunnel(GuacamoleTunnel tunnel, | ||||
|     protected GuacamoleTunnel createAssociatedTunnel(final GuacamoleTunnel tunnel, | ||||
|             final String authToken, final GuacamoleSession session, | ||||
|             final UserContext context, final TunnelRequest.Type type, | ||||
|             final String id) throws GuacamoleException { | ||||
| @@ -243,6 +299,10 @@ public class TunnelRequestService { | ||||
|             @Override | ||||
|             public void close() throws GuacamoleException { | ||||
|  | ||||
|                 // notify listeners to allow close request to be vetoed | ||||
|                 fireTunnelClosedEvent(context, | ||||
|                     session.getAuthenticatedUser().getCredentials(), tunnel); | ||||
|  | ||||
|                 long connectionEndTime = System.currentTimeMillis(); | ||||
|                 long duration = connectionEndTime - connectionStartTime; | ||||
|  | ||||
| @@ -328,6 +388,10 @@ public class TunnelRequestService { | ||||
|             // Create connected tunnel using provided connection ID and client information | ||||
|             GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info); | ||||
|  | ||||
|             // Notify listeners to allow connection to be vetoed | ||||
|             fireTunnelConnectEvent(userContext, | ||||
|                     session.getAuthenticatedUser().getCredentials(), tunnel); | ||||
|  | ||||
|             // Associate tunnel with session | ||||
|             return createAssociatedTunnel(tunnel, authToken, session, userContext, type, id); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user