GUACAMOLE-1224: Add a default, global event listener providing logging.

This commit is contained in:
Michael Jumper
2022-09-22 10:41:02 -07:00
parent 79a1bcb2a3
commit 9bda1b2c19
6 changed files with 411 additions and 1 deletions

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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}
* <p>
* 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();
}

View File

@@ -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() + "\")";
}
}

View File

@@ -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<String> 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)));