From f63c8b43a8277f9d38ada78f4acac72e02fcbed6 Mon Sep 17 00:00:00 2001 From: Carl Harris Date: Thu, 7 Sep 2017 16:58:44 -0400 Subject: [PATCH] GUACAMOLE-364: declare and implement new listener API This commit also deprecates the existing listener API and includes support for adapting existing listener implementations to the new API. --- ...camoleAuthenticationRejectedException.java | 33 --- .../GuacamoleTunnelConnectedException.java | 32 --- .../GuacamoleTunnelRejectedException.java | 32 --- .../net/event/AuthenticationSuccessEvent.java | 5 + .../guacamole/net/event/TunnelCloseEvent.java | 4 + .../net/event/TunnelConnectEvent.java | 4 + .../AuthenticationFailureListener.java | 6 +- .../AuthenticationSuccessListener.java | 6 +- .../net/event/listener/Listener.java | 29 +- .../event/listener/TunnelCloseListener.java | 6 +- .../event/listener/TunnelConnectListener.java | 6 +- .../apache/guacamole/extension/Extension.java | 10 +- .../guacamole/extension/ExtensionModule.java | 42 +-- .../guacamole/extension/ListenerFacade.java | 134 --------- .../guacamole/extension/ListenerFactory.java | 256 ++++++++++++++++++ .../rest/auth/AuthenticationService.java | 35 +-- .../guacamole/rest/event/ListenerService.java | 108 +------- .../tunnel/TunnelRequestService.java | 41 +-- 18 files changed, 378 insertions(+), 411 deletions(-) delete mode 100644 guacamole-common/src/main/java/org/apache/guacamole/GuacamoleAuthenticationRejectedException.java delete mode 100644 guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelConnectedException.java delete mode 100644 guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelRejectedException.java delete mode 100644 guacamole/src/main/java/org/apache/guacamole/extension/ListenerFacade.java create mode 100644 guacamole/src/main/java/org/apache/guacamole/extension/ListenerFactory.java diff --git a/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleAuthenticationRejectedException.java b/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleAuthenticationRejectedException.java deleted file mode 100644 index 907e0c947..000000000 --- a/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleAuthenticationRejectedException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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; - -/** - * An exception thrown when a successful authentication is rejected by a - * AuthenticationSuccessListener in an extension. - */ -public class GuacamoleAuthenticationRejectedException - extends GuacamoleSecurityException { - - public GuacamoleAuthenticationRejectedException() { - super("authentication rejected by listener extension"); - } - -} diff --git a/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelConnectedException.java b/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelConnectedException.java deleted file mode 100644 index c7d21cf1d..000000000 --- a/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelConnectedException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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; - -/** - * An exception thrown when a request to close a tunnel is vetoed by a - * TunnelCloseListener in an extension. - */ -public class GuacamoleTunnelConnectedException extends GuacamoleClientException { - - public GuacamoleTunnelConnectedException() { - super("tunnel close vetoed by listener extension"); - } - -} diff --git a/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelRejectedException.java b/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelRejectedException.java deleted file mode 100644 index b9367c180..000000000 --- a/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelRejectedException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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; - -/** - * An exception thrown when a successful tunnel connection is rejected by a - * TunnelConnectListener in an extension. - */ -public class GuacamoleTunnelRejectedException extends GuacamoleClientException { - - public GuacamoleTunnelRejectedException() { - super("tunnel connection rejected by listener extension"); - } - -} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java index 2e5ae3a17..c72d669fa 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/AuthenticationSuccessEvent.java @@ -26,6 +26,11 @@ import org.apache.guacamole.net.auth.UserContext; * An event which is triggered whenever a user's credentials pass * authentication. The credentials that passed authentication are included * within this event, and can be retrieved using getCredentials(). + *

+ * If a {@link org.apache.guacamole.net.event.listener.Listener} throws + * a GuacamoleException when handling an event of this type, successful authentication + * is effectively vetoed and will be subsequently processed as though the + * authentication failed. */ public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent { diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelCloseEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelCloseEvent.java index ab453e87e..c0e2a622e 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelCloseEvent.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelCloseEvent.java @@ -28,6 +28,10 @@ import org.apache.guacamole.net.auth.UserContext; * being closed can be accessed through getTunnel(), and the UserContext * associated with the request which is closing the tunnel can be retrieved * with getUserContext(). + *

+ * 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 vetoed and will remain connected. */ public class TunnelCloseEvent implements UserEvent, CredentialEvent, TunnelEvent { diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelConnectEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelConnectEvent.java index acf5e8922..62828db8f 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelConnectEvent.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/TunnelConnectEvent.java @@ -28,6 +28,10 @@ import org.apache.guacamole.net.auth.UserContext; * being connected can be accessed through getTunnel(), and the UserContext * associated with the request which is connecting the tunnel can be retrieved * with getUserContext(). + *

+ * 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 vetoed and will be subsequently closed. */ public class TunnelConnectEvent implements UserEvent, CredentialEvent, TunnelEvent { diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationFailureListener.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationFailureListener.java index 86122bad2..1971af89b 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationFailureListener.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationFailureListener.java @@ -26,8 +26,12 @@ import org.apache.guacamole.net.event.AuthenticationFailureEvent; * A listener whose authenticationFailed() hook will fire immediately * after a user's authentication attempt fails. Note that this hook cannot * be used to cancel the authentication failure. + * + * @deprecated + * Listeners should instead implement the {@link Listener} interface */ -public interface AuthenticationFailureListener extends Listener { +@Deprecated +public interface AuthenticationFailureListener { /** * Event hook which fires immediately after a user's authentication attempt diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationSuccessListener.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationSuccessListener.java index cc5e01d57..77a6ed1b2 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationSuccessListener.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/AuthenticationSuccessListener.java @@ -27,8 +27,12 @@ import org.apache.guacamole.net.event.AuthenticationSuccessEvent; * authentication attempt succeeds. If a user successfully authenticates, * the authenticationSucceeded() hook has the opportunity to cancel the * authentication and force it to fail. + * + * @deprecated + * Listeners should instead implement the {@link Listener} interface */ -public interface AuthenticationSuccessListener extends Listener { +@Deprecated +public interface AuthenticationSuccessListener { /** * Event hook which fires immediately after a user's authentication attempt diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/Listener.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/Listener.java index 58499934e..d21f68681 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/Listener.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/Listener.java @@ -19,10 +19,33 @@ package org.apache.guacamole.net.event.listener; +import org.apache.guacamole.GuacamoleException; + /** - * A marker interface extended by all listener types. This interface is used - * simply to validate that a listener class identified in an extension - * actually implements some listener interface. + * 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. + *

+ * 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 subject event + * + * @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; + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelCloseListener.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelCloseListener.java index 99f2c04de..70677b3ac 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelCloseListener.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelCloseListener.java @@ -25,8 +25,12 @@ import org.apache.guacamole.net.event.TunnelCloseEvent; /** * A listener whose tunnelClosed() hook will fire immediately after an * existing tunnel is closed. + * + * @deprecated + * Listeners should instead implement the {@link Listener} interface */ -public interface TunnelCloseListener extends Listener { +@Deprecated +public interface TunnelCloseListener { /** * Event hook which fires immediately before an existing tunnel is closed. diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelConnectListener.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelConnectListener.java index 7ac47e189..edc144e29 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelConnectListener.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/listener/TunnelConnectListener.java @@ -25,8 +25,12 @@ import org.apache.guacamole.net.event.TunnelConnectEvent; /** * A listener whose tunnelConnected() hook will fire immediately after a new * tunnel is connected. + * + * @deprecated + * Listeners should instead implement the {@link Listener} interface */ -public interface TunnelConnectListener extends Listener { +@Deprecated +public interface TunnelConnectListener { /** * Event hook which fires immediately after a new tunnel is connected. diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java b/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java index 697002d42..dc43b8f00 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/Extension.java @@ -113,7 +113,7 @@ public class Extension { /** * The collection of all Listener classes defined within the extension. */ - private final Collection> listenerClasses; + private final Collection> listenerClasses; /** * The resource for the small favicon for the extension. If provided, this @@ -328,15 +328,15 @@ public class Extension { * If any given class does not exist, or if any given class is not a * subclass of AuthenticationProvider. */ - private Collection> getListenerClasses(Collection names) + private Collection> getListenerClasses(Collection names) throws GuacamoleException { // If no classnames are provided, just return an empty list if (names == null) - return Collections.>emptyList(); + return Collections.>emptyList(); // Define all auth provider classes - Collection> classes = new ArrayList>(names.size()); + Collection> classes = new ArrayList>(names.size()); for (String name : names) classes.add(getListenerClass(name)); @@ -578,7 +578,7 @@ public class Extension { * @return * All declared listener classes with this extension. */ - public Collection> getListenerClasses() { + public Collection> getListenerClasses() { return listenerClasses; } diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java index 1e1a85452..f3ca70067 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java @@ -95,8 +95,8 @@ public class ExtensionModule extends ServletModule { /** * All currently-bound authentication providers, if any. */ - private final List boundListenerProviders = - new ArrayList(); + private final List boundListners = + new ArrayList(); /** * Service for adding and retrieving language resources. @@ -195,19 +195,19 @@ public class ExtensionModule extends ServletModule { } /** - * Binds the given Listener class such that any service - * requiring access to the Listener can obtain it via - * injection, along with any other bound Listener. - * - * @param listenerClass - * The Listener class to bind. + * 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 provider class to bind */ - private void bindListenerProvider(Class listenerClass) { + private void bindListeners(Class providerClass) { + + logger.debug("[{}] Binding listeners \"{}\".", + boundListners.size(), providerClass.getName()); + boundListners.addAll(ListenerFactory.createListeners(providerClass)); - // Bind listener - logger.debug("[{}] Binding Listener \"{}\".", - boundListenerProviders.size(), listenerClass.getName()); - boundListenerProviders.add(new ListenerFacade(listenerClass)); } /** @@ -218,23 +218,23 @@ public class ExtensionModule extends ServletModule { * @param listeners * The Listener classes to bind. */ - private void bindListenerProviders(Collection> listeners) { + private void bindListeners(Collection> listeners) { // Bind each listener within extension - for (Class listener : listeners) - bindListenerProvider(listener); + for (Class listener : listeners) + bindListeners(listener); } /** - * Returns a list of all currently-bound ListenerProvider instances. + * Returns a list of all currently-bound Listener instances. * * @return - * A List of all currently-bound ListenerProvider instances. The List is + * A List of all currently-bound Listener instances. The List is * not modifiable. */ @Provides - public List getListenerProviders() { - return Collections.unmodifiableList(boundListenerProviders); + public List getListeners() { + return Collections.unmodifiableList(boundListners); } /** @@ -378,7 +378,7 @@ public class ExtensionModule extends ServletModule { bindAuthenticationProviders(extension.getAuthenticationProviderClasses()); // Attempt to load all listeners - bindListenerProviders(extension.getListenerClasses()); + bindListeners(extension.getListenerClasses()); // Add any translation resources serveLanguageResources(extension.getTranslationResources()); diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ListenerFacade.java b/guacamole/src/main/java/org/apache/guacamole/extension/ListenerFacade.java deleted file mode 100644 index ab0b7c85d..000000000 --- a/guacamole/src/main/java/org/apache/guacamole/extension/ListenerFacade.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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.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.*; - -/** - * Provides a wrapper around a Listener subclass, allowing listener - * extensions to be bound without regard for which specific listener interfaces - * are implemented. - */ -class ListenerFacade implements ListenerProvider { - - private final Listener delegate; - - /** - * Creates a new ListenerFacade which delegates all listener methods - * calls to an instance of the given Listener subclass. If - * an instance of the given class cannot be created, creation of this - * facade will still succeed. Errors will be logged at the time listener - * creation fails, but subsequent events directed to the listener will be - * silently dropped. - * - * @param listenerClass - * The Listener subclass to instantiate. - */ - public ListenerFacade(Class listenerClass) { - delegate = ProviderFactory.newInstance("listener", listenerClass); - } - - /** - * Notifies the delegate listener of an authentication success event, if the - * listener implements the AuthenticationSuccessListener interface. - * - * @param - * event The AuthenticationSuccessEvent describing the authentication - * success that just occurred. - * @return - * false if the delegate listener rejects the successful authentication, - * else true - * - * @throws GuacamoleException - * if the delegate listener throws this exception - */ - @Override - public boolean authenticationSucceeded(AuthenticationSuccessEvent event) - throws GuacamoleException { - return !(delegate instanceof AuthenticationSuccessListener) - || ((AuthenticationSuccessListener) delegate).authenticationSucceeded(event); - } - - /** - * Notifies the delegate listener of an authentication failure event, if the - * listener implements the AuthenticationSuccessListener interface. - * - * @param - * event The AuthenticationFailureEvent describing the authentication - * failure that just occurred. - * - * @throws GuacamoleException - * if the delegate listener throws this exception - */ - @Override - public void authenticationFailed(AuthenticationFailureEvent event) - throws GuacamoleException { - if (delegate instanceof AuthenticationFailureListener) { - ((AuthenticationFailureListener) delegate).authenticationFailed(event); - } - } - - /** - * Notifies the delegate listener of a tunnel connected event, if the - * listener implements the TunnelConnectListener interface. - * - * @param - * event The TunnelConnectEvent describing the tunnel that was just connected - - * @return - * false if the delegate listener rejects the tunnel connection, - * else true - * - * @throws GuacamoleException - * if the delegate listener throws this exception - */ - @Override - public boolean tunnelConnected(TunnelConnectEvent event) - throws GuacamoleException { - return !(delegate instanceof TunnelConnectListener) - || ((TunnelConnectListener) delegate).tunnelConnected(event); - } - - /** - * Notifies the delegate listener of a tunnel close event, if the - * listener implements the TunnelCloseListener interface. - * - * @param - * event The TunnelCloseEvent describing the tunnel that is to be close - - * @return - * false if the delegate listener rejects the tunnel close request, - * else true - * - * @throws GuacamoleException - * if the delegate listener throws this exception - */ - @Override - public boolean tunnelClosed(TunnelCloseEvent event) throws GuacamoleException { - return !(delegate instanceof TunnelCloseListener) - || ((TunnelCloseListener) delegate).tunnelClosed(event); - } - -} diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ListenerFactory.java b/guacamole/src/main/java/org/apache/guacamole/extension/ListenerFactory.java new file mode 100644 index 000000000..c11f00f5d --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ListenerFactory.java @@ -0,0 +1,256 @@ +/* + * 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. + *

+ * 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 provider + * + * @return + * list of listeners represented by the given provider class + */ + static List createListeners(Class providerClass) { + + Object provider = ProviderFactory.newInstance("listener", providerClass); + + if (provider instanceof Listener) { + return Collections.singletonList((Listener) provider); + } + + return createListenerAdapters(provider); + + } + + @SuppressWarnings("deprecation") + private static List createListenerAdapters(Object provider) { + + final List listeners = new ArrayList(); + + 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 { + + private final AuthenticationSuccessListener delegate; + + /** + * Constructs a new adapter. + * + * @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 subject event + * + * @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 { + + private final AuthenticationFailureListener delegate; + + /** + * Constructs a new adapter. + * + * @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 subject event + * + * @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 { + + private final TunnelConnectListener delegate; + + /** + * Constructs a new adapter. + * + * @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 subject event + * + * @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 { + + private final TunnelCloseListener delegate; + + /** + * Constructs a new adapter. + * + * @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 subject event + * + * @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"); + } + } + } + + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java index cca284524..bdefc3efb 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java @@ -24,7 +24,7 @@ import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; -import org.apache.guacamole.GuacamoleAuthenticationRejectedException; + import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleUnauthorizedException; @@ -218,19 +218,17 @@ public class AuthenticationService { } /** - * Notify all bound AuthenticationSuccessListeners that a successful authentication - * has occurred. If any of the bound listeners returns false (indicating that the - * authentication should be rejected) a GuacamoleRejectedAuthenticationException is - * thrown. + * Notify all bound listeners that a successful authentication + * has occurred. * * @param authenticatedUser * The user that was successfully authenticated * @param session * Existing session for the user (if any) * @throws GuacamoleException - * If a filter throws an exception or if any filter rejects the authentication + * If thrown by a listener */ - private void notifyAuthenticationSuccessListeners( + private void fireAuthenticationSuccessEvent( AuthenticatedUser authenticatedUser, GuacamoleSession session) throws GuacamoleException { @@ -240,26 +238,21 @@ public class AuthenticationService { authenticatedUser.getAuthenticationProvider().getIdentifier()); } - AuthenticationSuccessEvent event = new AuthenticationSuccessEvent( - userContext, authenticatedUser.getCredentials()); - - if (!listenerService.authenticationSucceeded(event)) { - throw new GuacamoleAuthenticationRejectedException(); - } + listenerService.handleEvent(new AuthenticationSuccessEvent( + userContext, authenticatedUser.getCredentials())); } /** - * Notify all bound AuthenticationFailureListeners that an authentication has failed. + * Notify all bound listeners that an authentication attempt has failed. * * @param credentials * The credentials that failed to authenticate * @throws GuacamoleException - * If a filter throws an exception + * If thrown by a listener */ - private void notifyAuthenticationFailureListeners(Credentials credentials) + private void fireAuthenticationFailedEvent(Credentials credentials) throws GuacamoleException { - - listenerService.authenticationFailed(new AuthenticationFailureEvent(credentials)); + listenerService.handleEvent(new AuthenticationFailureEvent(credentials)); } /** @@ -290,13 +283,13 @@ public class AuthenticationService { if (existingSession != null) { AuthenticatedUser updatedUser = updateAuthenticatedUser( existingSession.getAuthenticatedUser(), credentials); - notifyAuthenticationSuccessListeners(updatedUser, existingSession); + fireAuthenticationSuccessEvent(updatedUser, existingSession); return updatedUser; } // Otherwise, attempt authentication as a new user AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials); - notifyAuthenticationSuccessListeners(authenticatedUser, null); + fireAuthenticationSuccessEvent(authenticatedUser, null); if (logger.isInfoEnabled()) logger.info("User \"{}\" successfully authenticated from {}.", @@ -310,7 +303,7 @@ public class AuthenticationService { // Log and rethrow any authentication errors catch (GuacamoleException e) { - notifyAuthenticationFailureListeners(credentials); + fireAuthenticationFailedEvent(credentials); // Get request and username for sake of logging HttpServletRequest request = credentials.getRequest(); diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/event/ListenerService.java b/guacamole/src/main/java/org/apache/guacamole/rest/event/ListenerService.java index 30c4357df..bbff8ee4b 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/event/ListenerService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/event/ListenerService.java @@ -22,118 +22,32 @@ package org.apache.guacamole.rest.event; import java.util.List; import com.google.inject.Inject; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.extension.ListenerProvider; -import org.apache.guacamole.net.event.AuthenticationFailureEvent; -import org.apache.guacamole.net.event.AuthenticationSuccessEvent; -import org.apache.guacamole.net.event.TunnelConnectEvent; -import org.apache.guacamole.net.event.TunnelCloseEvent; -import org.apache.guacamole.net.event.listener.AuthenticationFailureListener; -import org.apache.guacamole.net.event.listener.AuthenticationSuccessListener; -import org.apache.guacamole.net.event.listener.TunnelCloseListener; -import org.apache.guacamole.net.event.listener.TunnelConnectListener; +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 ListenerProvider { +public class ListenerService implements Listener { @Inject - private List listeners; + private List listeners; /** - * Notifies all bound listeners of an authentication success event. Listeners - * are allowed to veto a successful authentication by returning false from the - * listener method. Regardless of whether a particular listener rejects the - * successful authentication, all listeners are notified. + * 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 - * The AuthenticationSuccessEvent describing the successful authentication - * that just occurred. + * an object that describes the subject event * - * @return - * false if any bound listener returns false, else true - * - * @throws GuacamoleException - * If any bound listener throws this exception. If a listener throws an exception - * some listeners may not receive the authentication success event notification. + * @throws GuacamoleException if a registered listener throws a GuacamoleException */ @Override - public boolean authenticationSucceeded(AuthenticationSuccessEvent event) - throws GuacamoleException { - boolean result = true; - for (AuthenticationSuccessListener listener : listeners) { - result = result && listener.authenticationSucceeded(event); + public void handleEvent(Object event) throws GuacamoleException { + for (final Listener listener : listeners) { + listener.handleEvent(event); } - return result; - } - - /** - * Notifies all bound listeners of an authentication failure event. - * - * @param event - * The AuthenticationSuccessEvent describing the authentication failure - * that just occurred. - * - * @throws GuacamoleException - * If any bound listener throws this exception. If a listener throws an exception - * some listeners may not receive the authentication failure event notification. - */ - @Override - public void authenticationFailed(AuthenticationFailureEvent event) - throws GuacamoleException { - for (AuthenticationFailureListener listener : listeners) { - listener.authenticationFailed(event); - } - } - - /** - * Notifies all bound listeners of an tunnel connected event. Listeners - * are allowed to veto a tunnel connection by returning false from the - * listener method. Regardless of whether a particular listener rejects the - * tunnel connection, all listeners are notified. - * @param event - * The TunnelConnectedEvent describing the tunnel that was just connected - * - * @return - * false if any bound listener returns false, else true - * - * @throws GuacamoleException - * If any bound listener throws this exception. If a listener throws an exception - * some listeners may not receive the tunnel connected event notification. - */ - @Override - public boolean tunnelConnected(TunnelConnectEvent event) - throws GuacamoleException { - boolean result = true; - for (TunnelConnectListener listener : listeners) { - result = result && listener.tunnelConnected(event); - } - return result; - } - - /** - * Notifies all bound listeners of an tunnel close event. Listeners - * are allowed to veto the request to close a tunnel by returning false from - * the listener method. Regardless of whether a particular listener rejects the - * tunnel close request, all listeners are notified. - * @param event - * The TunnelCloseEvent describing the tunnel that is to be closed - * - * @return - * false if any bound listener returns false, else true - * - * @throws GuacamoleException - * If any bound listener throws this exception. If a listener throws an exception - * some listeners may not receive the tunnel close event notification. - */ - @Override - public boolean tunnelClosed(TunnelCloseEvent event) throws GuacamoleException { - boolean result = true; - for (TunnelCloseListener listener : listeners) { - result = result && listener.tunnelClosed(event); - } - return result; } } diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java index 56cf82a59..d3543d748 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java @@ -25,8 +25,6 @@ import java.util.List; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleSession; -import org.apache.guacamole.GuacamoleTunnelConnectedException; -import org.apache.guacamole.GuacamoleTunnelRejectedException; import org.apache.guacamole.GuacamoleUnauthorizedException; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.auth.Connection; @@ -70,10 +68,8 @@ public class TunnelRequestService { private ListenerService listenerService; /** - * Notifies bound TunnelConnectListeners that a new tunnel has been connected. - * Listeners are allowed to veto a connected tunnel by returning false from the - * listener method. If the ListenerService indicates that any listener rejected - * the tunnel, the tunnel is closed an GuacamoleTunnelRejectedException is thrown. + * 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 @@ -88,25 +84,15 @@ public class TunnelRequestService { * @throws GuacamoleException * If thrown by a listener or if any listener vetoes the connected tunnel */ - private void notifyTunnelConnectListeners(UserContext userContext, + private void fireTunnelConnectEvent(UserContext userContext, Credentials credentials, GuacamoleTunnel tunnel) throws GuacamoleException { - TunnelConnectEvent event = new TunnelConnectEvent(userContext, credentials, tunnel); - if (!listenerService.tunnelConnected(event)) { - try { - tunnel.close(); - } - catch (GuacamoleException closeEx) { - logger.warn("Error closing rejected tunnel connection: {}", closeEx.getMessage()); - } - throw new GuacamoleTunnelRejectedException(); - } + listenerService.handleEvent(new TunnelConnectEvent(userContext, credentials, tunnel)); } /** - * Notifies bound TunnelCloseListeners that a tunnel is to be closed. - * Listeners are allowed to veto a request to close a tunnel by returning false from - * the listener method. If the ListenerService indicates that any listener vetoed the - * request to the close the tunnel, a GuacamoleTunnelConnectedException is thrown. + * 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 @@ -119,15 +105,12 @@ public class TunnelRequestService { * The tunnel that was connected * * @throws GuacamoleException - * If thrown by a listener or if any listener vetoes the request to close the tunnel + * If thrown by a listener. */ - private void notifyTunnelCloseListeners(UserContext userContext, + private void fireTunnelClosedEvent(UserContext userContext, Credentials credentials, GuacamoleTunnel tunnel) throws GuacamoleException { - TunnelCloseEvent event = new TunnelCloseEvent(userContext, credentials, tunnel); - if (listenerService.tunnelClosed(event)) { - throw new GuacamoleTunnelConnectedException(); - } + listenerService.handleEvent(new TunnelCloseEvent(userContext, credentials, tunnel)); } /** @@ -317,7 +300,7 @@ public class TunnelRequestService { public void close() throws GuacamoleException { // notify listeners to allow close request to be vetoed - notifyTunnelCloseListeners(context, + fireTunnelClosedEvent(context, session.getAuthenticatedUser().getCredentials(), tunnel); long connectionEndTime = System.currentTimeMillis(); @@ -406,7 +389,7 @@ public class TunnelRequestService { GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info); // Notify listeners to allow connection to be vetoed - notifyTunnelConnectListeners(userContext, + fireTunnelConnectEvent(userContext, session.getAuthenticatedUser().getCredentials(), tunnel); // Associate tunnel with session