diff --git a/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/status/InMemoryAuthenticationFailureTracker.java b/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/status/InMemoryAuthenticationFailureTracker.java index b655168e1..58ed4e014 100644 --- a/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/status/InMemoryAuthenticationFailureTracker.java +++ b/extensions/guacamole-auth-ban/src/main/java/org/apache/guacamole/auth/ban/status/InMemoryAuthenticationFailureTracker.java @@ -21,7 +21,6 @@ package org.apache.guacamole.auth.ban.status; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.language.TranslatableGuacamoleClientTooManyException; @@ -88,40 +87,6 @@ public class InMemoryAuthenticationFailureTracker implements AuthenticationFailu } - /** - * Returns whether the given Credentials do not contain any specific - * authentication parameters, including HTTP parameters. An authentication - * request that contains no parameters whatsoever will tend to be the - * first, anonymous, credential-less authentication attempt that results in - * the initial login screen rendering. - * - * @param credentials - * The Credentials object to test. - * - * @return - * true if the given Credentials contain no authentication parameters - * whatsoever, false otherwise. - */ - private boolean isEmpty(Credentials credentials) { - - // An authentication request that contains an explicit username or - // password (even if blank) is non-empty, regardless of how the values - // were passed - if (credentials.getUsername() != null || credentials.getPassword() != null) - return false; - - // All further tests depend on HTTP request details - HttpServletRequest request = credentials.getRequest(); - if (request == null) - return true; - - // An authentication request is non-empty if it contains any HTTP - // parameters at all or contains an authentication token - return !request.getParameterNames().hasMoreElements() - && request.getHeader("Guacamole-Token") == null; - - } - /** * Reports that the given address has just failed to authenticate and * returns the AuthenticationFailureStatus that represents that failure. If @@ -168,7 +133,7 @@ public class InMemoryAuthenticationFailureTracker implements AuthenticationFailu boolean failed) throws GuacamoleException { // Ignore requests that do not contain explicit parameters of any kind - if (isEmpty(credentials)) + if (credentials.isEmpty()) return; // Determine originating address of the authentication request diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connection.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connection.java index 3d8e64d64..24cf49c72 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connection.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connection.java @@ -34,20 +34,7 @@ import org.apache.guacamole.protocol.GuacamoleConfiguration; * backing GuacamoleConfiguration may be intentionally obfuscated or tokenized * to protect sensitive configuration information. */ -public interface Connection extends Identifiable, Connectable, Attributes { - - /** - * Returns the name assigned to this Connection. - * @return The name assigned to this Connection. - */ - public String getName(); - - /** - * Sets the name assigned to this Connection. - * - * @param name The name to assign. - */ - public void setName(String name); +public interface Connection extends Identifiable, Connectable, Attributes, Nameable { /** * Returns the unique identifier of the parent ConnectionGroup for diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionGroup.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionGroup.java index 74412de7d..a080afcc5 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionGroup.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ConnectionGroup.java @@ -26,7 +26,7 @@ import org.apache.guacamole.GuacamoleException; * Represents a connection group, which can contain both other connection groups * as well as connections. */ -public interface ConnectionGroup extends Identifiable, Connectable, Attributes { +public interface ConnectionGroup extends Identifiable, Connectable, Attributes, Nameable { /** * All legal types of connection group. @@ -51,19 +51,6 @@ public interface ConnectionGroup extends Identifiable, Connectable, Attributes { }; - /** - * Returns the name assigned to this ConnectionGroup. - * @return The name assigned to this ConnectionGroup. - */ - public String getName(); - - /** - * Sets the name assigned to this ConnectionGroup. - * - * @param name The name to assign. - */ - public void setName(String name); - /** * Returns the unique identifier of the parent ConnectionGroup for * this ConnectionGroup. diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Credentials.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Credentials.java index 40f991244..6ad0e240b 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Credentials.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Credentials.java @@ -236,4 +236,36 @@ public class Credentials implements Serializable { this.remoteHostname = remoteHostname; } + /** + * Returns whether this Credentials object does not contain any specific + * authentication parameters, including HTTP parameters and the HTTP header + * used for the authentication token. An authentication request that + * contains no parameters whatsoever will tend to be the first, anonymous, + * credential-less authentication attempt that results in the initial login + * screen rendering. + * + * @return + * true if this Credentials object contains no authentication + * parameters whatsoever, false otherwise. + */ + public boolean isEmpty() { + + // An authentication request that contains an explicit username or + // password (even if blank) is non-empty, regardless of how the values + // were passed + if (getUsername() != null || getPassword() != null) + return false; + + // All further tests depend on HTTP request details + HttpServletRequest httpRequest = getRequest(); + if (httpRequest == null) + return true; + + // An authentication request is non-empty if it contains any HTTP + // parameters at all or contains an authentication token + return !httpRequest.getParameterNames().hasMoreElements() + && httpRequest.getHeader("Guacamole-Token") == null; + + } + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Directory.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Directory.java index 287145fc0..ba1e1522d 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Directory.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Directory.java @@ -34,6 +34,104 @@ import org.apache.guacamole.GuacamoleException; */ public interface Directory { + /** + * All Directory types that may be found on the {@link UserContext} + * interface. + */ + public enum Type { + + /** + * The type of a Directory that contains {@link ActiveConnection} + * objects. + */ + ACTIVE_CONNECTION(ActiveConnection.class), + + /** + * The type of a Directory that contains {@link Connection} + * objects. + */ + CONNECTION(Connection.class), + + /** + * The type of a Directory that contains {@link ConnectionGroup} + * objects. + */ + CONNECTION_GROUP(ConnectionGroup.class), + + /** + * The type of a Directory that contains {@link SharingProfile} + * objects. + */ + SHARING_PROFILE(SharingProfile.class), + + /** + * The type of a Directory that contains {@link User} objects. + */ + USER(User.class), + + /** + * The type of a Directory that contains {@link UserGroup} + * objects. + */ + USER_GROUP(UserGroup.class); + + /** + * The base class of the type of object stored within the type of + * Directory represented by this Directory.Type. + */ + private final Class objectType; + + /** + * Creates a new Directory.Type representing the type of a Directory + * that contains only subclasses of the given class. + * + * @param objectType + * The base class of the type of object stored within the type of + * Directory represented by this Directory.Type. + */ + private Type(Class objectType) { + this.objectType = objectType; + } + + /** + * Returns the base class of the type of object stored within a + * {@link Directory} of this type. + * + * @return + * The base class of the type of object stored within a + * {@link Directory} of this type. + */ + public Class getObjectType() { + return objectType; + } + + /** + * Returns the Directory.Type representing the type of a Directory that + * could contain an object having the given class. The class may be a + * subclass of the overall base class of the objects stored within the + * Directory. + * + * @param objectType + * The class to determine the Directory.Type of. + * + * @return + * The Directory.Type representing the type of a Directory that + * could contain an object having the given class, or null if there + * is no such Directory available via the UserContext interface. + */ + public static Type of(Class objectType) { + + for (Type type : Type.values()) { + if (type.getObjectType().isAssignableFrom(objectType)) + return type; + } + + return null; + + } + + } + /** * Returns the object having the given identifier. Note that changes to * the object returned will not necessarily affect the object stored within diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Nameable.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Nameable.java new file mode 100644 index 000000000..d40206054 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Nameable.java @@ -0,0 +1,45 @@ +/* + * 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.auth; + +/** + * An object which has a human-readable, arbitrary name. No requirement is + * imposed by this interface regarding whether this name must be unique, + * however implementations are free to impose such restrictions. + */ +public interface Nameable { + + /** + * Returns the human-readable name assigned to this object. + * + * @return + * The name assigned to this object. + */ + String getName(); + + /** + * Sets the human-readable name assigned to this object. + * + * @param name + * The name to assign. + */ + void setName(String name); + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/SharingProfile.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/SharingProfile.java index 2d4c4326a..1fe75d912 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/SharingProfile.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/SharingProfile.java @@ -25,23 +25,7 @@ import java.util.Map; * Represents the semantics which apply to an existing connection when shared, * along with a human-readable name and unique identifier. */ -public interface SharingProfile extends Identifiable, Attributes { - - /** - * Returns the human-readable name assigned to this SharingProfile. - * - * @return - * The name assigned to this SharingProfile. - */ - public String getName(); - - /** - * Sets the human-readable name assigned to this SharingProfile. - * - * @param name - * The name to assign. - */ - public void setName(String name); +public interface SharingProfile extends Identifiable, Attributes, Nameable { /** * Returns the identifier of the primary connection associated with this diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationShutdownEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationShutdownEvent.java new file mode 100644 index 000000000..ba7f50bac --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationShutdownEvent.java @@ -0,0 +1,36 @@ +/* + * 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; + +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.event.listener.Listener; + +/** + * Event that is dispatched when the web application has nearly completely shut + * down, including the authentication/authorization portion of extensions. Any + * installed extensions are still loaded (such that they may receive this event + * via {@link Listener#handleEvent(java.lang.Object)}, but their authentication + * providers will have been shut down via {@link AuthenticationProvider#shutdown()}, + * and resources from user sessions will have been closed and released via + * {@link UserContext#invalidate()}. + */ +public interface ApplicationShutdownEvent { +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationStartedEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationStartedEvent.java new file mode 100644 index 000000000..cf2ae2f3b --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationStartedEvent.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * Event that is dispatched when the web application has finished starting up, + * including all extensions. This event indicates only that the web application + * startup process has completed and all extensions have been loaded. It does + * not indicate whether all extensions have started successfully. + */ +public interface ApplicationStartedEvent { +} 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 a9b21dce8..a7270968e 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 @@ -42,17 +42,47 @@ public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent, */ private final AuthenticatedUser authenticatedUser; + /** + * Whether the successful authentication attempt represented by this event + * is related to an established Guacamole session. + */ + private final boolean existingSession; + /** * Creates a new AuthenticationSuccessEvent which represents a successful * authentication attempt by the user identified by the given - * AuthenticatedUser object. + * AuthenticatedUser object. The authentication attempt is presumed to be + * a fresh authentication attempt unrelated to an established session (a + * login attempt). * * @param authenticatedUser * The AuthenticatedUser identifying the user that successfully * authenticated. */ public AuthenticationSuccessEvent(AuthenticatedUser authenticatedUser) { + this(authenticatedUser, false); + } + + /** + * Creates a new AuthenticationSuccessEvent which represents a successful + * authentication attempt by the user identified by the given + * AuthenticatedUser object. Whether the authentication attempt is + * related to an established session (a periodic re-authentication attempt + * that updates session status) or not (a fresh login attempt) is + * determined by the value of the provided flag. + * + * @param authenticatedUser + * The AuthenticatedUser identifying the user that successfully + * authenticated. + * + * @param existingSession + * Whether this AuthenticationSuccessEvent represents an + * re-authentication attempt that updates the status of an established + * Guacamole session. + */ + public AuthenticationSuccessEvent(AuthenticatedUser authenticatedUser, boolean existingSession) { this.authenticatedUser = authenticatedUser; + this.existingSession = existingSession; } @Override @@ -70,4 +100,21 @@ public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent, return getAuthenticatedUser().getAuthenticationProvider(); } + /** + * Returns whether the successful authentication attempt represented by + * this event is related to an established Guacamole session. During normal + * operation, the Guacamole web application will periodically + * re-authenticate with the server to verify its authentication token and + * update the session state, in which case the value returned by this + * function will be true. If the user was not already authenticated and has + * just initially logged in, false is returned. + * + * @return + * true if this AuthenticationSuccessEvent is related to a Guacamole + * session that was already established, false otherwise. + */ + public boolean isExistingSession() { + return existingSession; + } + } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/DirectoryEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/DirectoryEvent.java new file mode 100644 index 000000000..468851af1 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/DirectoryEvent.java @@ -0,0 +1,104 @@ +/* + * 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; + +import org.apache.guacamole.net.auth.Directory; +import org.apache.guacamole.net.auth.Identifiable; + +/** + * Abstract basis for events which involve a modification made to the objects + * within a {@link Directory} through the operations exposed by the Directory + * interface. + * + * @param + * The type of object stored within the {@link Directory}. + */ +public interface DirectoryEvent + extends IdentifiableObjectEvent { + + /** + * The types of directory operations that may be represented by a + * DirectoryEvent. + */ + public enum Operation { + + /** + * An object was added to the {@link Directory}. The object added can + * be accessed with {@link #getObject()}, and its identifier may be + * obtained from {@link #getObjectIdentifier()}. + */ + ADD, + + /** + * An object was retrieved from a {@link Directory}. The object + * retrieved can be accessed with {@link #getObject()}, and its + * identifier may be obtained from {@link #getObjectIdentifier()}. + */ + GET, + + /** + * An existing object within a {@link Directory} was modified. The + * modified object can be accessed with {@link #getObject()}, and its + * identifier may be obtained from {@link #getObjectIdentifier()}. + */ + UPDATE, + + /** + * An existing object within a {@link Directory} was deleted/removed. + * The identifier of the object that was deleted may be obtained from + * {@link #getObjectIdentifier()}. The full object that was deleted + * will be made available via {@link #getObject()} if possible, but + * this is not guaranteed for deletions. + */ + REMOVE + + } + + /** + * Returns the operation that was performed/attempted. + * + * @return + * The operation that was performed or attempted. + */ + Operation getOperation(); + + /** + * {@inheritDoc} + *

+ * If the object was just created, this will be the identifier of the new + * object. + */ + @Override + String getObjectIdentifier(); + + /** + * {@inheritDoc} + *

+ * Currently, for object creation ({@link Operation#ADD ADD}), retrieval + * ({@link Operation#GET GET}), and modification ({@link Operation#UPDATE UPDATE}), + * it can be expected that the affected object will be available, however + * the caller should verify this regardless. For deletions + * ({@link Operation#REMOVE REMOVE}), the object can only be made available for + * single deletions, and cannot be made available for batch deletions. + */ + @Override + ObjectType getObject(); + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/DirectoryFailureEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/DirectoryFailureEvent.java new file mode 100644 index 000000000..a9ec474d1 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/DirectoryFailureEvent.java @@ -0,0 +1,36 @@ +/* + * 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; + +import org.apache.guacamole.net.auth.Directory; +import org.apache.guacamole.net.auth.Identifiable; + +/** + * Event that is dispatched whenever a REST API request to create/modify/delete + * an object within a {@link Directory} fails. The specific failure is made + * available via {@link #getFailure()}. + * + * @param + * The type of object stored within the {@link Directory}. + */ +public interface DirectoryFailureEvent + extends DirectoryEvent, FailureEvent { + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/DirectorySuccessEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/DirectorySuccessEvent.java new file mode 100644 index 000000000..691cee7f7 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/DirectorySuccessEvent.java @@ -0,0 +1,35 @@ +/* + * 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; + +import org.apache.guacamole.net.auth.Directory; +import org.apache.guacamole.net.auth.Identifiable; + +/** + * Event that is dispatched whenever a REST API request to create/modify/delete + * an object within a {@link Directory} succeeds. + * + * @param + * The type of object stored within the {@link Directory}. + */ +public interface DirectorySuccessEvent + extends DirectoryEvent { + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/IdentifiableObjectEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/IdentifiableObjectEvent.java new file mode 100644 index 000000000..69ecfeaa4 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/IdentifiableObjectEvent.java @@ -0,0 +1,80 @@ +/* + * 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; + +import org.apache.guacamole.net.auth.AuthenticatedUser; +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.Directory; +import org.apache.guacamole.net.auth.Identifiable; + +/** + * Abstract basis for events which affect or directly relate to objects that + * are {@link Identifiable} and may be stored within a {@link Directory}. + * + * @param + * The type of object affected or related to the event. + */ +public interface IdentifiableObjectEvent + extends AuthenticationProviderEvent, UserEvent { + + /** + * {@inheritDoc} + *

+ * NOTE: For subclasses of {@link IdentifiableObjectEvent}, this will be the + * AuthenticationProvider associated with the affected, {@link Identifiable} + * object. This is not necessarily the same as the AuthenticationProvider + * that authenticated the user performing the operation, which can be + * retrieved via {@link #getAuthenticatedUser()} and + * {@link AuthenticatedUser#getAuthenticationProvider()}. + */ + @Override + AuthenticationProvider getAuthenticationProvider(); + + /** + * Returns the type of {@link Directory} that may contains the object + * affected by the operation. + * + * @return + * The type of objects stored within the {@link Directory}. + */ + Directory.Type getDirectoryType(); + + /** + * Returns the identifier of the object affected by the operation. + * + * @return + * The identifier of the object affected by the operation. + */ + String getObjectIdentifier(); + + /** + * Returns the object affected by the operation, if available. Whether the + * affected object is available is context- and implementation-dependent. + * There is no general guarantee across all implementations of this event + * that the affected object will be available. If the object is not + * available, null is returned. + * + * @return + * The object affected by the operation performed, or null if that + * object is not available in the context of this event. + */ + ObjectType getObject(); + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/UserSessionInvalidatedEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/UserSessionInvalidatedEvent.java new file mode 100644 index 000000000..4398cfa89 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/UserSessionInvalidatedEvent.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * Event that is dispatched when a user has logged out or their session has + * expired. + */ +public interface UserSessionInvalidatedEvent extends UserEvent { +} diff --git a/guacamole-ext/src/test/java/org/apache/guacamole/net/auth/DirectoryTest.java b/guacamole-ext/src/test/java/org/apache/guacamole/net/auth/DirectoryTest.java new file mode 100644 index 000000000..04623aa58 --- /dev/null +++ b/guacamole-ext/src/test/java/org/apache/guacamole/net/auth/DirectoryTest.java @@ -0,0 +1,124 @@ +/* + * 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.auth; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test that verifies the functionality provided by the Directory interface. + */ +public class DirectoryTest { + + /** + * Returns a Collection of all classes that have associated Directories + * available via the UserContext interface. The classes are retrieved + * using reflection by enumerating the type parameters of the return types + * of all functions that return a Directory. + * + * @return + * A Collection of all classes that have associated Directories + * available via the UserContext interface. + */ + @SuppressWarnings("unchecked") // Verified via calls to isAssignableFrom() + private Collection> getDirectoryTypes() { + + Set> types = new HashSet<>(); + + Method[] methods = UserContext.class.getMethods(); + for (Method method : methods) { + + if (!Directory.class.isAssignableFrom(method.getReturnType())) + continue; + + Type retType = method.getGenericReturnType(); + Assert.assertTrue("UserContext functions that return directories " + + "must have proper type parameters for the returned " + + "directory.", retType instanceof ParameterizedType); + + Type[] typeArgs = ((ParameterizedType) retType).getActualTypeArguments(); + Assert.assertEquals("UserContext functions that return directories " + + "must properly declare exactly one type argument for " + + "those directories.", 1, typeArgs.length); + + Class directoryType = (Class) typeArgs[0]; + Assert.assertTrue("Directories returned by UserContext functions " + + "must contain subclasses of Identifiable.", + Identifiable.class.isAssignableFrom(directoryType)); + + types.add((Class) directoryType); + + } + + return Collections.unmodifiableSet(types); + + } + + /** + * Verifies that Directory.Type covers the types of all directories exposed + * by the UserContext interface. + */ + @Test + public void testTypeCoverage() { + + Collection> types = getDirectoryTypes(); + + Assert.assertEquals("Directory.Type must provide exactly one value " + + "for each type of directory provideed by the UserContext " + + "interface.", types.size(), Directory.Type.values().length); + + for (Class type : types) { + + Directory.Type dirType = Directory.Type.of(type); + Assert.assertNotNull("of() must provide mappings for all directory " + + "types defined on the UserContext interface.", dirType); + + Assert.assertEquals("getObjectType() must return the same base " + + "superclass used by UserContext for all directory " + + "types defined on the UserContext interface.", type, + dirType.getObjectType()); + + } + + } + + /** + * Verifies that each type declared by Directory.Type exposes an + * associated class via getObjectType() which then maps back to the same + * type via Directory.Type.of(). + */ + @Test + public void testTypeIdentity() { + for (Directory.Type dirType : Directory.Type.values()) { + Assert.assertEquals("For all defined directory types, " + + "Directory.Type.of(theType.getObjectType()) must " + + "correctly map back to theType.", dirType, + Directory.Type.of(dirType.getObjectType())); + } + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java index 2b3f155d0..1068477c4 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java @@ -35,11 +35,14 @@ import org.apache.guacamole.environment.LocalEnvironment; import org.apache.guacamole.extension.ExtensionModule; import org.apache.guacamole.log.LogModule; import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.event.ApplicationShutdownEvent; +import org.apache.guacamole.net.event.ApplicationStartedEvent; import org.apache.guacamole.properties.BooleanGuacamoleProperty; import org.apache.guacamole.properties.FileGuacamoleProperties; import org.apache.guacamole.rest.RESTServiceModule; import org.apache.guacamole.rest.auth.HashTokenSessionMap; import org.apache.guacamole.rest.auth.TokenSessionMap; +import org.apache.guacamole.rest.event.ListenerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -125,7 +128,13 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener */ @Inject private List temporaryFiles; - + + /** + * Service for dispatching events to registered listeners. + */ + @Inject + private ListenerService listenerService; + /** * Internal reference to the Guice injector that was lazily created when * getInjector() was first invoked. @@ -179,6 +188,17 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener // Store reference to injector for use by Jersey and HK2 bridge servletContextEvent.getServletContext().setAttribute(GUICE_INJECTOR, injector); + // Inform any listeners that application startup has completed + try { + listenerService.handleEvent(new ApplicationStartedEvent() { + // The application startup event currently has no content + }); + } + catch (GuacamoleException e) { + logger.error("An extension listening for application startup failed: {}", e.getMessage()); + logger.debug("Extension failed internally while handling the application startup event.", e); + } + } @Override @@ -228,19 +248,36 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener // Clean up reference to Guice injector servletContextEvent.getServletContext().removeAttribute(GUICE_INJECTOR); - // Shutdown TokenSessionMap + // Shutdown TokenSessionMap, invalidating all sessions (logging all + // users out) if (sessionMap != null) sessionMap.shutdown(); - // Unload all extensions + // Unload authentication for all extensions if (authProviders != null) { for (AuthenticationProvider authProvider : authProviders) authProvider.shutdown(); } + // Inform any listeners that application shutdown has completed + try { + listenerService.handleEvent(new ApplicationShutdownEvent() { + // The application shutdown event currently has no content + }); + } + catch (GuacamoleException e) { + logger.error("An extension listening for application shutdown failed: {}", e.getMessage()); + logger.debug("Extension failed internally while handling the application shutdown event.", e); + } + } finally { + // NOTE: This temporary file cleanup must happen AFTER firing the + // ApplicationShutdownEvent, or an extension that relies on a .jar + // file among those temporary files might fail internally when + // attempting to process the event. + // Regardless of what may succeed/fail here, always attempt to // clean up ALL temporary files if (temporaryFiles != null) diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java index 24ea196c5..3e8d488c3 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java @@ -23,12 +23,13 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.event.UserSessionInvalidatedEvent; import org.apache.guacamole.rest.auth.DecoratedUserContext; +import org.apache.guacamole.rest.event.ListenerService; import org.apache.guacamole.tunnel.UserTunnel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,8 +59,12 @@ public class GuacamoleSession { /** * All currently-active tunnels, indexed by tunnel UUID. */ - private final Map tunnels = - new ConcurrentHashMap(); + private final Map tunnels = new ConcurrentHashMap<>(); + + /** + * Service for dispatching events to registered event listeners. + */ + private final ListenerService listenerService; /** * The last time this session was accessed. @@ -70,9 +75,9 @@ public class GuacamoleSession { * Creates a new Guacamole session associated with the given * AuthenticatedUser and UserContexts. * - * @param environment - * The environment of the Guacamole server associated with this new - * session. + * @param listenerService + * The service to use to notify registered event listeners when this + * session is invalidated. * * @param authenticatedUser * The authenticated user to associate this session with. @@ -83,11 +88,12 @@ public class GuacamoleSession { * @throws GuacamoleException * If an error prevents the session from being created. */ - public GuacamoleSession(Environment environment, + public GuacamoleSession(ListenerService listenerService, AuthenticatedUser authenticatedUser, List userContexts) throws GuacamoleException { this.lastAccessedTime = System.currentTimeMillis(); + this.listenerService = listenerService; this.authenticatedUser = authenticatedUser; this.userContexts = userContexts; } @@ -260,6 +266,23 @@ public class GuacamoleSession { // Invalidate the authenticated user object authenticatedUser.invalidate(); + // Advise any registered listeners that the user's session is now + // invalidated + try { + listenerService.handleEvent(new UserSessionInvalidatedEvent() { + + @Override + public AuthenticatedUser getAuthenticatedUser() { + return authenticatedUser; + } + + }); + } + catch (GuacamoleException e) { + logger.error("An extension listening for session invalidation failed: {}", e.getMessage()); + logger.debug("Extension failed internally while handling the session invalidation event.", e); + } + } } diff --git a/guacamole/src/main/java/org/apache/guacamole/event/AffectedObject.java b/guacamole/src/main/java/org/apache/guacamole/event/AffectedObject.java new file mode 100644 index 000000000..c08b4ed03 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/event/AffectedObject.java @@ -0,0 +1,129 @@ +/* + * 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.event; + +import org.apache.guacamole.net.auth.Nameable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.guacamole.net.event.IdentifiableObjectEvent; + +/** + * Loggable representation of the object affected by an operation. + */ +public class AffectedObject implements LoggableDetail { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(AffectedObject.class); + + /** + * The event representing the requested operation. + */ + private final IdentifiableObjectEvent event; + + /** + * Creates a new AffectedObject representing the object affected by the + * operation described by the given event. + * + * @param event + * The event representing the operation. + */ + public AffectedObject(IdentifiableObjectEvent event) { + this.event = event; + } + + @Override + public String toString() { + + Object object = event.getObject(); + String identifier = event.getObjectIdentifier(); + String dataSource = event.getAuthenticationProvider().getIdentifier(); + + String objectType; + String name = null; // Not all objects have names + + // Obtain name of object (if applicable and available) + if (object instanceof Nameable) { + try { + name = ((Nameable) object).getName(); + } + catch (RuntimeException | Error e) { + logger.debug("Name of object \"{}\" could not be retrieved.", identifier, e); + } + } + + // Determine type of object + switch (event.getDirectoryType()) { + + // Active connections + case ACTIVE_CONNECTION: + objectType = "active connection"; + break; + + // Connections + case CONNECTION: + objectType = "connection"; + break; + + // Connection groups + case CONNECTION_GROUP: + objectType = "connection group"; + break; + + // Sharing profiles + case SHARING_PROFILE: + objectType = "sharing profile"; + break; + + // Users + case USER: + + if (identifier != null && identifier.equals(event.getAuthenticatedUser().getIdentifier())) + return "their own user account within \"" + dataSource + "\""; + + objectType = "user"; + break; + + // User groups + case USER_GROUP: + objectType = "user group"; + break; + + // Unknown + default: + objectType = (object != null) ? object.getClass().toString() : "an unknown object"; + + } + + // Describe at least the type of the object and its identifier, + // including the name of the object, as well, if available + if (identifier != null) { + if (name != null) + return objectType + " \"" + identifier + "\" within \"" + dataSource + "\" (currently named \"" + name + "\")"; + else + return objectType + " \"" + identifier + "\" within \"" + dataSource + "\""; + } + else + return objectType + " within \"" + dataSource + "\""; + + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java b/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java new file mode 100644 index 000000000..16bee6ae8 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java @@ -0,0 +1,248 @@ +/* + * 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.event; + +import javax.annotation.Nonnull; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleResourceNotFoundException; +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.Credentials; +import org.apache.guacamole.net.auth.Identifiable; +import org.apache.guacamole.net.auth.User; +import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; +import org.apache.guacamole.net.event.ApplicationShutdownEvent; +import org.apache.guacamole.net.event.ApplicationStartedEvent; +import org.apache.guacamole.net.event.AuthenticationFailureEvent; +import org.apache.guacamole.net.event.AuthenticationRequestReceivedEvent; +import org.apache.guacamole.net.event.AuthenticationSuccessEvent; +import org.apache.guacamole.net.event.DirectoryEvent; +import org.apache.guacamole.net.event.DirectoryFailureEvent; +import org.apache.guacamole.net.event.DirectorySuccessEvent; +import org.apache.guacamole.net.event.IdentifiableObjectEvent; +import org.apache.guacamole.net.event.UserSessionInvalidatedEvent; +import org.apache.guacamole.net.event.listener.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Listener that records each event that occurs in the logs, such as changes + * made to objects via the REST API. + */ +public class EventLoggingListener implements Listener { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(EventLoggingListener.class); + + /** + * Returns whether the given event affects the password of a User object. + * + * @param event + * The event to check. + * + * @return + * true if a user's password is specifically set or modified by the + * given event, false otherwise. + */ + private boolean isPasswordAffected(IdentifiableObjectEvent event) { + + Identifiable object = event.getObject(); + if (!(object instanceof User)) + return false; + + return ((User) object).getPassword() != null; + + } + + /** + * Logs that an operation was performed on an object within a Directory + * successfully. + * + * @param event + * The event describing the operation successfully performed on the + * object. + */ + private void logSuccess(DirectorySuccessEvent event) { + DirectoryEvent.Operation op = event.getOperation(); + switch (op) { + + case GET: + logger.debug("{} successfully accessed/retrieved {}", new RequestingUser(event), new AffectedObject(event)); + break; + + case ADD: + if (isPasswordAffected(event)) + logger.info("{} successfully created {}, setting their password", new RequestingUser(event), new AffectedObject(event)); + else + logger.info("{} successfully created {}", new RequestingUser(event), new AffectedObject(event)); + break; + + case UPDATE: + if (isPasswordAffected(event)) + logger.info("{} successfully updated {}, changing their password", new RequestingUser(event), new AffectedObject(event)); + else + logger.info("{} successfully updated {}", new RequestingUser(event), new AffectedObject(event)); + break; + + case REMOVE: + logger.info("{} successfully deleted {}", new RequestingUser(event), new AffectedObject(event)); + break; + + default: + logger.warn("DirectoryEvent operation type has no corresponding log message implemented: {}", op); + logger.info("{} successfully performed an unknown action on {} {}", new RequestingUser(event), new AffectedObject(event)); + + } + } + + /** + * Logs that an operation failed to be performed on an object within a + * Directory. + * + * @param event + * The event describing the operation that failed. + */ + private void logFailure(DirectoryFailureEvent event) { + DirectoryEvent.Operation op = event.getOperation(); + switch (op) { + + case GET: + if (event.getFailure() instanceof GuacamoleResourceNotFoundException) + logger.debug("{} failed to access/retrieve {}: {}", new RequestingUser(event), new AffectedObject(event), new Failure(event)); + else + logger.info("{} failed to access/retrieve {}: {}", new RequestingUser(event), new AffectedObject(event), new Failure(event)); + break; + + case ADD: + logger.info("{} failed to create {}: {}", new RequestingUser(event), new AffectedObject(event), new Failure(event)); + break; + + case UPDATE: + logger.info("{} failed to update {}: {}", new RequestingUser(event), new AffectedObject(event), new Failure(event)); + break; + + case REMOVE: + logger.info("{} failed to delete {}: {}", new RequestingUser(event), new AffectedObject(event), new Failure(event)); + break; + + default: + logger.warn("DirectoryEvent operation type has no corresponding log message implemented: {}", op); + logger.info("{} failed to perform an unknown action on {}: {}", new RequestingUser(event), new AffectedObject(event), new Failure(event)); + + } + } + + /** + * Logs that authentication succeeded for a user. + * + * @param event + * The event describing the authentication attempt that succeeded. + */ + private void logSuccess(AuthenticationSuccessEvent event) { + if (!event.isExistingSession()) + logger.info("{} successfully authenticated from {}", + new RequestingUser(event), + new RemoteAddress(event.getCredentials())); + else + logger.debug("{} successfully re-authenticated their existing " + + "session from {}", new RequestingUser(event), + new RemoteAddress(event.getCredentials())); + } + + /** + * Logs that authentication failed for a user. + * + * @param event + * The event describing the authentication attempt that failed. + */ + private void logFailure(AuthenticationFailureEvent event) { + + AuthenticationProvider authProvider = event.getAuthenticationProvider(); + + Credentials creds = event.getCredentials(); + String username = creds.getUsername(); + + if (creds.isEmpty()) + logger.debug("Empty authentication attempt (login screen " + + "initialization) from {} failed: {}", + new RemoteAddress(creds), new Failure(event)); + else if (username == null || username.isEmpty()) + logger.debug("Anonymous authentication attempt from {} failed: {}", + new RemoteAddress(creds), new Failure(event)); + else if (event.getFailure() instanceof GuacamoleInsufficientCredentialsException) { + if (authProvider != null) + logger.debug("Authentication attempt from {} for user \"{}\" " + + "requires additional credentials to continue: {} " + + "(requested by \"{}\")", new RemoteAddress(creds), + username, new Failure(event), authProvider.getIdentifier()); + else + logger.debug("Authentication attempt from {} for user \"{}\" " + + "requires additional credentials to continue: {}", + new RemoteAddress(creds), username, new Failure(event)); + } + else { + if (authProvider != null) + logger.warn("Authentication attempt from {} for user \"{}\" " + + "failed: {} (rejected by \"{}\")", new RemoteAddress(creds), + username, new Failure(event), authProvider.getIdentifier()); + else + logger.warn("Authentication attempt from {} for user \"{}\" " + + "failed: {}", new RemoteAddress(creds), username, + new Failure(event)); + } + + } + + @Override + public void handleEvent(@Nonnull Object event) throws GuacamoleException { + + // General object creation/modification/deletion + if (event instanceof DirectorySuccessEvent) + logSuccess((DirectorySuccessEvent) event); + else if (event instanceof DirectoryFailureEvent) + logFailure((DirectoryFailureEvent) event); + + // Login / logout / session expiration + else if (event instanceof AuthenticationSuccessEvent) + logSuccess((AuthenticationSuccessEvent) event); + else if (event instanceof AuthenticationFailureEvent) + logFailure((AuthenticationFailureEvent) event); + else if (event instanceof UserSessionInvalidatedEvent) + logger.info("{} has logged out, or their session has expired or " + + "been terminated.", new RequestingUser((UserSessionInvalidatedEvent) event)); + else if (event instanceof AuthenticationRequestReceivedEvent) + logger.trace("Authentication request received from {}", + new RemoteAddress(((AuthenticationRequestReceivedEvent) event).getCredentials())); + + // Application startup/shutdown + else if (event instanceof ApplicationStartedEvent) + logger.info("The Apache Guacamole web application has started."); + else if (event instanceof ApplicationShutdownEvent) + logger.info("The Apache Guacamole web application has shut down."); + + // Unknown events + else + logger.debug("Ignoring unknown/unimplemented event type: {}", + event.getClass()); + + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/event/Failure.java b/guacamole/src/main/java/org/apache/guacamole/event/Failure.java new file mode 100644 index 000000000..10b3c960b --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/event/Failure.java @@ -0,0 +1,56 @@ +/* + * 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.event; + +import org.apache.guacamole.net.event.FailureEvent; + +/** + * Loggable representation of a failure that occurred. + */ +public class Failure implements LoggableDetail { + + /** + * The event representing the failure. + */ + private final FailureEvent event; + + /** + * Creates a new Failure representing the failure described by the given + * event. + * + * @param event + * The event representing the failure. + */ + public Failure(FailureEvent event) { + this.event = event; + } + + @Override + public String toString() { + + Throwable failure = event.getFailure(); + if (failure == null) + return "unknown error (no specific failure recorded)"; + + return failure.getMessage(); + + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/event/LoggableDetail.java b/guacamole/src/main/java/org/apache/guacamole/event/LoggableDetail.java new file mode 100644 index 000000000..909412e40 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/event/LoggableDetail.java @@ -0,0 +1,39 @@ +/* + * 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.event; + +/** + * Provides a {@link #toString()} implementation that returns a human-readable + * string that is intended to be logged and which describes a particular detail + * of an event. + */ +public interface LoggableDetail { + + /** + * {@inheritDoc} + *

+ * A LoggableDetail implementation of toString() is required to return a + * string that is human-readable, describes a detail of a provided event, + * and that is intended to be logged. + */ + @Override + String toString(); + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/event/RemoteAddress.java b/guacamole/src/main/java/org/apache/guacamole/event/RemoteAddress.java new file mode 100644 index 000000000..f55602f59 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/event/RemoteAddress.java @@ -0,0 +1,95 @@ +/* + * 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.event; + +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; +import org.apache.guacamole.net.auth.Credentials; + +/** + * Loggable representation of the remote address of a user, including any + * intervening proxies noted by "X-Forwarded-For". This representation takes + * into account the fact that "X-Forwarded-For" may come from an untrusted + * source, logging such addresses within square brackets alongside the trusted + * source IP. + */ +public class RemoteAddress implements LoggableDetail { + + /** + * Regular expression which matches any IPv4 address. + */ + private static final String IPV4_ADDRESS_REGEX = "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"; + + /** + * Regular expression which matches any IPv6 address. + */ + private static final String IPV6_ADDRESS_REGEX = "([0-9a-fA-F]*(:[0-9a-fA-F]*){0,7})"; + + /** + * Regular expression which matches any IP address, regardless of version. + */ + private static final String IP_ADDRESS_REGEX = "(" + IPV4_ADDRESS_REGEX + "|" + IPV6_ADDRESS_REGEX + ")"; + + /** + * Regular expression which matches any Port Number. + */ + private static final String PORT_NUMBER_REGEX = "(:[0-9]{1,5})?"; + + /** + * Pattern which matches valid values of the de-facto standard + * "X-Forwarded-For" header. + */ + private static final Pattern X_FORWARDED_FOR = Pattern.compile("^" + IP_ADDRESS_REGEX + PORT_NUMBER_REGEX + "(, " + IP_ADDRESS_REGEX + PORT_NUMBER_REGEX + ")*$"); + + /** + * The credentials supplied by the user when they authenticated. + */ + private final Credentials creds; + + /** + * Creates a new RemoteAddress representing the source address of the HTTP + * request that provided the given Credentials. + * + * @param creds + * The Credentials associated with the request whose source address + * should be represented by this RemoteAddress. + */ + public RemoteAddress(Credentials creds) { + this.creds = creds; + } + + @Override + public String toString() { + + HttpServletRequest request = creds.getRequest(); + if (request == null) + return creds.getRemoteAddress(); + + // Log X-Forwarded-For, if present and valid + String header = request.getHeader("X-Forwarded-For"); + if (header != null && X_FORWARDED_FOR.matcher(header).matches()) + return "[" + header + ", " + request.getRemoteAddr() + "]"; + + // If header absent or invalid, just use source IP + return request.getRemoteAddr(); + + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/event/RequestingUser.java b/guacamole/src/main/java/org/apache/guacamole/event/RequestingUser.java new file mode 100644 index 000000000..bcc14f4c4 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/event/RequestingUser.java @@ -0,0 +1,59 @@ +/* + * 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.event; + +import org.apache.guacamole.net.auth.AuthenticatedUser; +import org.apache.guacamole.net.event.UserEvent; + +/** + * Loggable representation of the user that requested an operation. + */ +public class RequestingUser implements LoggableDetail { + + /** + * The event representing the requested operation. + */ + private final UserEvent event; + + /** + * Creates a new RequestingUser that represents the user that requested the + * operation described by the given event. + * + * @param event + * The event representing the requested operation. + */ + public RequestingUser(UserEvent event) { + this.event = event; + } + + @Override + public String toString() { + + AuthenticatedUser user = event.getAuthenticatedUser(); + String identifier = user.getIdentifier(); + + if (AuthenticatedUser.ANONYMOUS_IDENTIFIER.equals(identifier)) + return "Anonymous user (authenticated by \"" + user.getAuthenticationProvider().getIdentifier() + "\")"; + + return "User \"" + identifier + "\" (authenticated by \"" + user.getAuthenticationProvider().getIdentifier() + "\")"; + + } + +} 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 0fcff57dd..e5fbe2e57 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java @@ -35,6 +35,7 @@ import org.apache.guacamole.auth.file.FileAuthenticationProvider; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.event.EventLoggingListener; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.event.listener.Listener; import org.apache.guacamole.properties.StringSetProperty; @@ -628,8 +629,9 @@ public class ExtensionModule extends ServletModule { final Set toleratedAuthProviders = getToleratedAuthenticationProviders(); loadExtensions(javaScriptResources, cssResources, toleratedAuthProviders); - // Always bind default file-driven auth last + // Always bind default file-driven auth and event logging last bindAuthenticationProvider(FileAuthenticationProvider.class, toleratedAuthProviders); + bindListener(EventLoggingListener.class); // Dynamically generate app.js and app.css from extensions serve("/app.js").with(new ResourceServlet(new SequenceResource(javaScriptResources))); diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/ActiveConnectionDirectoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/ActiveConnectionDirectoryResource.java index 5296565ab..88319f01c 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/ActiveConnectionDirectoryResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/ActiveConnectionDirectoryResource.java @@ -26,6 +26,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.net.auth.ActiveConnection; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.Permissions; import org.apache.guacamole.net.auth.UserContext; @@ -48,6 +49,9 @@ public class ActiveConnectionDirectoryResource * operations and subresources available for the given ActiveConnection * Directory. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * @@ -63,11 +67,13 @@ public class ActiveConnectionDirectoryResource * representing ActiveConnections. */ @AssistedInject - public ActiveConnectionDirectoryResource(@Assisted UserContext userContext, + public ActiveConnectionDirectoryResource( + @Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext, @Assisted Directory directory, DirectoryObjectTranslator translator, DirectoryObjectResourceFactory resourceFactory) { - super(userContext, directory, translator, resourceFactory); + super(authenticatedUser, userContext, ActiveConnection.class, directory, translator, resourceFactory); } @Override diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/ActiveConnectionResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/ActiveConnectionResource.java index 514daa124..59afe18d1 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/ActiveConnectionResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/ActiveConnectionResource.java @@ -30,6 +30,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.net.auth.ActiveConnection; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.UserContext; @@ -47,17 +48,6 @@ import org.apache.guacamole.rest.directory.DirectoryResourceFactory; public class ActiveConnectionResource extends DirectoryObjectResource { - /** - * The UserContext associated with the Directory which contains the - * Connection exposed by this resource. - */ - private final UserContext userContext; - - /** - * The ActiveConnection exposed by this ActiveConnectionResource. - */ - private final ActiveConnection activeConnection; - /** * A factory which can be used to create instances of resources representing * Connection. @@ -70,6 +60,9 @@ public class ActiveConnectionResource * Creates a new ActiveConnectionResource which exposes the operations and * subresources available for the given ActiveConnection. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * @@ -85,13 +78,12 @@ public class ActiveConnectionResource * ActiveConnections. */ @AssistedInject - public ActiveConnectionResource(@Assisted UserContext userContext, + public ActiveConnectionResource(@Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext, @Assisted Directory directory, @Assisted ActiveConnection activeConnection, DirectoryObjectTranslator translator) { - super(userContext, directory, activeConnection, translator); - this.userContext = userContext; - this.activeConnection = activeConnection; + super(authenticatedUser, userContext, ActiveConnection.class, directory, activeConnection, translator); } /** @@ -109,9 +101,12 @@ public class ActiveConnectionResource public DirectoryObjectResource getConnection() throws GuacamoleException { + UserContext userContext = getUserContext(); + ActiveConnection activeConnection = getInternalObject(); + // Return the underlying connection as a resource return connectionDirectoryResourceFactory - .create(userContext, userContext.getConnectionDirectory()) + .create(getAuthenticatedUser(), userContext, userContext.getConnectionDirectory()) .getObjectResource(activeConnection.getConnectionIdentifier()); } @@ -137,7 +132,7 @@ public class ActiveConnectionResource throws GuacamoleException { // Generate and return sharing credentials for the active connection - return new APIUserCredentials(activeConnection.getSharingCredentials(sharingProfileIdentifier)); + return new APIUserCredentials(getInternalObject().getSharingCredentials(sharingProfileIdentifier)); } 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 705d7a856..305073de3 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 @@ -21,16 +21,13 @@ package org.apache.guacamole.rest.auth; import java.util.ArrayList; import java.util.List; -import java.util.regex.Pattern; import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.GuacamoleUnauthorizedException; import org.apache.guacamole.GuacamoleSession; -import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; @@ -56,12 +53,6 @@ public class AuthenticationService { */ private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class); - /** - * The Guacamole server environment. - */ - @Inject - private Environment environment; - /** * All configured authentication providers which can be used to * authenticate users or retrieve data associated with authenticated users. @@ -105,57 +96,6 @@ public class AuthenticationService { */ public static final String TOKEN_PARAMETER_NAME = "token"; - /** - * Regular expression which matches any IPv4 address. - */ - private static final String IPV4_ADDRESS_REGEX = "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"; - - /** - * Regular expression which matches any IPv6 address. - */ - private static final String IPV6_ADDRESS_REGEX = "([0-9a-fA-F]*(:[0-9a-fA-F]*){0,7})"; - - /** - * Regular expression which matches any IP address, regardless of version. - */ - private static final String IP_ADDRESS_REGEX = "(" + IPV4_ADDRESS_REGEX + "|" + IPV6_ADDRESS_REGEX + ")"; - - /** - * Regular expression which matches any Port Number. - */ - private static final String PORT_NUMBER_REGEX = "(:[0-9]{1,5})?"; - - /** - * Pattern which matches valid values of the de-facto standard - * "X-Forwarded-For" header. - */ - private static final Pattern X_FORWARDED_FOR = Pattern.compile("^" + IP_ADDRESS_REGEX + PORT_NUMBER_REGEX + "(, " + IP_ADDRESS_REGEX + PORT_NUMBER_REGEX + ")*$"); - - /** - * Returns a formatted string containing an IP address, or list of IP - * addresses, which represent the HTTP client and any involved proxies. As - * the headers used to determine proxies can easily be forged, this data is - * superficially validated to ensure that it at least looks like a list of - * IPs. - * - * @param request - * The HTTP request to format. - * - * @return - * A formatted string containing one or more IP addresses. - */ - private String getLoggableAddress(HttpServletRequest request) { - - // Log X-Forwarded-For, if present and valid - String header = request.getHeader("X-Forwarded-For"); - if (header != null && X_FORWARDED_FOR.matcher(header).matches()) - return "[" + header + ", " + request.getRemoteAddr() + "]"; - - // If header absent or invalid, just use source IP - return request.getRemoteAddr(); - - } - /** * Attempts authentication against all AuthenticationProviders, in order, * using the provided credentials. The first authentication failure takes @@ -443,13 +383,13 @@ public class AuthenticationService { // If no existing session, generate a new token/session pair else { authToken = authTokenGenerator.getToken(); - tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts)); - logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier()); + tokenSessionMap.put(authToken, new GuacamoleSession(listenerService, authenticatedUser, userContexts)); } // Report authentication success try { - listenerService.handleEvent(new AuthenticationSuccessEvent(authenticatedUser)); + listenerService.handleEvent(new AuthenticationSuccessEvent(authenticatedUser, + existingSession != null)); } catch (GuacamoleException e) { throw new GuacamoleAuthenticationProcessException("User " @@ -461,25 +401,9 @@ public class AuthenticationService { // Log and rethrow any authentication errors catch (GuacamoleAuthenticationProcessException e) { - // Get request and username for sake of logging - HttpServletRequest request = credentials.getRequest(); - String username = credentials.getUsername(); - listenerService.handleEvent(new AuthenticationFailureEvent(credentials, e.getAuthenticationProvider(), e.getCause())); - // Log authentication failures with associated usernames - if (username != null) { - if (logger.isWarnEnabled()) - logger.warn("Authentication attempt from {} for user \"{}\" failed.", - getLoggableAddress(request), username); - } - - // Log anonymous authentication failures - else if (logger.isDebugEnabled()) - logger.debug("Anonymous authentication attempt from {} failed.", - getLoggableAddress(request)); - // Rethrow exception e.rethrowCause(); @@ -497,11 +421,6 @@ public class AuthenticationService { } - if (logger.isInfoEnabled()) - logger.info("User \"{}\" successfully authenticated from {}.", - authenticatedUser.getIdentifier(), - getLoggableAddress(credentials.getRequest())); - return authToken; } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/HashTokenSessionMap.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/HashTokenSessionMap.java index a344ab911..24f653098 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/HashTokenSessionMap.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/HashTokenSessionMap.java @@ -225,7 +225,13 @@ public class HashTokenSessionMap implements TokenSessionMap { @Override public void shutdown() { + + // Terminate the automatic session invalidation thread executor.shutdownNow(); + + // Forcibly invalidate any remaining sessions + sessionMap.values().stream().forEach(GuacamoleSession::invalidate); + } } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionDirectoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionDirectoryResource.java index 88408a796..612e05502 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionDirectoryResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionDirectoryResource.java @@ -25,6 +25,7 @@ import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.Permissions; @@ -47,6 +48,9 @@ public class ConnectionDirectoryResource * Creates a new ConnectionDirectoryResource which exposes the operations * and subresources available for the given Connection Directory. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * @@ -62,11 +66,11 @@ public class ConnectionDirectoryResource * representing Connections. */ @AssistedInject - public ConnectionDirectoryResource(@Assisted UserContext userContext, - @Assisted Directory directory, + public ConnectionDirectoryResource(@Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext, @Assisted Directory directory, DirectoryObjectTranslator translator, DirectoryObjectResourceFactory resourceFactory) { - super(userContext, directory, translator, resourceFactory); + super(authenticatedUser, userContext, Connection.class, directory, translator, resourceFactory); } @Override diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionResource.java index 15586e187..484543ba2 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionResource.java @@ -32,6 +32,7 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleUnsupportedException; import org.apache.guacamole.net.auth.ActivityRecordSet; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.Permissions; @@ -65,17 +66,6 @@ public class ConnectionResource extends DirectoryObjectResource directory, @Assisted Connection connection, DirectoryObjectTranslator translator) { - super(userContext, directory, connection, translator); - this.userContext = userContext; - this.connection = connection; + super(authenticatedUser, userContext, Connection.class, directory, connection, translator); } /** @@ -126,8 +118,10 @@ public class ConnectionResource extends DirectoryObjectResource getConnectionParameters() throws GuacamoleException { + Connection connection = getInternalObject(); + // Pull effective permissions - Permissions effective = userContext.self().getEffectivePermissions(); + Permissions effective = getUserContext().self().getEffectivePermissions(); // Retrieve permission sets SystemPermissionSet systemPermissions = effective.getSystemPermissions(); @@ -162,6 +156,8 @@ public class ConnectionResource extends DirectoryObjectResource getSharingProfileDirectoryResource() throws GuacamoleException { + UserContext userContext = getUserContext(); + Connection connection = getInternalObject(); + // Produce subset of all SharingProfiles, containing only those which // are associated with this connection Directory sharingProfiles = new DirectoryView<>( @@ -209,7 +208,7 @@ public class ConnectionResource extends DirectoryObjectResource { - /** - * The UserContext associated with the Directory which contains the - * ConnectionGroup exposed by this resource. - */ - private final UserContext userContext; - - /** - * The Directory exposed by this resource. - */ - private final Directory directory; - - /** - * A factory which can be used to create instances of resources representing - * ConnectionGroups. - */ - private final DirectoryObjectResourceFactory resourceFactory; - /** * Creates a new ConnectionGroupDirectoryResource which exposes the * operations and subresources available for the given ConnectionGroup * Directory. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * @@ -81,23 +68,24 @@ public class ConnectionGroupDirectoryResource * representing ConnectionGroups. */ @AssistedInject - public ConnectionGroupDirectoryResource(@Assisted UserContext userContext, + public ConnectionGroupDirectoryResource( + @Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext, @Assisted Directory directory, DirectoryObjectTranslator translator, DirectoryObjectResourceFactory resourceFactory) { - super(userContext, directory, translator, resourceFactory); - this.userContext = userContext; - this.directory = directory; - this.resourceFactory = resourceFactory; + super(authenticatedUser, userContext, ConnectionGroup.class, directory, translator, resourceFactory); } @Override public DirectoryObjectResource getObjectResource(String identifier) throws GuacamoleException { + UserContext userContext = getUserContext(); + // Use root group if identifier is the standard root identifier if (identifier != null && identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER)) - return resourceFactory.create(userContext, directory, + return getResourceFactory().create(getAuthenticatedUser(), userContext, getDirectory(), userContext.getRootConnectionGroup()); return super.getObjectResource(identifier); diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupResource.java index f91b8ebb9..22f41b0cf 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupResource.java @@ -29,6 +29,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.UserContext; @@ -45,21 +46,13 @@ import org.apache.guacamole.rest.directory.DirectoryObjectTranslator; public class ConnectionGroupResource extends DirectoryObjectResource { - /** - * The UserContext associated with the Directory which contains the - * ConnectionGroup exposed by this resource. - */ - private final UserContext userContext; - - /** - * The ConnectionGroup object represented by this ConnectionGroupResource. - */ - private final ConnectionGroup connectionGroup; - /** * Creates a new ConnectionGroupResource which exposes the operations and * subresources available for the given ConnectionGroup. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * @@ -75,13 +68,13 @@ public class ConnectionGroupResource * object given. */ @AssistedInject - public ConnectionGroupResource(@Assisted UserContext userContext, + public ConnectionGroupResource( + @Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext, @Assisted Directory directory, @Assisted ConnectionGroup connectionGroup, DirectoryObjectTranslator translator) { - super(userContext, directory, connectionGroup, translator); - this.userContext = userContext; - this.connectionGroup = connectionGroup; + super(authenticatedUser, userContext, ConnectionGroup.class, directory, connectionGroup, translator); } /** @@ -107,8 +100,8 @@ public class ConnectionGroupResource throws GuacamoleException { // Retrieve the requested tree, filtering by the given permissions - ConnectionGroupTree tree = new ConnectionGroupTree(userContext, - connectionGroup, permissions); + ConnectionGroupTree tree = new ConnectionGroupTree(getUserContext(), + getInternalObject(), permissions); // Return tree as a connection group return tree.getRootAPIConnectionGroup(); diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectResource.java index 7b354a226..8ed36fec0 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectResource.java @@ -19,6 +19,7 @@ package org.apache.guacamole.rest.directory; +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -27,9 +28,15 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.AuthenticatedUser; +import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.Identifiable; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.event.DirectoryEvent; +import org.apache.guacamole.net.event.DirectoryFailureEvent; +import org.apache.guacamole.net.event.DirectorySuccessEvent; +import org.apache.guacamole.rest.event.ListenerService; /** * A REST resource which abstracts the operations available on an existing @@ -51,12 +58,22 @@ import org.apache.guacamole.net.auth.UserContext; @Consumes(MediaType.APPLICATION_JSON) public abstract class DirectoryObjectResource { + /** + * The user that is accessing this resource. + */ + private final AuthenticatedUser authenticatedUser; + /** * The UserContext associated with the Directory containing the object * represented by this DirectoryObjectResource. */ private final UserContext userContext; + /** + * The type of object represented by this DirectoryObjectResource. + */ + private final Class internalType; + /** * The Directory which contains the object represented by this * DirectoryObjectResource. @@ -74,13 +91,26 @@ public abstract class DirectoryObjectResource translator; + /** + * Service for dispatching events to registered listeners. + */ + @Inject + private ListenerService listenerService; + /** * Creates a new DirectoryObjectResource which exposes the operations * available for the given object. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * + * @param internalType + * The type of object that this DirectoryObjectResource should + * represent. + * * @param directory * The Directory which contains the given object. * @@ -91,15 +121,162 @@ public abstract class DirectoryObjectResource internalType, Directory directory, InternalType object, DirectoryObjectTranslator translator) { + this.authenticatedUser = authenticatedUser; this.userContext = userContext; this.directory = directory; + this.internalType = internalType; this.object = object; this.translator = translator; } + /** + * Notifies all registered listeners that the given operation has succeeded + * against the object represented by this resource. + * + * @param operation + * The operation that was performed. + * + * @throws GuacamoleException + * If a listener throws a GuacamoleException from its event handler. + */ + protected void fireDirectorySuccessEvent(DirectoryEvent.Operation operation) + throws GuacamoleException { + listenerService.handleEvent(new DirectorySuccessEvent() { + + @Override + public Directory.Type getDirectoryType() { + return Directory.Type.of(internalType); + } + + @Override + public DirectoryEvent.Operation getOperation() { + return operation; + } + + @Override + public String getObjectIdentifier() { + return object.getIdentifier(); + } + + @Override + public InternalType getObject() { + return object; + } + + @Override + public AuthenticatedUser getAuthenticatedUser() { + return authenticatedUser; + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return userContext.getAuthenticationProvider(); + } + + }); + } + + /** + * Notifies all registered listeners that the given operation has failed + * against the object represented by this resource. + * + * @param operation + * The operation that failed. + * + * @param failure + * The failure that occurred. + * + * @throws GuacamoleException + * If a listener throws a GuacamoleException from its event handler. + */ + protected void fireDirectoryFailureEvent(DirectoryEvent.Operation operation, + Throwable failure) throws GuacamoleException { + listenerService.handleEvent(new DirectoryFailureEvent() { + + @Override + public Directory.Type getDirectoryType() { + return Directory.Type.of(internalType); + } + + @Override + public DirectoryEvent.Operation getOperation() { + return operation; + } + + @Override + public String getObjectIdentifier() { + return object.getIdentifier(); + } + + @Override + public InternalType getObject() { + return object; + } + + @Override + public AuthenticatedUser getAuthenticatedUser() { + return authenticatedUser; + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return userContext.getAuthenticationProvider(); + } + + @Override + public Throwable getFailure() { + return failure; + } + + }); + } + + /** + * Returns the user accessing this resource. + * + * @return + * The user accessing this resource. + */ + protected AuthenticatedUser getAuthenticatedUser() { + return authenticatedUser; + } + + /** + * Returns the UserContext providing the Directory that contains the object + * exposed by this resource. + * + * @return + * The UserContext providing the Directory that contains the object + * exposed by this resource. + */ + protected UserContext getUserContext() { + return userContext; + } + + /** + * Returns the Directory containing the object exposed by this resource. + * + * @return + * The Directory containing the object exposed by this resource. + */ + protected Directory getDirectory() { + return directory; + } + + /** + * Returns the object exposed by this resource. + * + * @return + * The object exposed by this resource. + */ + protected InternalType getInternalObject() { + return object; + } + /** * Returns the object represented by this DirectoryObjectResource, in a * format intended for interchange. @@ -138,7 +315,15 @@ public abstract class DirectoryObjectResource - create(UserContext userContext, Directory directory, - InternalType object); + create(AuthenticatedUser authenticatedUser, UserContext userContext, + Directory directory, InternalType object); } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java index e9b4b225b..50d428528 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PATCH; @@ -36,6 +37,8 @@ import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleResourceNotFoundException; import org.apache.guacamole.GuacamoleUnsupportedException; +import org.apache.guacamole.net.auth.AuthenticatedUser; +import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.Identifiable; import org.apache.guacamole.net.auth.Permissions; @@ -44,7 +47,11 @@ import org.apache.guacamole.net.auth.permission.ObjectPermission; import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; import org.apache.guacamole.net.auth.permission.SystemPermission; import org.apache.guacamole.net.auth.permission.SystemPermissionSet; +import org.apache.guacamole.net.event.DirectoryEvent; +import org.apache.guacamole.net.event.DirectoryFailureEvent; +import org.apache.guacamole.net.event.DirectorySuccessEvent; import org.apache.guacamole.rest.APIPatch; +import org.apache.guacamole.rest.event.ListenerService; /** * A REST resource which abstracts the operations available on all Guacamole @@ -68,12 +75,23 @@ import org.apache.guacamole.rest.APIPatch; @Consumes(MediaType.APPLICATION_JSON) public abstract class DirectoryResource { + /** + * The user that is accessing this resource. + */ + private final AuthenticatedUser authenticatedUser; + /** * The UserContext associated with the Directory being exposed by this * DirectoryResource. */ private final UserContext userContext; + /** + * The type of object contained within the Directory exposed by this + * DirectoryResource. + */ + private final Class internalType; + /** * The Directory being exposed by this DirectoryResource. */ @@ -92,13 +110,25 @@ public abstract class DirectoryResource resourceFactory; + /** + * Service for dispatching events to registered listeners. + */ + @Inject + private ListenerService listenerService; + /** * Creates a new DirectoryResource which exposes the operations available * for the given Directory. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * + * @param internalType + * The type of object contained within the given Directory. + * * @param directory * The Directory being exposed by this DirectoryResource. * @@ -110,11 +140,15 @@ public abstract class DirectoryResource directory, + public DirectoryResource(AuthenticatedUser authenticatedUser, + UserContext userContext, Class internalType, + Directory directory, DirectoryObjectTranslator translator, DirectoryObjectResourceFactory resourceFactory) { + this.authenticatedUser = authenticatedUser; this.userContext = userContext; this.directory = directory; + this.internalType = internalType; this.translator = translator; this.resourceFactory = resourceFactory; } @@ -139,6 +173,174 @@ public abstract class DirectoryResource() { + + @Override + public Directory.Type getDirectoryType() { + return Directory.Type.of(internalType); + } + + @Override + public DirectoryEvent.Operation getOperation() { + return operation; + } + + @Override + public String getObjectIdentifier() { + return identifier; + } + + @Override + public InternalType getObject() { + return object; + } + + @Override + public AuthenticatedUser getAuthenticatedUser() { + return authenticatedUser; + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return userContext.getAuthenticationProvider(); + } + + }); + } + + /** + * Notifies all registered listeners that the given operation has failed + * against the object having the given identifier within the Directory + * represented by this resource. + * + * @param operation + * The operation that failed. + * + * @param identifier + * The identifier of the object that would have been affected by the + * operation had it succeeded. + * + * @param object + * The specific object would have been affected by the operation, if + * available, had it succeeded, including any changes that were + * intended to be applied to the object. If not available, this may be + * null. + * + * @param failure + * The failure that occurred. + * + * @throws GuacamoleException + * If a listener throws a GuacamoleException from its event handler. + */ + protected void fireDirectoryFailureEvent(DirectoryEvent.Operation operation, + String identifier, InternalType object, Throwable failure) + throws GuacamoleException { + listenerService.handleEvent(new DirectoryFailureEvent() { + + @Override + public Directory.Type getDirectoryType() { + return Directory.Type.of(internalType); + } + + @Override + public DirectoryEvent.Operation getOperation() { + return operation; + } + + @Override + public String getObjectIdentifier() { + return identifier; + } + + @Override + public InternalType getObject() { + return object; + } + + @Override + public AuthenticatedUser getAuthenticatedUser() { + return authenticatedUser; + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return userContext.getAuthenticationProvider(); + } + + @Override + public Throwable getFailure() { + return failure; + } + + }); + } + + /** + * Returns the user accessing this resource. + * + * @return + * The user accessing this resource. + */ + protected AuthenticatedUser getAuthenticatedUser() { + return authenticatedUser; + } + + /** + * Returns the UserContext providing the Directory exposed by this + * resource. + * + * @return + * The UserContext providing the Directory exposed by this resource. + */ + protected UserContext getUserContext() { + return userContext; + } + + /** + * Returns the Directory exposed by this resource. + * + * @return + * The Directory exposed by this resource. + */ + protected Directory getDirectory() { + return directory; + } + + /** + * Returns a factory that can be used to create instances of resources + * representing individual objects contained within the Directory exposed + * by this resource. + * + * @return + * A factory that can be used to create instances of resources + * representing individual objects contained within the Directory + * exposed by this resource. + */ + public DirectoryObjectResourceFactory getResourceFactory() { + return resourceFactory; + } + /** * Returns a map of all objects available within this DirectoryResource, * filtering the returned map by the given permission, if specified. @@ -213,7 +415,15 @@ public abstract class DirectoryResource resource = resourceFactory.create(authenticatedUser, userContext, directory, object); + fireDirectorySuccessEvent(DirectoryEvent.Operation.GET, identifier, object); + return resource; } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResourceFactory.java b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResourceFactory.java index 357777a6f..c28294bae 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResourceFactory.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResourceFactory.java @@ -19,6 +19,7 @@ package org.apache.guacamole.rest.directory; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.Identifiable; import org.apache.guacamole.net.auth.UserContext; @@ -41,6 +42,9 @@ public interface DirectoryResourceFactory - create(UserContext userContext, Directory directory); + create(AuthenticatedUser authenticatedUser, UserContext userContext, + Directory directory); } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResource.java index 0c334b068..6116c3785 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResource.java @@ -116,7 +116,7 @@ public class SessionResource { UserContext userContext = session.getUserContext(authProviderIdentifier); // Return a resource exposing the retrieved UserContext - return userContextResourceFactory.create(userContext); + return userContextResourceFactory.create(session.getAuthenticatedUser(), userContext); } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/session/UserContextResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/session/UserContextResource.java index cbb04d172..b696259c3 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/session/UserContextResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/session/UserContextResource.java @@ -30,6 +30,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.net.auth.ActiveConnection; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.net.auth.SharingProfile; @@ -59,6 +60,11 @@ public class UserContextResource { */ private final UserContext userContext; + /** + * The user that is accessing this resource. + */ + private final AuthenticatedUser authenticatedUser; + /** * Factory for creating DirectoryObjectResources which expose a given User. */ @@ -115,12 +121,17 @@ public class UserContextResource { * Creates a new UserContextResource which exposes the data within the * given UserContext. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext which should be exposed through this * UserContextResource. */ @AssistedInject - public UserContextResource(@Assisted UserContext userContext) { + public UserContextResource(@Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext) { + this.authenticatedUser = authenticatedUser; this.userContext = userContext; } @@ -140,7 +151,7 @@ public class UserContextResource { @Path("self") public DirectoryObjectResource getSelfResource() throws GuacamoleException { - return userResourceFactory.create(userContext, + return userResourceFactory.create(authenticatedUser, userContext, userContext.getUserDirectory(), userContext.self()); } @@ -158,8 +169,8 @@ public class UserContextResource { @Path("activeConnections") public DirectoryResource getActiveConnectionDirectoryResource() throws GuacamoleException { - return activeConnectionDirectoryResourceFactory.create(userContext, - userContext.getActiveConnectionDirectory()); + return activeConnectionDirectoryResourceFactory.create(authenticatedUser, + userContext, userContext.getActiveConnectionDirectory()); } /** @@ -176,8 +187,8 @@ public class UserContextResource { @Path("connections") public DirectoryResource getConnectionDirectoryResource() throws GuacamoleException { - return connectionDirectoryResourceFactory.create(userContext, - userContext.getConnectionDirectory()); + return connectionDirectoryResourceFactory.create(authenticatedUser, + userContext, userContext.getConnectionDirectory()); } /** @@ -194,8 +205,8 @@ public class UserContextResource { @Path("connectionGroups") public DirectoryResource getConnectionGroupDirectoryResource() throws GuacamoleException { - return connectionGroupDirectoryResourceFactory.create(userContext, - userContext.getConnectionGroupDirectory()); + return connectionGroupDirectoryResourceFactory.create(authenticatedUser, + userContext, userContext.getConnectionGroupDirectory()); } /** @@ -212,8 +223,8 @@ public class UserContextResource { @Path("sharingProfiles") public DirectoryResource getSharingProfileDirectoryResource() throws GuacamoleException { - return sharingProfileDirectoryResourceFactory.create(userContext, - userContext.getSharingProfileDirectory()); + return sharingProfileDirectoryResourceFactory.create(authenticatedUser, + userContext, userContext.getSharingProfileDirectory()); } /** @@ -230,8 +241,8 @@ public class UserContextResource { @Path("users") public DirectoryResource getUserDirectoryResource() throws GuacamoleException { - return userDirectoryResourceFactory.create(userContext, - userContext.getUserDirectory()); + return userDirectoryResourceFactory.create(authenticatedUser, + userContext, userContext.getUserDirectory()); } /** @@ -248,8 +259,8 @@ public class UserContextResource { @Path("userGroups") public DirectoryResource getUserGroupDirectoryResource() throws GuacamoleException { - return userGroupDirectoryResourceFactory.create(userContext, - userContext.getUserGroupDirectory()); + return userGroupDirectoryResourceFactory.create(authenticatedUser, + userContext, userContext.getUserGroupDirectory()); } /** diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/session/UserContextResourceFactory.java b/guacamole/src/main/java/org/apache/guacamole/rest/session/UserContextResourceFactory.java index 5eea3edf5..c969a6311 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/session/UserContextResourceFactory.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/session/UserContextResourceFactory.java @@ -19,6 +19,7 @@ package org.apache.guacamole.rest.session; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.UserContext; /** @@ -31,6 +32,9 @@ public interface UserContextResourceFactory { * Creates a new UserContextResource which exposes the contents of the * given UserContext. * + * @param authenticatedUser + * The user that is accessing the resource. + * * @param userContext * The UserContext whose contents should be exposed. * @@ -38,6 +42,7 @@ public interface UserContextResourceFactory { * A new UserContextResource which exposes the contents of the given * UserContext. */ - UserContextResource create(UserContext userContext); + UserContextResource create(AuthenticatedUser authenticatedUser, + UserContext userContext); } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/sharingprofile/SharingProfileDirectoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/sharingprofile/SharingProfileDirectoryResource.java index ab24ef381..16ae29509 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/sharingprofile/SharingProfileDirectoryResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/sharingprofile/SharingProfileDirectoryResource.java @@ -25,6 +25,7 @@ import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.Permissions; import org.apache.guacamole.net.auth.SharingProfile; @@ -48,6 +49,9 @@ public class SharingProfileDirectoryResource * operations and subresources available for the given SharingProfile * Directory. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * @@ -63,11 +67,13 @@ public class SharingProfileDirectoryResource * representing SharingProfiles. */ @AssistedInject - public SharingProfileDirectoryResource(@Assisted UserContext userContext, + public SharingProfileDirectoryResource( + @Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext, @Assisted Directory directory, DirectoryObjectTranslator translator, DirectoryObjectResourceFactory resourceFactory) { - super(userContext, directory, translator, resourceFactory); + super(authenticatedUser, userContext, SharingProfile.class, directory, translator, resourceFactory); } @Override diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/sharingprofile/SharingProfileResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/sharingprofile/SharingProfileResource.java index 4797ade7f..46daac9b3 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/sharingprofile/SharingProfileResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/sharingprofile/SharingProfileResource.java @@ -29,6 +29,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.Permissions; import org.apache.guacamole.net.auth.SharingProfile; @@ -49,21 +50,13 @@ import org.apache.guacamole.rest.directory.DirectoryObjectTranslator; public class SharingProfileResource extends DirectoryObjectResource { - /** - * The UserContext associated with the Directory which contains the - * SharingProfile exposed by this resource. - */ - private final UserContext userContext; - - /** - * The SharingProfile object represented by this SharingProfileResource. - */ - private final SharingProfile sharingProfile; - /** * Creates a new SharingProfileResource which exposes the operations and * subresources available for the given SharingProfile. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * @@ -78,13 +71,12 @@ public class SharingProfileResource * object given. */ @AssistedInject - public SharingProfileResource(@Assisted UserContext userContext, + public SharingProfileResource(@Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext, @Assisted Directory directory, @Assisted SharingProfile sharingProfile, DirectoryObjectTranslator translator) { - super(userContext, directory, sharingProfile, translator); - this.userContext = userContext; - this.sharingProfile = sharingProfile; + super(authenticatedUser, userContext, SharingProfile.class, directory, sharingProfile, translator); } /** @@ -103,8 +95,10 @@ public class SharingProfileResource public Map getParameters() throws GuacamoleException { + SharingProfile sharingProfile = getInternalObject(); + // Pull effective permissions - Permissions effective = userContext.self().getEffectivePermissions(); + Permissions effective = getUserContext().self().getEffectivePermissions(); // Retrieve permission sets SystemPermissionSet systemPermissions = effective.getSystemPermissions(); diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelCollectionResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelCollectionResource.java index abd7b422d..770469c7e 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelCollectionResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelCollectionResource.java @@ -105,7 +105,7 @@ public class TunnelCollectionResource { throw new GuacamoleResourceNotFoundException("No such tunnel."); // Return corresponding tunnel resource - return tunnelResourceFactory.create(tunnel); + return tunnelResourceFactory.create(session.getAuthenticatedUser(), tunnel); } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelResource.java index 7c0ec7eef..7107ab277 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelResource.java @@ -34,6 +34,7 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleResourceNotFoundException; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.auth.ActiveConnection; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.protocols.ProtocolInfo; import org.apache.guacamole.rest.activeconnection.APIActiveConnection; @@ -55,6 +56,11 @@ public class TunnelResource { */ private static final String DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_OCTET_STREAM; + /** + * The user that is accessing this resource. + */ + private final AuthenticatedUser authenticatedUser; + /** * The tunnel that this TunnelResource represents. */ @@ -78,11 +84,16 @@ public class TunnelResource { * Creates a new TunnelResource which exposes the operations and * subresources available for the given tunnel. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param tunnel * The tunnel that this TunnelResource should represent. */ @AssistedInject - public TunnelResource(@Assisted UserTunnel tunnel) { + public TunnelResource(@Assisted AuthenticatedUser authenticatedUser, + @Assisted UserTunnel tunnel) { + this.authenticatedUser = authenticatedUser; this.tunnel = tunnel; } @@ -110,7 +121,7 @@ public class TunnelResource { throw new GuacamoleResourceNotFoundException("No readable active connection for tunnel."); // Return the associated ActiveConnection as a resource - return activeConnectionResourceFactory.create(userContext, + return activeConnectionResourceFactory.create(authenticatedUser, userContext, userContext.getActiveConnectionDirectory(), activeConnection); } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelResourceFactory.java b/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelResourceFactory.java index 2deacf88a..9efdf9c68 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelResourceFactory.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelResourceFactory.java @@ -19,6 +19,7 @@ package org.apache.guacamole.rest.tunnel; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.tunnel.UserTunnel; /** @@ -31,12 +32,15 @@ public interface TunnelResourceFactory { * Creates a new TunnelResource which exposes the contents of the * given tunnel. * + * @param authenticatedUser + * The user that is accessing the resource. + * * @param tunnel * The tunnel whose contents should be exposed. * * @return * A new TunnelResource which exposes the contents of the given tunnel. */ - TunnelResource create(UserTunnel tunnel); + TunnelResource create(AuthenticatedUser authenticatedUser, UserTunnel tunnel); } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/user/UserDirectoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/user/UserDirectoryResource.java index f93016fe9..0918ca82f 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/user/UserDirectoryResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/user/UserDirectoryResource.java @@ -25,6 +25,7 @@ import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.User; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.Permissions; @@ -46,6 +47,9 @@ public class UserDirectoryResource extends DirectoryResource { * Creates a new UserDirectoryResource which exposes the operations and * subresources available for the given User Directory. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * @@ -61,11 +65,12 @@ public class UserDirectoryResource extends DirectoryResource { * representing Users. */ @AssistedInject - public UserDirectoryResource(@Assisted UserContext userContext, + public UserDirectoryResource(@Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext, @Assisted Directory directory, DirectoryObjectTranslator translator, DirectoryObjectResourceFactory resourceFactory) { - super(userContext, directory, translator, resourceFactory); + super(authenticatedUser, userContext, User.class, directory, translator, resourceFactory); } @Override diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/user/UserResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/user/UserResource.java index aa8a3ec7e..91600f736 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/user/UserResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/user/UserResource.java @@ -33,6 +33,7 @@ import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleUnsupportedException; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.User; @@ -40,6 +41,7 @@ import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException; import org.apache.guacamole.net.auth.simple.SimpleActivityRecordSet; +import org.apache.guacamole.net.event.DirectoryEvent; import org.apache.guacamole.rest.directory.DirectoryObjectResource; import org.apache.guacamole.rest.directory.DirectoryObjectTranslator; import org.apache.guacamole.rest.history.UserHistoryResource; @@ -63,27 +65,13 @@ public class UserResource */ private static final Logger logger = LoggerFactory.getLogger(UserResource.class); - /** - * The UserContext associated with the Directory which contains the User - * exposed by this resource. - */ - private final UserContext userContext; - - /** - * The Directory which contains the User object represented by this - * UserResource. - */ - private final Directory directory; - - /** - * The User object represented by this UserResource. - */ - private final User user; - /** * Creates a new UserResource which exposes the operations and subresources * available for the given User. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * @@ -97,14 +85,12 @@ public class UserResource * A DirectoryObjectTranslator implementation which handles Users. */ @AssistedInject - public UserResource(@Assisted UserContext userContext, + public UserResource(@Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext, @Assisted Directory directory, @Assisted User user, DirectoryObjectTranslator translator) { - super(userContext, directory, user, translator); - this.userContext = userContext; - this.directory = directory; - this.user = user; + super(authenticatedUser, userContext, User.class, directory, user, translator); } /** @@ -122,6 +108,8 @@ public class UserResource public UserHistoryResource getUserHistory() throws GuacamoleException { + User user = getInternalObject(); + // First try to retrieve history using the current getUserHistory() method. try { return new UserHistoryResource(user.getUserHistory()); @@ -147,13 +135,19 @@ public class UserResource public void updateObject(APIUser modifiedObject) throws GuacamoleException { // A user may not use this endpoint to update their password - User currentUser = userContext.self(); - if ( - currentUser.getIdentifier().equals(modifiedObject.getUsername()) - && modifiedObject.getPassword() != null) { - throw new GuacamoleSecurityException( - "Permission denied. The password update endpoint must" - + " be used to change the current user's password."); + try { + User currentUser = getUserContext().self(); + if ( + currentUser.getIdentifier().equals(modifiedObject.getUsername()) + && modifiedObject.getPassword() != null) { + throw new GuacamoleSecurityException( + "Permission denied. The password update endpoint must" + + " be used to change the current user's password."); + } + } + catch (GuacamoleException | RuntimeException | Error e) { + fireDirectoryFailureEvent(DirectoryEvent.Operation.UPDATE, e); + throw e; } super.updateObject(modifiedObject); @@ -178,13 +172,15 @@ public class UserResource public void updatePassword(APIUserPasswordUpdate userPasswordUpdate, @Context HttpServletRequest request) throws GuacamoleException { + User user = getInternalObject(); + // Build credentials Credentials credentials = new Credentials(user.getIdentifier(), userPasswordUpdate.getOldPassword(), request); // Verify that the old password was correct try { - AuthenticationProvider authProvider = userContext.getAuthenticationProvider(); + AuthenticationProvider authProvider = getUserContext().getAuthenticationProvider(); if (authProvider.authenticateUser(credentials) == null) throw new GuacamoleSecurityException("Permission denied."); } @@ -195,8 +191,15 @@ public class UserResource } // Set password to the newly provided one - user.setPassword(userPasswordUpdate.getNewPassword()); - directory.update(user); + try { + user.setPassword(userPasswordUpdate.getNewPassword()); + getDirectory().update(user); + fireDirectorySuccessEvent(DirectoryEvent.Operation.UPDATE); + } + catch (GuacamoleException | RuntimeException | Error e) { + fireDirectoryFailureEvent(DirectoryEvent.Operation.UPDATE, e); + throw e; + } } @@ -211,7 +214,7 @@ public class UserResource */ @Path("permissions") public PermissionSetResource getPermissions() { - return new PermissionSetResource(user); + return new PermissionSetResource(getInternalObject()); } /** @@ -228,7 +231,7 @@ public class UserResource @GET @Path("effectivePermissions") public APIPermissionSet getEffectivePermissions() throws GuacamoleException { - return new APIPermissionSet(user.getEffectivePermissions()); + return new APIPermissionSet(getInternalObject().getEffectivePermissions()); } /** @@ -245,7 +248,7 @@ public class UserResource */ @Path("userGroups") public RelatedObjectSetResource getUserGroups() throws GuacamoleException { - return new RelatedObjectSetResource(user.getUserGroups()); + return new RelatedObjectSetResource(getInternalObject().getUserGroups()); } } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/usergroup/UserGroupDirectoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/usergroup/UserGroupDirectoryResource.java index fc4d48be3..44327a29c 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/usergroup/UserGroupDirectoryResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/usergroup/UserGroupDirectoryResource.java @@ -25,6 +25,7 @@ import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.UserGroup; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.Permissions; @@ -46,6 +47,9 @@ public class UserGroupDirectoryResource extends DirectoryResource directory, DirectoryObjectTranslator translator, DirectoryObjectResourceFactory resourceFactory) { - super(userContext, directory, translator, resourceFactory); + super(authenticatedUser, userContext, UserGroup.class, directory, translator, resourceFactory); } @Override diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/usergroup/UserGroupResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/usergroup/UserGroupResource.java index 350b59fed..6994484d9 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/usergroup/UserGroupResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/usergroup/UserGroupResource.java @@ -26,6 +26,7 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.UserGroup; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.UserContext; @@ -43,15 +44,13 @@ import org.apache.guacamole.rest.permission.PermissionSetResource; public class UserGroupResource extends DirectoryObjectResource { - /** - * The UserGroup object represented by this UserGroupResource. - */ - private final UserGroup userGroup; - /** * Creates a new UserGroupResource which exposes the operations and * subresources available for the given UserGroup. * + * @param authenticatedUser + * The user that is accessing this resource. + * * @param userContext * The UserContext associated with the given Directory. * @@ -65,12 +64,12 @@ public class UserGroupResource * A DirectoryObjectTranslator implementation which handles Users. */ @AssistedInject - public UserGroupResource(@Assisted UserContext userContext, + public UserGroupResource(@Assisted AuthenticatedUser authenticatedUser, + @Assisted UserContext userContext, @Assisted Directory directory, @Assisted UserGroup userGroup, DirectoryObjectTranslator translator) { - super(userContext, directory, userGroup, translator); - this.userGroup = userGroup; + super(authenticatedUser, userContext, UserGroup.class, directory, userGroup, translator); } /** @@ -87,7 +86,7 @@ public class UserGroupResource */ @Path("userGroups") public RelatedObjectSetResource getUserGroups() throws GuacamoleException { - return new RelatedObjectSetResource(userGroup.getUserGroups()); + return new RelatedObjectSetResource(getInternalObject().getUserGroups()); } /** @@ -104,7 +103,7 @@ public class UserGroupResource */ @Path("memberUsers") public RelatedObjectSetResource getMemberUsers() throws GuacamoleException { - return new RelatedObjectSetResource(userGroup.getMemberUsers()); + return new RelatedObjectSetResource(getInternalObject().getMemberUsers()); } /** @@ -121,7 +120,7 @@ public class UserGroupResource */ @Path("memberUserGroups") public RelatedObjectSetResource getMemberUserGroups() throws GuacamoleException { - return new RelatedObjectSetResource(userGroup.getMemberUserGroups()); + return new RelatedObjectSetResource(getInternalObject().getMemberUserGroups()); } /** @@ -135,7 +134,7 @@ public class UserGroupResource */ @Path("permissions") public PermissionSetResource getPermissions() { - return new PermissionSetResource(userGroup); + return new PermissionSetResource(getInternalObject()); } }