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..572a9693a --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/event/AffectedObject.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.event; + +import org.apache.guacamole.net.auth.Nameable; +import org.apache.guacamole.net.event.DirectoryEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 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 DirectoryEvent> 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(DirectoryEvent> event) { + this.event = event; + } + + @Override + public String toString() { + + Object object = event.getObject(); + String identifier = event.getObjectIdentifier(); + + 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: + 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 + "\" (currently named \"" + name + "\")"; + else + return objectType + " \"" + identifier + "\""; + } + else + return objectType; + + } + +} 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..def5c19d9 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java @@ -0,0 +1,130 @@ +/* + * 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.event.DirectoryEvent; +import org.apache.guacamole.net.event.DirectoryFailureEvent; +import org.apache.guacamole.net.event.DirectorySuccessEvent; +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); + + /** + * 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: + logger.info("{} successfully created {}", new RequestingUser(event), new AffectedObject(event)); + break; + + case UPDATE: + 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)); + + } + } + + @Override + public void handleEvent(@Nonnull Object event) throws GuacamoleException { + + if (event instanceof DirectorySuccessEvent) + logSuccess((DirectorySuccessEvent>) event); + + else if (event instanceof DirectoryFailureEvent) + logFailure((DirectoryFailureEvent>) event); + + 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/RequestingUser.java b/guacamole/src/main/java/org/apache/guacamole/event/RequestingUser.java
new file mode 100644
index 000000000..124aa8f80
--- /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.DirectoryEvent;
+
+/**
+ * Loggable representation of the user that requested an operation.
+ */
+public class RequestingUser implements LoggableDetail {
+
+ /**
+ * The event representing the requested operation.
+ */
+ private final DirectoryEvent> 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(DirectoryEvent> 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