From a123eacab5060d0e965bedf86482169b4b235d35 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 10 Feb 2022 17:17:48 -0800 Subject: [PATCH] GUACAMOLE-462: Add extension for automatically associated session recordings, logs, etc. with history entries. --- .../.gitignore | 3 + .../.ratignore | 0 .../pom.xml | 52 +++ .../src/main/assembly/dist.xml | 53 +++ .../HistoryAuthenticationProvider.java | 49 +++ .../history/connection/HistoryConnection.java | 71 ++++ .../connection/HistoryConnectionRecord.java | 309 ++++++++++++++++++ .../RecordedConnectionActivityRecordSet.java | 125 +++++++ .../history/user/HistoryUserContext.java | 84 +++++ .../src/main/resources/guac-manifest.json | 16 + .../src/main/resources/translations/en.json | 14 + extensions/pom.xml | 1 + 12 files changed, 777 insertions(+) create mode 100644 extensions/guacamole-history-recording-storage/.gitignore create mode 100644 extensions/guacamole-history-recording-storage/.ratignore create mode 100644 extensions/guacamole-history-recording-storage/pom.xml create mode 100644 extensions/guacamole-history-recording-storage/src/main/assembly/dist.xml create mode 100644 extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/HistoryAuthenticationProvider.java create mode 100644 extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnection.java create mode 100644 extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnectionRecord.java create mode 100644 extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/RecordedConnectionActivityRecordSet.java create mode 100644 extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/user/HistoryUserContext.java create mode 100644 extensions/guacamole-history-recording-storage/src/main/resources/guac-manifest.json create mode 100644 extensions/guacamole-history-recording-storage/src/main/resources/translations/en.json diff --git a/extensions/guacamole-history-recording-storage/.gitignore b/extensions/guacamole-history-recording-storage/.gitignore new file mode 100644 index 000000000..1de9633ae --- /dev/null +++ b/extensions/guacamole-history-recording-storage/.gitignore @@ -0,0 +1,3 @@ +src/main/resources/generated/ +target/ +*~ diff --git a/extensions/guacamole-history-recording-storage/.ratignore b/extensions/guacamole-history-recording-storage/.ratignore new file mode 100644 index 000000000..e69de29bb diff --git a/extensions/guacamole-history-recording-storage/pom.xml b/extensions/guacamole-history-recording-storage/pom.xml new file mode 100644 index 000000000..27cca873d --- /dev/null +++ b/extensions/guacamole-history-recording-storage/pom.xml @@ -0,0 +1,52 @@ + + + + + 4.0.0 + org.apache.guacamole + guacamole-history-recording-storage + jar + 1.4.0 + guacamole-history-recording-storage + http://guacamole.apache.org/ + + + org.apache.guacamole + extensions + 1.4.0 + ../ + + + + + + + org.apache.guacamole + guacamole-ext + 1.4.0 + provided + + + + + diff --git a/extensions/guacamole-history-recording-storage/src/main/assembly/dist.xml b/extensions/guacamole-history-recording-storage/src/main/assembly/dist.xml new file mode 100644 index 000000000..6ee3cd8c8 --- /dev/null +++ b/extensions/guacamole-history-recording-storage/src/main/assembly/dist.xml @@ -0,0 +1,53 @@ + + + + + dist + ${project.artifactId}-${project.version} + + + + tar.gz + + + + + + + + + target/licenses + + + + + target + + + *.jar + + + + + + diff --git a/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/HistoryAuthenticationProvider.java b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/HistoryAuthenticationProvider.java new file mode 100644 index 000000000..61c551956 --- /dev/null +++ b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/HistoryAuthenticationProvider.java @@ -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); + } + +} diff --git a/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnection.java b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnection.java new file mode 100644 index 000000000..4f1067413 --- /dev/null +++ b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnection.java @@ -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 getConnectionHistory() throws GuacamoleException { + return new RecordedConnectionActivityRecordSet(currentUser, super.getConnectionHistory()); + } + +} diff --git a/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnectionRecord.java b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnectionRecord.java new file mode 100644 index 000000000..13c76bd6a --- /dev/null +++ b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/HistoryConnectionRecord.java @@ -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 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 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 logs = new HashMap<>(super.getLogs()); + if (recording.isDirectory()) { + Arrays.asList(recording.listFiles()).stream() + .forEach((file) -> addActivityLog(logs, file)); + } + else + addActivityLog(logs, recording); + + return logs; + + } + +} diff --git a/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/RecordedConnectionActivityRecordSet.java b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/RecordedConnectionActivityRecordSet.java new file mode 100644 index 000000000..5caffdfa7 --- /dev/null +++ b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/connection/RecordedConnectionActivityRecordSet.java @@ -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 { + + /** + * 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 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 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; + + } + +} diff --git a/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/user/HistoryUserContext.java b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/user/HistoryUserContext.java new file mode 100644 index 000000000..62d639698 --- /dev/null +++ b/extensions/guacamole-history-recording-storage/src/main/java/org/apache/guacamole/history/user/HistoryUserContext.java @@ -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 getConnectionDirectory() throws GuacamoleException { + return new DecoratingDirectory(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 getConnectionHistory() + throws GuacamoleException { + return new RecordedConnectionActivityRecordSet(currentUser, super.getConnectionHistory()); + } + +} diff --git a/extensions/guacamole-history-recording-storage/src/main/resources/guac-manifest.json b/extensions/guacamole-history-recording-storage/src/main/resources/guac-manifest.json new file mode 100644 index 000000000..51422359d --- /dev/null +++ b/extensions/guacamole-history-recording-storage/src/main/resources/guac-manifest.json @@ -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" + ] + +} diff --git a/extensions/guacamole-history-recording-storage/src/main/resources/translations/en.json b/extensions/guacamole-history-recording-storage/src/main/resources/translations/en.json new file mode 100644 index 000000000..13b7dba2e --- /dev/null +++ b/extensions/guacamole-history-recording-storage/src/main/resources/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" + } + +} diff --git a/extensions/pom.xml b/extensions/pom.xml index 966bf410f..de2b24556 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -50,6 +50,7 @@ guacamole-auth-totp + guacamole-history-recording-storage guacamole-vault