mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUACAMOLE-364: Merge changes restoring extension support for event listeners.
This commit is contained in:
@@ -26,6 +26,11 @@ import org.apache.guacamole.net.auth.UserContext;
|
|||||||
* An event which is triggered whenever a user's credentials pass
|
* An event which is triggered whenever a user's credentials pass
|
||||||
* authentication. The credentials that passed authentication are included
|
* authentication. The credentials that passed authentication are included
|
||||||
* within this event, and can be retrieved using getCredentials().
|
* within this event, and can be retrieved using getCredentials().
|
||||||
|
* <p>
|
||||||
|
* If a {@link org.apache.guacamole.net.event.listener.Listener} throws
|
||||||
|
* a GuacamoleException when handling an event of this type, successful authentication
|
||||||
|
* is effectively <em>vetoed</em> and will be subsequently processed as though the
|
||||||
|
* authentication failed.
|
||||||
*/
|
*/
|
||||||
public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent {
|
public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent {
|
||||||
|
|
||||||
|
@@ -28,6 +28,10 @@ import org.apache.guacamole.net.auth.UserContext;
|
|||||||
* being closed can be accessed through getTunnel(), and the UserContext
|
* being closed can be accessed through getTunnel(), and the UserContext
|
||||||
* associated with the request which is closing the tunnel can be retrieved
|
* associated with the request which is closing the tunnel can be retrieved
|
||||||
* with getUserContext().
|
* with getUserContext().
|
||||||
|
* <p>
|
||||||
|
* If a {@link org.apache.guacamole.net.event.listener.Listener} throws
|
||||||
|
* a GuacamoleException when handling an event of this type, the request to close
|
||||||
|
* the tunnel is effectively <em>vetoed</em> and will remain connected.
|
||||||
*/
|
*/
|
||||||
public class TunnelCloseEvent implements UserEvent, CredentialEvent, TunnelEvent {
|
public class TunnelCloseEvent implements UserEvent, CredentialEvent, TunnelEvent {
|
||||||
|
|
||||||
|
@@ -28,6 +28,10 @@ import org.apache.guacamole.net.auth.UserContext;
|
|||||||
* being connected can be accessed through getTunnel(), and the UserContext
|
* being connected can be accessed through getTunnel(), and the UserContext
|
||||||
* associated with the request which is connecting the tunnel can be retrieved
|
* associated with the request which is connecting the tunnel can be retrieved
|
||||||
* with getUserContext().
|
* with getUserContext().
|
||||||
|
* <p>
|
||||||
|
* If a {@link org.apache.guacamole.net.event.listener.Listener} throws
|
||||||
|
* a GuacamoleException when handling an event of this type, the tunnel connection
|
||||||
|
* is effectively <em>vetoed</em> and will be subsequently closed.
|
||||||
*/
|
*/
|
||||||
public class TunnelConnectEvent implements UserEvent, CredentialEvent, TunnelEvent {
|
public class TunnelConnectEvent implements UserEvent, CredentialEvent, TunnelEvent {
|
||||||
|
|
||||||
|
@@ -26,19 +26,26 @@ import org.apache.guacamole.net.event.AuthenticationFailureEvent;
|
|||||||
* A listener whose authenticationFailed() hook will fire immediately
|
* A listener whose authenticationFailed() hook will fire immediately
|
||||||
* after a user's authentication attempt fails. Note that this hook cannot
|
* after a user's authentication attempt fails. Note that this hook cannot
|
||||||
* be used to cancel the authentication failure.
|
* be used to cancel the authentication failure.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
* Listeners should instead implement the {@link Listener} interface.
|
||||||
*/
|
*/
|
||||||
public interface AuthenticationFailureListener {
|
@Deprecated
|
||||||
|
public interface AuthenticationFailureListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event hook which fires immediately after a user's authentication attempt
|
* Event hook which fires immediately after a user's authentication attempt
|
||||||
* fails.
|
* fails.
|
||||||
*
|
*
|
||||||
* @param e The AuthenticationFailureEvent describing the authentication
|
* @param e
|
||||||
* failure that just occurred.
|
* The AuthenticationFailureEvent describing the authentication
|
||||||
* @throws GuacamoleException If an error occurs while handling the
|
* failure that just occurred.
|
||||||
* authentication failure event. Note that
|
*
|
||||||
* throwing an exception will NOT cause the
|
* @throws GuacamoleException
|
||||||
* authentication failure to be canceled.
|
* If an error occurs while handling the authentication failure event.
|
||||||
|
* Note that throwing an exception will NOT cause the authentication
|
||||||
|
* failure to be canceled (which makes no sense), but it will prevent
|
||||||
|
* subsequent listeners from receiving the notification.
|
||||||
*/
|
*/
|
||||||
void authenticationFailed(AuthenticationFailureEvent e)
|
void authenticationFailed(AuthenticationFailureEvent e)
|
||||||
throws GuacamoleException;
|
throws GuacamoleException;
|
||||||
|
@@ -27,7 +27,11 @@ import org.apache.guacamole.net.event.AuthenticationSuccessEvent;
|
|||||||
* authentication attempt succeeds. If a user successfully authenticates,
|
* authentication attempt succeeds. If a user successfully authenticates,
|
||||||
* the authenticationSucceeded() hook has the opportunity to cancel the
|
* the authenticationSucceeded() hook has the opportunity to cancel the
|
||||||
* authentication and force it to fail.
|
* authentication and force it to fail.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
* Listeners should instead implement the {@link Listener} interface.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public interface AuthenticationSuccessListener {
|
public interface AuthenticationSuccessListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,15 +39,18 @@ public interface AuthenticationSuccessListener {
|
|||||||
* succeeds. The return value of this hook dictates whether the
|
* succeeds. The return value of this hook dictates whether the
|
||||||
* successful authentication attempt is canceled.
|
* successful authentication attempt is canceled.
|
||||||
*
|
*
|
||||||
* @param e The AuthenticationFailureEvent describing the authentication
|
* @param e
|
||||||
* failure that just occurred.
|
* The AuthenticationFailureEvent describing the authentication
|
||||||
* @return true if the successful authentication attempt should be
|
* failure that just occurred.
|
||||||
* allowed, or false if the attempt should be denied, causing
|
*
|
||||||
* the attempt to effectively fail.
|
* @return
|
||||||
* @throws GuacamoleException If an error occurs while handling the
|
* true if the successful authentication attempt should be
|
||||||
* authentication success event. Throwing an
|
* allowed, or false if the attempt should be denied, causing
|
||||||
* exception will also cancel the authentication
|
* the attempt to effectively fail.
|
||||||
* success.
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If an error occurs while handling the authentication success event.
|
||||||
|
* Throwing an exception will also cancel the authentication success.
|
||||||
*/
|
*/
|
||||||
boolean authenticationSucceeded(AuthenticationSuccessEvent e)
|
boolean authenticationSucceeded(AuthenticationSuccessEvent e)
|
||||||
throws GuacamoleException;
|
throws GuacamoleException;
|
||||||
|
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.net.event.listener;
|
||||||
|
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for events that occur in handing various Guacamole requests
|
||||||
|
* such as authentication, tunnel connect/close, etc. Listeners are registered
|
||||||
|
* through the extension manifest mechanism. When an event occurs, listeners
|
||||||
|
* are notified in the order in which they are declared in the manifest and
|
||||||
|
* continues until either all listeners have been notified or with the first
|
||||||
|
* listener that throws a GuacamoleException or other runtime exception.
|
||||||
|
*/
|
||||||
|
public interface Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the recipient that an event has occurred.
|
||||||
|
* <p>
|
||||||
|
* Throwing an exception from an event listener can act to veto an action in
|
||||||
|
* progress for some event types. See the Javadoc for specific event types for
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* An object that describes the event that has occurred.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the listener wishes to stop notification of the event to subsequent
|
||||||
|
* listeners. For some event types, this acts to veto an action in progress;
|
||||||
|
* e.g. treating a successful authentication as though it failed.
|
||||||
|
*/
|
||||||
|
void handleEvent(Object event) throws GuacamoleException;
|
||||||
|
|
||||||
|
}
|
@@ -23,26 +23,33 @@ import org.apache.guacamole.GuacamoleException;
|
|||||||
import org.apache.guacamole.net.event.TunnelCloseEvent;
|
import org.apache.guacamole.net.event.TunnelCloseEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A listener whose tunnelClosed() hook will fire immediately after an
|
* A listener whose tunnelClosed() hook will fire immediately before an
|
||||||
* existing tunnel is closed.
|
* existing tunnel is closed.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
* Listeners should instead implement the {@link Listener} interface.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public interface TunnelCloseListener {
|
public interface TunnelCloseListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event hook which fires immediately after an existing tunnel is closed.
|
* Event hook which fires immediately before an existing tunnel is closed.
|
||||||
* The return value of this hook dictates whether the tunnel is allowed to
|
* The return value of this hook dictates whether the tunnel is allowed to
|
||||||
* be closed.
|
* be closed.
|
||||||
*
|
*
|
||||||
* @param e The TunnelCloseEvent describing the tunnel being closed and
|
* @param e
|
||||||
* any associated credentials.
|
* The TunnelCloseEvent describing the tunnel being closed and
|
||||||
* @return true if the tunnel should be allowed to be closed, or false
|
* any associated credentials.
|
||||||
* if the attempt should be denied, causing the attempt to
|
*
|
||||||
* effectively fail.
|
* @return
|
||||||
* @throws GuacamoleException If an error occurs while handling the
|
* true if the tunnel should be allowed to be closed, or false
|
||||||
* tunnel close event. Throwing an exception
|
* if the attempt should be denied, causing the attempt to
|
||||||
* will also stop the tunnel from being closed.
|
* effectively fail.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If an error occurs while handling the tunnel close event. Throwing
|
||||||
|
* an exception will also stop the tunnel from being closed.
|
||||||
*/
|
*/
|
||||||
boolean tunnelClosed(TunnelCloseEvent e)
|
boolean tunnelClosed(TunnelCloseEvent e) throws GuacamoleException;
|
||||||
throws GuacamoleException;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -25,25 +25,32 @@ import org.apache.guacamole.net.event.TunnelConnectEvent;
|
|||||||
/**
|
/**
|
||||||
* A listener whose tunnelConnected() hook will fire immediately after a new
|
* A listener whose tunnelConnected() hook will fire immediately after a new
|
||||||
* tunnel is connected.
|
* tunnel is connected.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
* Listeners should instead implement the {@link Listener} interface.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public interface TunnelConnectListener {
|
public interface TunnelConnectListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event hook which fires immediately after a new tunnel is connected.
|
* Event hook which fires immediately after a new tunnel is connected.
|
||||||
* The return value of this hook dictates whether the tunnel is made visible
|
* The return value of this hook dictates whether the tunnel is made visible
|
||||||
* to the session.
|
* to the session.
|
||||||
*
|
*
|
||||||
* @param e The TunnelConnectEvent describing the tunnel being connected and
|
* @param e
|
||||||
* any associated credentials.
|
* The TunnelConnectEvent describing the tunnel being connected and
|
||||||
* @return true if the tunnel should be allowed to be connected, or false
|
* any associated credentials.
|
||||||
* if the attempt should be denied, causing the attempt to
|
*
|
||||||
* effectively fail.
|
* @return
|
||||||
* @throws GuacamoleException If an error occurs while handling the
|
* true if the tunnel should be allowed to be connected, or false
|
||||||
* tunnel connect event. Throwing an exception
|
* if the attempt should be denied, causing the attempt to
|
||||||
* will also stop the tunnel from being made
|
* effectively fail.
|
||||||
* visible to the session.
|
*
|
||||||
*/
|
* @throws GuacamoleException
|
||||||
boolean tunnelConnected(TunnelConnectEvent e)
|
* If an error occurs while handling the tunnel connect event. Throwing
|
||||||
throws GuacamoleException;
|
* an exception will also stop the tunnel from being made visible to the
|
||||||
|
* session.
|
||||||
|
*/
|
||||||
|
boolean tunnelConnected(TunnelConnectEvent e) throws GuacamoleException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package org.apache.guacamole.extension;
|
package org.apache.guacamole.extension;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||||
@@ -66,58 +65,8 @@ public class AuthenticationProviderFacade implements AuthenticationProvider {
|
|||||||
* The AuthenticationProvider subclass to instantiate.
|
* The AuthenticationProvider subclass to instantiate.
|
||||||
*/
|
*/
|
||||||
public AuthenticationProviderFacade(Class<? extends AuthenticationProvider> authProviderClass) {
|
public AuthenticationProviderFacade(Class<? extends AuthenticationProvider> authProviderClass) {
|
||||||
|
authProvider = ProviderFactory.newInstance("authentication provider",
|
||||||
AuthenticationProvider instance = null;
|
authProviderClass);
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -35,6 +35,7 @@ import java.util.Map;
|
|||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipException;
|
import java.util.zip.ZipException;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
|
import org.apache.guacamole.net.event.listener.Listener;
|
||||||
import org.codehaus.jackson.JsonParseException;
|
import org.codehaus.jackson.JsonParseException;
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
@@ -109,6 +110,11 @@ public class Extension {
|
|||||||
*/
|
*/
|
||||||
private final Collection<Class<AuthenticationProvider>> authenticationProviderClasses;
|
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
|
* The resource for the small favicon for the extension. If provided, this
|
||||||
* will replace the default Guacamole icon.
|
* 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
|
* Loads the given file as an extension, which must be a .jar containing
|
||||||
* a guac-manifest.json file describing its contents.
|
* a guac-manifest.json file describing its contents.
|
||||||
@@ -363,6 +443,9 @@ public class Extension {
|
|||||||
// Define authentication providers
|
// Define authentication providers
|
||||||
authenticationProviderClasses = getAuthenticationProviderClasses(manifest.getAuthProviders());
|
authenticationProviderClasses = getAuthenticationProviderClasses(manifest.getAuthProviders());
|
||||||
|
|
||||||
|
// Define listeners
|
||||||
|
listenerClasses = getListenerClasses(manifest.getListeners());
|
||||||
|
|
||||||
// Get small icon resource if provided
|
// Get small icon resource if provided
|
||||||
if (manifest.getSmallIcon() != null)
|
if (manifest.getSmallIcon() != null)
|
||||||
smallIcon = new ClassPathResource(classLoader, "image/png", manifest.getSmallIcon());
|
smallIcon = new ClassPathResource(classLoader, "image/png", manifest.getSmallIcon());
|
||||||
@@ -488,6 +571,17 @@ public class Extension {
|
|||||||
return authenticationProviderClasses;
|
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
|
* Returns the resource for the small favicon for the extension. If
|
||||||
* provided, this will replace the default Guacamole icon.
|
* provided, this will replace the default Guacamole icon.
|
||||||
|
@@ -87,6 +87,11 @@ public class ExtensionManifest {
|
|||||||
*/
|
*/
|
||||||
private Collection<String> authProviders;
|
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
|
* The path to the small favicon. If provided, this will replace the default
|
||||||
* Guacamole icon.
|
* Guacamole icon.
|
||||||
@@ -355,6 +360,32 @@ public class ExtensionManifest {
|
|||||||
this.authProviders = authProviders;
|
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
|
* Returns the path to the small favicon, relative to the root of the
|
||||||
* extension.
|
* extension.
|
||||||
|
@@ -34,6 +34,7 @@ import org.apache.guacamole.GuacamoleException;
|
|||||||
import org.apache.guacamole.GuacamoleServerException;
|
import org.apache.guacamole.GuacamoleServerException;
|
||||||
import org.apache.guacamole.environment.Environment;
|
import org.apache.guacamole.environment.Environment;
|
||||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
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.Resource;
|
||||||
import org.apache.guacamole.resource.ResourceServlet;
|
import org.apache.guacamole.resource.ResourceServlet;
|
||||||
import org.apache.guacamole.resource.SequenceResource;
|
import org.apache.guacamole.resource.SequenceResource;
|
||||||
@@ -91,6 +92,12 @@ public class ExtensionModule extends ServletModule {
|
|||||||
private final List<AuthenticationProvider> boundAuthenticationProviders =
|
private final List<AuthenticationProvider> boundAuthenticationProviders =
|
||||||
new ArrayList<AuthenticationProvider>();
|
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.
|
* Service for adding and retrieving language resources.
|
||||||
*/
|
*/
|
||||||
@@ -187,6 +194,49 @@ public class ExtensionModule extends ServletModule {
|
|||||||
return Collections.unmodifiableList(boundAuthenticationProviders);
|
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
|
* Serves each of the given resources as a language resource. Language
|
||||||
* resources are served from within the "/translations" directory as JSON
|
* 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
|
// Attempt to load all authentication providers
|
||||||
bindAuthenticationProviders(extension.getAuthenticationProviderClasses());
|
bindAuthenticationProviders(extension.getAuthenticationProviderClasses());
|
||||||
|
|
||||||
|
// Attempt to load all listeners
|
||||||
|
bindListeners(extension.getListenerClasses());
|
||||||
|
|
||||||
// Add any translation resources
|
// Add any translation resources
|
||||||
serveLanguageResources(extension.getTranslationResources());
|
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;
|
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.UserContextResourceFactory;
|
||||||
import org.apache.guacamole.rest.session.SessionRESTService;
|
import org.apache.guacamole.rest.session.SessionRESTService;
|
||||||
import com.google.inject.Scopes;
|
import com.google.inject.Scopes;
|
||||||
@@ -76,6 +77,7 @@ public class RESTServiceModule extends ServletModule {
|
|||||||
bind(TokenSessionMap.class).toInstance(tokenSessionMap);
|
bind(TokenSessionMap.class).toInstance(tokenSessionMap);
|
||||||
|
|
||||||
// Bind low-level services
|
// Bind low-level services
|
||||||
|
bind(ListenerService.class);
|
||||||
bind(AuthenticationService.class);
|
bind(AuthenticationService.class);
|
||||||
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);
|
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);
|
||||||
|
|
||||||
|
@@ -24,9 +24,11 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.GuacamoleSecurityException;
|
import org.apache.guacamole.GuacamoleSecurityException;
|
||||||
import org.apache.guacamole.GuacamoleUnauthorizedException;
|
import org.apache.guacamole.GuacamoleUnauthorizedException;
|
||||||
|
import org.apache.guacamole.GuacamoleSession;
|
||||||
import org.apache.guacamole.environment.Environment;
|
import org.apache.guacamole.environment.Environment;
|
||||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
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.CredentialsInfo;
|
||||||
import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
|
import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
|
||||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -74,6 +78,12 @@ public class AuthenticationService {
|
|||||||
@Inject
|
@Inject
|
||||||
private AuthTokenGenerator authTokenGenerator;
|
private AuthTokenGenerator authTokenGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The service to use to notify registered authentication listeners.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private ListenerService listenerService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regular expression which matches any IPv4 address.
|
* 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
|
* Returns the AuthenticatedUser associated with the given session and
|
||||||
* credentials, performing a fresh authentication and creating a new
|
* credentials, performing a fresh authentication and creating a new
|
||||||
@@ -232,11 +283,17 @@ public class AuthenticationService {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
// Re-authenticate user if session exists
|
// Re-authenticate user if session exists
|
||||||
if (existingSession != null)
|
if (existingSession != null) {
|
||||||
return updateAuthenticatedUser(existingSession.getAuthenticatedUser(), credentials);
|
AuthenticatedUser updatedUser = updateAuthenticatedUser(
|
||||||
|
existingSession.getAuthenticatedUser(), credentials);
|
||||||
|
fireAuthenticationSuccessEvent(updatedUser, existingSession);
|
||||||
|
return updatedUser;
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, attempt authentication as a new user
|
// Otherwise, attempt authentication as a new user
|
||||||
AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials);
|
AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials);
|
||||||
|
fireAuthenticationSuccessEvent(authenticatedUser, null);
|
||||||
|
|
||||||
if (logger.isInfoEnabled())
|
if (logger.isInfoEnabled())
|
||||||
logger.info("User \"{}\" successfully authenticated from {}.",
|
logger.info("User \"{}\" successfully authenticated from {}.",
|
||||||
authenticatedUser.getIdentifier(),
|
authenticatedUser.getIdentifier(),
|
||||||
@@ -249,6 +306,8 @@ public class AuthenticationService {
|
|||||||
// Log and rethrow any authentication errors
|
// Log and rethrow any authentication errors
|
||||||
catch (GuacamoleException e) {
|
catch (GuacamoleException e) {
|
||||||
|
|
||||||
|
fireAuthenticationFailedEvent(credentials);
|
||||||
|
|
||||||
// Get request and username for sake of logging
|
// Get request and username for sake of logging
|
||||||
HttpServletRequest request = credentials.getRequest();
|
HttpServletRequest request = credentials.getRequest();
|
||||||
String username = credentials.getUsername();
|
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.GuacamoleTunnel;
|
||||||
import org.apache.guacamole.net.auth.Connection;
|
import org.apache.guacamole.net.auth.Connection;
|
||||||
import org.apache.guacamole.net.auth.ConnectionGroup;
|
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.Directory;
|
||||||
import org.apache.guacamole.net.auth.UserContext;
|
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.rest.auth.AuthenticationService;
|
||||||
import org.apache.guacamole.protocol.GuacamoleClientInformation;
|
import org.apache.guacamole.protocol.GuacamoleClientInformation;
|
||||||
|
import org.apache.guacamole.rest.event.ListenerService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -57,6 +61,58 @@ public class TunnelRequestService {
|
|||||||
@Inject
|
@Inject
|
||||||
private AuthenticationService authenticationService;
|
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
|
* Reads and returns the client information provided within the given
|
||||||
* request.
|
* request.
|
||||||
@@ -226,7 +282,7 @@ public class TunnelRequestService {
|
|||||||
* @throws GuacamoleException
|
* @throws GuacamoleException
|
||||||
* If an error occurs while obtaining the tunnel.
|
* 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 String authToken, final GuacamoleSession session,
|
||||||
final UserContext context, final TunnelRequest.Type type,
|
final UserContext context, final TunnelRequest.Type type,
|
||||||
final String id) throws GuacamoleException {
|
final String id) throws GuacamoleException {
|
||||||
@@ -243,6 +299,10 @@ public class TunnelRequestService {
|
|||||||
@Override
|
@Override
|
||||||
public void close() throws GuacamoleException {
|
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 connectionEndTime = System.currentTimeMillis();
|
||||||
long duration = connectionEndTime - connectionStartTime;
|
long duration = connectionEndTime - connectionStartTime;
|
||||||
|
|
||||||
@@ -328,6 +388,10 @@ public class TunnelRequestService {
|
|||||||
// Create connected tunnel using provided connection ID and client information
|
// Create connected tunnel using provided connection ID and client information
|
||||||
GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info);
|
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
|
// Associate tunnel with session
|
||||||
return createAssociatedTunnel(tunnel, authToken, session, userContext, type, id);
|
return createAssociatedTunnel(tunnel, authToken, session, userContext, type, id);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user