diff --git a/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelConnectedException.java b/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelConnectedException.java new file mode 100644 index 000000000..96693a483 --- /dev/null +++ b/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelConnectedException.java @@ -0,0 +1,33 @@ +/* + * 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 new file mode 100644 index 000000000..c2e09f927 --- /dev/null +++ b/guacamole-common/src/main/java/org/apache/guacamole/GuacamoleTunnelRejectedException.java @@ -0,0 +1,33 @@ +/* + * 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/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java index 628386916..823ac1b9f 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java @@ -25,14 +25,20 @@ 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; import org.apache.guacamole.net.auth.ConnectionGroup; +import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.event.TunnelCloseEvent; +import org.apache.guacamole.net.event.TunnelConnectEvent; import org.apache.guacamole.rest.auth.AuthenticationService; import org.apache.guacamole.protocol.GuacamoleClientInformation; +import org.apache.guacamole.rest.event.ListenerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,6 +63,74 @@ public class TunnelRequestService { @Inject private AuthenticationService authenticationService; + /** + * A service for notifying listeners about tunnel connect/closed events. + */ + @Inject + 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. + * + * @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 notifyTunnelConnectListeners(UserContext userContext, + Credentials credentials, GuacamoleTunnel tunnel) throws GuacamoleException { + TunnelConnectEvent event = new TunnelConnectEvent(userContext, credentials, tunnel); + boolean ok = listenerService.tunnelConnected(event); + if (!ok) { + try { + tunnel.close(); + } + catch (GuacamoleException closeEx) { + logger.warn("Error closing rejected tunnel connection: {}", closeEx.getMessage()); + } + throw new GuacamoleTunnelRejectedException(); + } + } + + /** + * 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. + * + * @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 request to close the tunnel + */ + private void notifyTunnelCloseListeners(UserContext userContext, + Credentials credentials, GuacamoleTunnel tunnel) + throws GuacamoleException { + TunnelCloseEvent event = new TunnelCloseEvent(userContext, credentials, tunnel); + if (listenerService.tunnelClosed(event)) { + throw new GuacamoleTunnelConnectedException(); + } + } + /** * Reads and returns the client information provided within the given * request. @@ -226,7 +300,7 @@ public class TunnelRequestService { * @throws GuacamoleException * If an error occurs while obtaining the tunnel. */ - protected GuacamoleTunnel createAssociatedTunnel(GuacamoleTunnel tunnel, + protected GuacamoleTunnel createAssociatedTunnel(final GuacamoleTunnel tunnel, final String authToken, final GuacamoleSession session, final UserContext context, final TunnelRequest.Type type, final String id) throws GuacamoleException { @@ -243,6 +317,10 @@ public class TunnelRequestService { @Override public void close() throws GuacamoleException { + // notify listeners to allow close request to be vetoed + notifyTunnelCloseListeners(context, + session.getAuthenticatedUser().getCredentials(), tunnel); + long connectionEndTime = System.currentTimeMillis(); long duration = connectionEndTime - connectionStartTime; @@ -328,6 +406,10 @@ public class TunnelRequestService { // Create connected tunnel using provided connection ID and client information GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info); + // Notify listeners to allow connection to be vetoed + notifyTunnelConnectListeners(userContext, + session.getAuthenticatedUser().getCredentials(), tunnel); + // Associate tunnel with session return createAssociatedTunnel(tunnel, authToken, session, userContext, type, id);