mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUACAMOLE-462: Add extension for automatically associated session recordings, logs, etc. with history entries.
This commit is contained in:
3
extensions/guacamole-history-recording-storage/.gitignore
vendored
Normal file
3
extensions/guacamole-history-recording-storage/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
src/main/resources/generated/
|
||||
target/
|
||||
*~
|
52
extensions/guacamole-history-recording-storage/pom.xml
Normal file
52
extensions/guacamole-history-recording-storage/pom.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-history-recording-storage</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.4.0</version>
|
||||
<name>guacamole-history-recording-storage</name>
|
||||
<url>http://guacamole.apache.org/</url>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>extensions</artifactId>
|
||||
<version>1.4.0</version>
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Guacamole Extension API -->
|
||||
<dependency>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-ext</artifactId>
|
||||
<version>1.4.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||
|
||||
<id>dist</id>
|
||||
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
|
||||
|
||||
<!-- Output tar.gz -->
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
|
||||
<!-- Include licenses and extension .jar -->
|
||||
<fileSets>
|
||||
|
||||
<!-- Include licenses -->
|
||||
<fileSet>
|
||||
<outputDirectory></outputDirectory>
|
||||
<directory>target/licenses</directory>
|
||||
</fileSet>
|
||||
|
||||
<!-- Include extension .jar -->
|
||||
<fileSet>
|
||||
<directory>target</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
|
||||
</fileSets>
|
||||
|
||||
</assembly>
|
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.history;
|
||||
|
||||
import org.apache.guacamole.history.user.HistoryUserContext;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
|
||||
/**
|
||||
* AuthenticationProvider implementation which automatically associates history
|
||||
* entries with session recordings, typescripts, etc. History association is
|
||||
* determined by matching the history entry UUID with the filenames of files
|
||||
* located within a standardized/configurable directory.
|
||||
*/
|
||||
public class HistoryAuthenticationProvider extends AbstractAuthenticationProvider {
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "recording-storage";
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserContext decorate(UserContext context,
|
||||
AuthenticatedUser authenticatedUser, Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
return new HistoryUserContext(context.self(), context);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.history.connection;
|
||||
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.ActivityRecordSet;
|
||||
import org.apache.guacamole.net.auth.Connection;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
import org.apache.guacamole.net.auth.DelegatingConnection;
|
||||
import org.apache.guacamole.net.auth.User;
|
||||
|
||||
/**
|
||||
* Connection implementation that automatically defines ActivityLogs for
|
||||
* files that relate to history entries associated with the wrapped connection.
|
||||
*/
|
||||
public class HistoryConnection extends DelegatingConnection {
|
||||
|
||||
/**
|
||||
* The current Guacamole user.
|
||||
*/
|
||||
private final User currentUser;
|
||||
|
||||
/**
|
||||
* Creates a new HistoryConnection that wraps the given connection,
|
||||
* automatically associating history entries with ActivityLogs based on
|
||||
* related files (session recordings, typescripts, etc.).
|
||||
*
|
||||
* @param currentUser
|
||||
* The current Guacamole user.
|
||||
*
|
||||
* @param connection
|
||||
* The connection to wrap.
|
||||
*/
|
||||
public HistoryConnection(User currentUser, Connection connection) {
|
||||
super(connection);
|
||||
this.currentUser = currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection wrapped by this HistoryConnection.
|
||||
*
|
||||
* @return
|
||||
* The connection wrapped by this HistoryConnection.
|
||||
*/
|
||||
public Connection getWrappedConnection() {
|
||||
return getDelegateConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityRecordSet<ConnectionRecord> getConnectionHistory() throws GuacamoleException {
|
||||
return new RecordedConnectionActivityRecordSet(currentUser, super.getConnectionHistory());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* 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.history.connection;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.environment.LocalEnvironment;
|
||||
import org.apache.guacamole.io.GuacamoleReader;
|
||||
import org.apache.guacamole.io.ReaderGuacamoleReader;
|
||||
import org.apache.guacamole.language.TranslatableMessage;
|
||||
import org.apache.guacamole.net.auth.ActivityLog;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
import org.apache.guacamole.net.auth.DelegatingConnectionRecord;
|
||||
import org.apache.guacamole.net.auth.FileActivityLog;
|
||||
import org.apache.guacamole.properties.FileGuacamoleProperty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* ConnectionRecord implementation that automatically defines ActivityLogs for
|
||||
* files that relate to the wrapped record.
|
||||
*/
|
||||
public class HistoryConnectionRecord extends DelegatingConnectionRecord {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(HistoryConnectionRecord.class);
|
||||
|
||||
/**
|
||||
* The namespace for URL UUIDs as defined by RFC 4122.
|
||||
*/
|
||||
private static final UUID UUID_NAMESPACE_URL = UUID.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
|
||||
|
||||
/**
|
||||
* The filename suffix of typescript timing files.
|
||||
*/
|
||||
private static final String TIMING_FILE_SUFFIX = ".timing";
|
||||
|
||||
/**
|
||||
* The default directory to search for associated session recordings, if
|
||||
* not overridden with the "recording-search-path" property.
|
||||
*/
|
||||
private static final File DEFAULT_RECORDING_SEARCH_PATH = new File("/var/lib/guacamole/recordings");
|
||||
|
||||
/**
|
||||
* The directory to search for associated session recordings. By default,
|
||||
* "/var/lib/guacamole/recordings" will be used.
|
||||
*/
|
||||
private static final FileGuacamoleProperty RECORDING_SEARCH_PATH = new FileGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "recording-search-path";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The recording file associated with the wrapped connection record. This
|
||||
* may be a single file or a directory that may contain any number of
|
||||
* relevant recordings.
|
||||
*/
|
||||
private final File recording;
|
||||
|
||||
/**
|
||||
* Creates a new HistoryConnectionRecord that wraps the given
|
||||
* ConnectionRecord, automatically associating ActivityLogs based on
|
||||
* related files (session recordings, typescripts, etc.).
|
||||
*
|
||||
* @param record
|
||||
* The ConnectionRecord to wrap.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the configured path for stored recordings cannot be read.
|
||||
*/
|
||||
public HistoryConnectionRecord(ConnectionRecord record) throws GuacamoleException {
|
||||
super(record);
|
||||
|
||||
Environment environment = LocalEnvironment.getInstance();
|
||||
File recordingPath = environment.getProperty(RECORDING_SEARCH_PATH,
|
||||
DEFAULT_RECORDING_SEARCH_PATH);
|
||||
|
||||
String uuid = record.getUUID().toString();
|
||||
File recordingFile = new File(recordingPath, uuid);
|
||||
this.recording = recordingFile.canRead() ? recordingFile : null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given file appears to be a Guacamole session
|
||||
* recording. As there is no standard extension for session recordings,
|
||||
* this is determined by attempting to read a single Guacamole instruction
|
||||
* from the file.
|
||||
*
|
||||
* @param file
|
||||
* The file to test.
|
||||
*
|
||||
* @return
|
||||
* true if the file appears to be a Guacamole session recording, false
|
||||
* otherwise.
|
||||
*/
|
||||
private boolean isSessionRecording(File file) {
|
||||
|
||||
try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
|
||||
|
||||
GuacamoleReader guacReader = new ReaderGuacamoleReader(reader);
|
||||
if (guacReader.readInstruction() != null)
|
||||
return true;
|
||||
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("File \"{}\" does not appear to be a session "
|
||||
+ "recording, as it could not be parsed as Guacamole "
|
||||
+ "protocol data.", file, e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.warn("Possible session recording \"{}\" could not be "
|
||||
+ "identified as it cannot be read: {}", file, e.getMessage());
|
||||
logger.debug("Possible session recording \"{}\" could not be read.", file, e);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given file appears to be a typescript (text
|
||||
* recording of a terminal session). As there is no standard extension for
|
||||
* session recordings, this is determined by testing whether there is an
|
||||
* associated timing file. Guacamole will always include a timing file for
|
||||
* its typescripts.
|
||||
*
|
||||
* @param file
|
||||
* The file to test.
|
||||
*
|
||||
* @return
|
||||
* true if the file appears to be a typescript, false otherwise.
|
||||
*/
|
||||
private boolean isTypescript(File file) {
|
||||
return new File(file.getAbsolutePath() + TIMING_FILE_SUFFIX).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given file appears to be a typescript timing file.
|
||||
* Typescript timing files have the standard extension ".timing".
|
||||
*
|
||||
* @param file
|
||||
* The file to test.
|
||||
*
|
||||
* @return
|
||||
* true if the file appears to be a typescript timing file, false
|
||||
* otherwise.
|
||||
*/
|
||||
private boolean isTypescriptTiming(File file) {
|
||||
return file.getName().endsWith(TIMING_FILE_SUFFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of session recording or log contained within the given
|
||||
* file by inspecting its name and contents.
|
||||
*
|
||||
* @param file
|
||||
* The file to test.
|
||||
*
|
||||
* @return
|
||||
* The type of session recording or log contained within the given
|
||||
* file, or null if this cannot be determined.
|
||||
*/
|
||||
private ActivityLog.Type getType(File file) {
|
||||
|
||||
if (isSessionRecording(file))
|
||||
return ActivityLog.Type.GUACAMOLE_SESSION_RECORDING;
|
||||
|
||||
if (isTypescript(file))
|
||||
return ActivityLog.Type.TYPESCRIPT;
|
||||
|
||||
if (isTypescriptTiming(file))
|
||||
return ActivityLog.Type.TYPESCRIPT_TIMING;
|
||||
|
||||
return ActivityLog.Type.SERVER_LOG;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ActivityLog instance representing the session recording or
|
||||
* log contained within the given file. If the type of recording/log cannot
|
||||
* be determined, or if the file is unreadable, null is returned.
|
||||
*
|
||||
* @param file
|
||||
* The file to produce an ActivityLog instance for.
|
||||
*
|
||||
* @return
|
||||
* A new ActivityLog instance representing the recording/log contained
|
||||
* within the given file, or null if the file is unreadable or cannot
|
||||
* be identified.
|
||||
*/
|
||||
private ActivityLog getActivityLog(File file) {
|
||||
|
||||
// Verify file can actually be read
|
||||
if (!file.canRead()) {
|
||||
logger.warn("Ignoring file \"{}\" relevant to connection history "
|
||||
+ "record as it cannot be read.", file);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine type of recording/log by inspecting file
|
||||
ActivityLog.Type logType = getType(file);
|
||||
if (logType == null) {
|
||||
logger.warn("Recording/log type of \"{}\" cannot be determined.", file);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FileActivityLog(
|
||||
logType,
|
||||
new TranslatableMessage("RECORDING_STORAGE.INFO_" + logType.name()),
|
||||
file
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an ActivityLog instance representing the session recording or log
|
||||
* contained within the given file to the given map of logs. If no
|
||||
* ActivityLog can be produced for the given file (it is unreadable or
|
||||
* cannot be identified), this function has no effect.
|
||||
*
|
||||
* @param logs
|
||||
* The map of logs to add the ActivityLog to.
|
||||
*
|
||||
* @param file
|
||||
* The file to produce an ActivityLog instance for.
|
||||
*/
|
||||
private void addActivityLog(Map<String, ActivityLog> logs, File file) {
|
||||
|
||||
ActivityLog log = getActivityLog(file);
|
||||
if (log == null)
|
||||
return;
|
||||
|
||||
// Convert file into deterministic name UUID within URL namespace
|
||||
UUID fileUUID;
|
||||
try {
|
||||
byte[] urlBytes = file.toURI().toURL().toString().getBytes(StandardCharsets.UTF_8);
|
||||
fileUUID = UUID.nameUUIDFromBytes(ByteBuffer.allocate(16 + urlBytes.length)
|
||||
.putLong(UUID_NAMESPACE_URL.getMostSignificantBits())
|
||||
.putLong(UUID_NAMESPACE_URL.getLeastSignificantBits())
|
||||
.put(urlBytes)
|
||||
.array());
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
logger.warn("Ignoring file \"{}\" as a unique URL and UUID for that file could not be generated: {}", e.getMessage());
|
||||
logger.debug("URL for file \"{}\" could not be determined.", file, e);
|
||||
return;
|
||||
}
|
||||
|
||||
logs.put(fileUUID.toString(), log);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ActivityLog> getLogs() {
|
||||
|
||||
// Do nothing if there are no associated logs
|
||||
if (recording == null)
|
||||
return super.getLogs();
|
||||
|
||||
// Add associated log (or logs, if this is a directory)
|
||||
Map<String, ActivityLog> logs = new HashMap<>(super.getLogs());
|
||||
if (recording.isDirectory()) {
|
||||
Arrays.asList(recording.listFiles()).stream()
|
||||
.forEach((file) -> addActivityLog(logs, file));
|
||||
}
|
||||
else
|
||||
addActivityLog(logs, recording);
|
||||
|
||||
return logs;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.history.connection;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.ActivityRecordSet;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
import org.apache.guacamole.net.auth.DecoratingActivityRecordSet;
|
||||
import org.apache.guacamole.net.auth.Permissions;
|
||||
import org.apache.guacamole.net.auth.User;
|
||||
import org.apache.guacamole.net.auth.permission.ObjectPermission;
|
||||
import org.apache.guacamole.net.auth.permission.SystemPermission;
|
||||
|
||||
/**
|
||||
* ActivityRecordSet implementation that automatically defines ActivityLogs for
|
||||
* files that relate to history entries within the wrapped set.
|
||||
*/
|
||||
public class RecordedConnectionActivityRecordSet extends DecoratingActivityRecordSet<ConnectionRecord> {
|
||||
|
||||
/**
|
||||
* Whether the current user is an administrator.
|
||||
*/
|
||||
private final boolean isAdmin;
|
||||
|
||||
/**
|
||||
* The overall set of connection permissions defined for the current user.
|
||||
*/
|
||||
private final Set<ObjectPermission> connectionPermissions;
|
||||
|
||||
/**
|
||||
* Creates a new RecordedConnectionActivityRecordSet that wraps the given
|
||||
* ActivityRecordSet, automatically associating history entries with
|
||||
* ActivityLogs based on related files (session recordings, typescripts,
|
||||
* etc.).
|
||||
*
|
||||
* @param currentUser
|
||||
* The current Guacamole user.
|
||||
*
|
||||
* @param activityRecordSet
|
||||
* The ActivityRecordSet to wrap.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the permissions for the current user cannot be retrieved.
|
||||
*/
|
||||
public RecordedConnectionActivityRecordSet(User currentUser,
|
||||
ActivityRecordSet<ConnectionRecord> activityRecordSet)
|
||||
throws GuacamoleException {
|
||||
super(activityRecordSet);
|
||||
|
||||
// Determine whether current user is an administrator
|
||||
Permissions perms = currentUser.getEffectivePermissions();
|
||||
isAdmin = perms.getSystemPermissions().hasPermission(SystemPermission.Type.ADMINISTER);
|
||||
|
||||
// If not an admin, additionally pull specific connection permissions
|
||||
if (isAdmin)
|
||||
connectionPermissions = Collections.emptySet();
|
||||
else
|
||||
connectionPermissions = perms.getConnectionPermissions().getPermissions();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current user has permission to view the logs
|
||||
* associated with the given history record. It is already given that the
|
||||
* user has permission to view the history record itself. This extension
|
||||
* considers a user to have permission to view history logs if they are
|
||||
* an administrator or if they have permission to edit the associated
|
||||
* connection.
|
||||
*
|
||||
* @param record
|
||||
* The record to check.
|
||||
*
|
||||
* @return
|
||||
* true if the current user has permission to view the logs associated
|
||||
* with the given record, false otherwise.
|
||||
*/
|
||||
private boolean canViewLogs(ConnectionRecord record) {
|
||||
|
||||
// Administrator can always view
|
||||
if (isAdmin)
|
||||
return true;
|
||||
|
||||
// Non-administrator CANNOT view if permissions cannot be verified
|
||||
String identifier = record.getConnectionIdentifier();
|
||||
if (identifier == null)
|
||||
return false;
|
||||
|
||||
// Non-administer can only view if they implicitly have permission to
|
||||
// configure recordings (they have permission to edit)
|
||||
ObjectPermission canUpdate = new ObjectPermission(ObjectPermission.Type.UPDATE, identifier);
|
||||
return connectionPermissions.contains(canUpdate);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConnectionRecord decorate(ConnectionRecord record) throws GuacamoleException {
|
||||
|
||||
// Provide access to logs only if permission is granted
|
||||
if (canViewLogs(record))
|
||||
return new HistoryConnectionRecord(record);
|
||||
|
||||
return record;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.history.user;
|
||||
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.history.connection.HistoryConnection;
|
||||
import org.apache.guacamole.history.connection.RecordedConnectionActivityRecordSet;
|
||||
import org.apache.guacamole.net.auth.ActivityRecordSet;
|
||||
import org.apache.guacamole.net.auth.Connection;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
import org.apache.guacamole.net.auth.DecoratingDirectory;
|
||||
import org.apache.guacamole.net.auth.DelegatingUserContext;
|
||||
import org.apache.guacamole.net.auth.Directory;
|
||||
import org.apache.guacamole.net.auth.User;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
|
||||
/**
|
||||
* UserContext implementation that automatically defines ActivityLogs for
|
||||
* files that relate to history entries.
|
||||
*/
|
||||
public class HistoryUserContext extends DelegatingUserContext {
|
||||
|
||||
/**
|
||||
* The current Guacamole user.
|
||||
*/
|
||||
private final User currentUser;
|
||||
|
||||
/**
|
||||
* Creates a new HistoryUserContext that wraps the given UserContext,
|
||||
* automatically associating history entries with ActivityLogs based on
|
||||
* related files (session recordings, typescripts, etc.).
|
||||
*
|
||||
* @param currentUser
|
||||
* The current Guacamole user.
|
||||
*
|
||||
* @param context
|
||||
* The UserContext to wrap.
|
||||
*/
|
||||
public HistoryUserContext(User currentUser, UserContext context) {
|
||||
super(context);
|
||||
this.currentUser = currentUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Directory<Connection> getConnectionDirectory() throws GuacamoleException {
|
||||
return new DecoratingDirectory<Connection>(super.getConnectionDirectory()) {
|
||||
|
||||
@Override
|
||||
protected Connection decorate(Connection object) {
|
||||
return new HistoryConnection(currentUser, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection undecorate(Connection object) throws GuacamoleException {
|
||||
return ((HistoryConnection) object).getWrappedConnection();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityRecordSet<ConnectionRecord> getConnectionHistory()
|
||||
throws GuacamoleException {
|
||||
return new RecordedConnectionActivityRecordSet(currentUser, super.getConnectionHistory());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
{
|
||||
|
||||
"guacamoleVersion" : "1.4.0",
|
||||
|
||||
"name" : "Session Recording Storage",
|
||||
"namespace" : "recording-storage",
|
||||
|
||||
"authProviders" : [
|
||||
"org.apache.guacamole.history.HistoryAuthenticationProvider"
|
||||
],
|
||||
|
||||
"translations" : [
|
||||
"translations/en.json"
|
||||
]
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_RECORDING_STORAGE" : {
|
||||
"NAME" : "Session Recording Storage"
|
||||
},
|
||||
|
||||
"RECORDING_STORAGE" : {
|
||||
"INFO_GUACAMOLE_SESSION_RECORDING" : "Graphical recording of remote desktop session",
|
||||
"INFO_SERVER_LOG" : "Server/system log",
|
||||
"INFO_TYPESCRIPT" : "Text recording of terminal session",
|
||||
"INFO_TYPESCRIPT_TIMING" : "Timing information for text recording of terminal session"
|
||||
}
|
||||
|
||||
}
|
@@ -50,6 +50,7 @@
|
||||
<module>guacamole-auth-totp</module>
|
||||
|
||||
<!-- Additional features -->
|
||||
<module>guacamole-history-recording-storage</module>
|
||||
<module>guacamole-vault</module>
|
||||
|
||||
</modules>
|
||||
|
Reference in New Issue
Block a user