mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUACAMOLE-462: Merge add support for associating connection history with logs/recordings.
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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.auth.jdbc.base;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.auth.jdbc.user.UserModel;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* Common interface for mapping activity records.
|
||||
*
|
||||
* @param <ModelType>
|
||||
* The type of model object representing the activity records mapped by
|
||||
* this mapper.
|
||||
*/
|
||||
public interface ActivityRecordMapper<ModelType> {
|
||||
|
||||
/**
|
||||
* Inserts the given activity record.
|
||||
*
|
||||
* @param record
|
||||
* The activity record to insert.
|
||||
*
|
||||
* @return
|
||||
* The number of rows inserted.
|
||||
*/
|
||||
int insert(@Param("record") ModelType record);
|
||||
|
||||
/**
|
||||
* Updates the given activity record in the database, assigning an end
|
||||
* date. No column of the existing activity record is updated except for
|
||||
* the end date. If the record does not actually exist, this operation has
|
||||
* no effect.
|
||||
*
|
||||
* @param record
|
||||
* The activity record to update.
|
||||
*
|
||||
* @return
|
||||
* The number of rows updated.
|
||||
*/
|
||||
int updateEndDate(@Param("record") ModelType record);
|
||||
|
||||
/**
|
||||
* Searches for up to <code>limit</code> activity records that contain
|
||||
* the given terms, sorted by the given predicates, regardless of whether
|
||||
* the data they are associated with is readable by any particular user.
|
||||
* This should only be called on behalf of a system administrator. If
|
||||
* records are needed by a non-administrative user who must have explicit
|
||||
* read rights, use {@link searchReadable()} instead.
|
||||
*
|
||||
* @param identifier
|
||||
* The optional identifier of the object whose history is being
|
||||
* retrieved, or null if records related to any such object should be
|
||||
* retrieved.
|
||||
*
|
||||
* @param recordIdentifier
|
||||
* The identifier of the specific history record to retrieve, if not
|
||||
* all matching records. Search terms, etc. will still be applied to
|
||||
* the single record.
|
||||
*
|
||||
* @param terms
|
||||
* The search terms that must match the returned records.
|
||||
*
|
||||
* @param sortPredicates
|
||||
* A list of predicates to sort the returned records by, in order of
|
||||
* priority.
|
||||
*
|
||||
* @param limit
|
||||
* The maximum number of records that should be returned.
|
||||
*
|
||||
* @return
|
||||
* The results of the search performed with the given parameters.
|
||||
*/
|
||||
List<ModelType> search(@Param("identifier") String identifier,
|
||||
@Param("recordIdentifier") String recordIdentifier,
|
||||
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
|
||||
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
|
||||
@Param("limit") int limit);
|
||||
|
||||
/**
|
||||
* Searches for up to <code>limit</code> activity records that contain
|
||||
* the given terms, sorted by the given predicates. Only records that are
|
||||
* associated with data explicitly readable by the given user will be
|
||||
* returned. If records are needed by a system administrator (who, by
|
||||
* definition, does not need explicit read rights), use {@link search()}
|
||||
* instead.
|
||||
*
|
||||
* @param identifier
|
||||
* The optional identifier of the object whose history is being
|
||||
* retrieved, or null if records related to any such object should be
|
||||
* retrieved.
|
||||
*
|
||||
* @param user
|
||||
* The user whose permissions should determine whether a record is
|
||||
* returned.
|
||||
*
|
||||
* @param recordIdentifier
|
||||
* The identifier of the specific history record to retrieve, if not
|
||||
* all matching records. Search terms, etc. will still be applied to
|
||||
* the single record.
|
||||
*
|
||||
* @param terms
|
||||
* The search terms that must match the returned records.
|
||||
*
|
||||
* @param sortPredicates
|
||||
* A list of predicates to sort the returned records by, in order of
|
||||
* priority.
|
||||
*
|
||||
* @param limit
|
||||
* The maximum number of records that should be returned.
|
||||
*
|
||||
* @param effectiveGroups
|
||||
* The identifiers of all groups that should be taken into account
|
||||
* when determining the permissions effectively granted to the user. If
|
||||
* no groups are given, only permissions directly granted to the user
|
||||
* will be used.
|
||||
*
|
||||
* @return
|
||||
* The results of the search performed with the given parameters.
|
||||
*/
|
||||
List<ModelType> searchReadable(@Param("identifier") String identifier,
|
||||
@Param("user") UserModel user,
|
||||
@Param("recordIdentifier") String recordIdentifier,
|
||||
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
|
||||
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
|
||||
@Param("limit") int limit,
|
||||
@Param("effectiveGroups") Collection<String> effectiveGroups);
|
||||
|
||||
}
|
@@ -19,8 +19,9 @@
|
||||
|
||||
package org.apache.guacamole.auth.jdbc.base;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import org.apache.guacamole.net.auth.ActivityRecord;
|
||||
|
||||
/**
|
||||
@@ -33,16 +34,45 @@ public class ModeledActivityRecord implements ActivityRecord {
|
||||
*/
|
||||
private final ActivityRecordModel model;
|
||||
|
||||
/**
|
||||
* The UUID namespace of the type 3 name UUID to generate for the record.
|
||||
* This namespace should correspond to the source of IDs for the model such
|
||||
* that the combination of this namespace with the numeric record ID will
|
||||
* always be unique and deterministic across all activity records,
|
||||
* regardless of record type.
|
||||
*/
|
||||
private final UUID namespace;
|
||||
|
||||
/**
|
||||
* Creates a new ModeledActivityRecord backed by the given model object.
|
||||
* Changes to this record will affect the backing model object, and changes
|
||||
* to the backing model object will affect this record.
|
||||
*
|
||||
*
|
||||
* @param namespace
|
||||
* The UUID namespace of the type 3 name UUID to generate for the
|
||||
* record. This namespace should correspond to the source of IDs for
|
||||
* the model such that the combination of this namespace with the
|
||||
* numeric record ID will always be unique and deterministic across all
|
||||
* activity records, regardless of record type.
|
||||
*
|
||||
* @param model
|
||||
* The model object to use to back this activity record.
|
||||
*/
|
||||
public ModeledActivityRecord(ActivityRecordModel model) {
|
||||
public ModeledActivityRecord(UUID namespace, ActivityRecordModel model) {
|
||||
this.model = model;
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the backing model object. Changes to this record will affect the
|
||||
* backing model object, and changes to the backing model object will
|
||||
* affect this record.
|
||||
*
|
||||
* @return
|
||||
* The backing model object.
|
||||
*/
|
||||
public ActivityRecordModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,4 +100,31 @@ public class ModeledActivityRecord implements ActivityRecord {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
|
||||
Integer id = model.getRecordID();
|
||||
if (id == null)
|
||||
return null;
|
||||
|
||||
return id.toString();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
|
||||
Integer id = model.getRecordID();
|
||||
if (id == null)
|
||||
return null;
|
||||
|
||||
// Convert record ID to a name UUID in the given namespace
|
||||
return UUID.nameUUIDFromBytes(ByteBuffer.allocate(24)
|
||||
.putLong(namespace.getMostSignificantBits())
|
||||
.putLong(namespace.getLeastSignificantBits())
|
||||
.putLong(id)
|
||||
.array());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -29,6 +29,8 @@ import org.apache.guacamole.net.auth.ActivityRecord;
|
||||
import org.apache.guacamole.net.auth.ActivityRecordSet;
|
||||
import org.apache.guacamole.net.auth.ActivityRecordSet.SortableProperty;
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A JDBC implementation of ActivityRecordSet. Calls to asCollection() will
|
||||
@@ -41,6 +43,11 @@ import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
public abstract class ModeledActivityRecordSet<RecordType extends ActivityRecord>
|
||||
extends RestrictedObject implements ActivityRecordSet<RecordType> {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(ModeledActivityRecordSet.class);
|
||||
|
||||
/**
|
||||
* The set of strings that each must occur somewhere within the returned
|
||||
* records, whether within the associated username, an associated date, or
|
||||
@@ -73,6 +80,11 @@ public abstract class ModeledActivityRecordSet<RecordType extends ActivityRecord
|
||||
* @param user
|
||||
* The user retrieving the history.
|
||||
*
|
||||
* @param recordIdentifier
|
||||
* The identifier of the specific history record to retrieve, if not
|
||||
* all matching records. Search terms, etc. will still be applied to
|
||||
* the single record.
|
||||
*
|
||||
* @param requiredContents
|
||||
* The search terms that must be contained somewhere within each of the
|
||||
* returned records.
|
||||
@@ -90,16 +102,35 @@ public abstract class ModeledActivityRecordSet<RecordType extends ActivityRecord
|
||||
* @throws GuacamoleException
|
||||
* If permission to read the history records is denied.
|
||||
*/
|
||||
protected abstract Collection<RecordType> retrieveHistory(
|
||||
AuthenticatedUser user,
|
||||
protected abstract List<RecordType> retrieveHistory(
|
||||
AuthenticatedUser user, String recordIdentifier,
|
||||
Set<ActivityRecordSearchTerm> requiredContents,
|
||||
List<ActivityRecordSortPredicate> sortPredicates,
|
||||
int limit) throws GuacamoleException;
|
||||
|
||||
@Override
|
||||
public RecordType get(String identifier) throws GuacamoleException {
|
||||
|
||||
List<RecordType> records = retrieveHistory(getCurrentUser(),
|
||||
identifier, requiredContents, sortPredicates, limit);
|
||||
|
||||
if (records.isEmpty())
|
||||
return null;
|
||||
|
||||
if (records.size() == 1)
|
||||
return records.get(0);
|
||||
|
||||
logger.warn("Multiple history records match ID \"{}\"! This should "
|
||||
+ "not be possible and may indicate a bug or database "
|
||||
+ "corruption.", identifier);
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<RecordType> asCollection()
|
||||
throws GuacamoleException {
|
||||
return retrieveHistory(getCurrentUser(), requiredContents,
|
||||
return retrieveHistory(getCurrentUser(), null, requiredContents,
|
||||
sortPredicates, limit);
|
||||
}
|
||||
|
||||
|
@@ -19,113 +19,9 @@
|
||||
|
||||
package org.apache.guacamole.auth.jdbc.connection;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.guacamole.auth.jdbc.user.UserModel;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordMapper;
|
||||
|
||||
/**
|
||||
* Mapper for connection record objects.
|
||||
*/
|
||||
public interface ConnectionRecordMapper {
|
||||
|
||||
/**
|
||||
* Returns a collection of all connection records associated with the
|
||||
* connection having the given identifier.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier of the connection whose records are to be retrieved.
|
||||
*
|
||||
* @return
|
||||
* A collection of all connection records associated with the
|
||||
* connection having the given identifier. This collection will be
|
||||
* empty if no such connection exists.
|
||||
*/
|
||||
List<ConnectionRecordModel> select(@Param("identifier") String identifier);
|
||||
|
||||
/**
|
||||
* Inserts the given connection record.
|
||||
*
|
||||
* @param record
|
||||
* The connection record to insert.
|
||||
*
|
||||
* @return
|
||||
* The number of rows inserted.
|
||||
*/
|
||||
int insert(@Param("record") ConnectionRecordModel record);
|
||||
|
||||
/**
|
||||
* Searches for up to <code>limit</code> connection records that contain
|
||||
* the given terms, sorted by the given predicates, regardless of whether
|
||||
* the data they are associated with is is readable by any particular user.
|
||||
* This should only be called on behalf of a system administrator. If
|
||||
* records are needed by a non-administrative user who must have explicit
|
||||
* read rights, use {@link searchReadable()} instead.
|
||||
*
|
||||
* @param identifier
|
||||
* The optional connection identifier to which records should be limited,
|
||||
* or null if all records should be retrieved.
|
||||
*
|
||||
* @param terms
|
||||
* The search terms that must match the returned records.
|
||||
*
|
||||
* @param sortPredicates
|
||||
* A list of predicates to sort the returned records by, in order of
|
||||
* priority.
|
||||
*
|
||||
* @param limit
|
||||
* The maximum number of records that should be returned.
|
||||
*
|
||||
* @return
|
||||
* The results of the search performed with the given parameters.
|
||||
*/
|
||||
List<ConnectionRecordModel> search(@Param("identifier") String identifier,
|
||||
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
|
||||
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
|
||||
@Param("limit") int limit);
|
||||
|
||||
/**
|
||||
* Searches for up to <code>limit</code> connection records that contain
|
||||
* the given terms, sorted by the given predicates. Only records that are
|
||||
* associated with data explicitly readable by the given user will be
|
||||
* returned. If records are needed by a system administrator (who, by
|
||||
* definition, does not need explicit read rights), use {@link search()}
|
||||
* instead.
|
||||
*
|
||||
* @param identifier
|
||||
* The optional connection identifier for which records should be
|
||||
* retrieved, or null if all readable records should be retrieved.
|
||||
*
|
||||
* @param user
|
||||
* The user whose permissions should determine whether a record is
|
||||
* returned.
|
||||
*
|
||||
* @param terms
|
||||
* The search terms that must match the returned records.
|
||||
*
|
||||
* @param sortPredicates
|
||||
* A list of predicates to sort the returned records by, in order of
|
||||
* priority.
|
||||
*
|
||||
* @param limit
|
||||
* The maximum number of records that should be returned.
|
||||
*
|
||||
* @param effectiveGroups
|
||||
* The identifiers of all groups that should be taken into account
|
||||
* when determining the permissions effectively granted to the user. If
|
||||
* no groups are given, only permissions directly granted to the user
|
||||
* will be used.
|
||||
*
|
||||
* @return
|
||||
* The results of the search performed with the given parameters.
|
||||
*/
|
||||
List<ConnectionRecordModel> searchReadable(@Param("identifier") String identifier,
|
||||
@Param("user") UserModel user,
|
||||
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
|
||||
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
|
||||
@Param("limit") int limit,
|
||||
@Param("effectiveGroups") Collection<String> effectiveGroups);
|
||||
|
||||
}
|
||||
public interface ConnectionRecordMapper extends ActivityRecordMapper<ConnectionRecordModel> {}
|
||||
|
@@ -20,9 +20,9 @@
|
||||
package org.apache.guacamole.auth.jdbc.connection;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate;
|
||||
@@ -38,6 +38,15 @@ import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
*/
|
||||
public class ConnectionRecordSet extends ModeledActivityRecordSet<ConnectionRecord> {
|
||||
|
||||
/**
|
||||
* The namespace for the type 3 UUIDs generated for connection history
|
||||
* records. This UUID namespace is itself a type 3 UUID within the "ns:OID"
|
||||
* namespace for the OID "1.3.6.1.4.1.18060.18.2.1.2", which has been
|
||||
* specifically allocated for Apache Guacamole database connection
|
||||
* history records.
|
||||
*/
|
||||
public static final UUID UUID_NAMESPACE = UUID.fromString("8b55f070-95f4-3d31-93ee-9c5845e7aa40");
|
||||
|
||||
/**
|
||||
* Service for managing connection objects.
|
||||
*/
|
||||
@@ -69,14 +78,15 @@ public class ConnectionRecordSet extends ModeledActivityRecordSet<ConnectionReco
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<ConnectionRecord> retrieveHistory(
|
||||
AuthenticatedUser user, Set<ActivityRecordSearchTerm> requiredContents,
|
||||
List<ActivityRecordSortPredicate> sortPredicates, int limit)
|
||||
throws GuacamoleException {
|
||||
protected List<ConnectionRecord> retrieveHistory(
|
||||
AuthenticatedUser user, String recordIdentifier,
|
||||
Set<ActivityRecordSearchTerm> requiredContents,
|
||||
List<ActivityRecordSortPredicate> sortPredicates,
|
||||
int limit) throws GuacamoleException {
|
||||
|
||||
// Retrieve history from database
|
||||
return connectionService.retrieveHistory(identifier, getCurrentUser(),
|
||||
requiredContents, sortPredicates, limit);
|
||||
recordIdentifier, requiredContents, sortPredicates, limit);
|
||||
|
||||
}
|
||||
|
||||
|
@@ -390,40 +390,6 @@ public class ConnectionService extends ModeledChildDirectoryObjectService<Modele
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the connection history of the given connection, including any
|
||||
* active connections.
|
||||
*
|
||||
* @param user
|
||||
* The user retrieving the connection history.
|
||||
*
|
||||
* @param connection
|
||||
* The connection whose history is being retrieved.
|
||||
*
|
||||
* @return
|
||||
* The connection history of the given connection, including any
|
||||
* active connections.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If permission to read the connection history is denied.
|
||||
*/
|
||||
public List<ConnectionRecord> retrieveHistory(ModeledAuthenticatedUser user,
|
||||
ModeledConnection connection) throws GuacamoleException {
|
||||
|
||||
String identifier = connection.getIdentifier();
|
||||
|
||||
// Get current active connections.
|
||||
List<ConnectionRecord> records = new ArrayList<>(tunnelService.getActiveConnections(connection));
|
||||
Collections.reverse(records);
|
||||
|
||||
// Add in the history records.
|
||||
records.addAll(retrieveHistory(identifier, user, Collections.emptyList(),
|
||||
Collections.emptyList(), Integer.MAX_VALUE));
|
||||
|
||||
return records;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the connection history records matching the given criteria.
|
||||
* Retrieves up to <code>limit</code> connection history records matching
|
||||
@@ -437,6 +403,11 @@ public class ConnectionService extends ModeledChildDirectoryObjectService<Modele
|
||||
* @param user
|
||||
* The user retrieving the connection history.
|
||||
*
|
||||
* @param recordIdentifier
|
||||
* The identifier of the specific history record to retrieve, if not
|
||||
* all matching records. Search terms, etc. will still be applied to
|
||||
* the single record.
|
||||
*
|
||||
* @param requiredContents
|
||||
* The search terms that must be contained somewhere within each of the
|
||||
* returned records.
|
||||
@@ -456,7 +427,7 @@ public class ConnectionService extends ModeledChildDirectoryObjectService<Modele
|
||||
* If permission to read the connection history is denied.
|
||||
*/
|
||||
public List<ConnectionRecord> retrieveHistory(String identifier,
|
||||
ModeledAuthenticatedUser user,
|
||||
ModeledAuthenticatedUser user, String recordIdentifier,
|
||||
Collection<ActivityRecordSearchTerm> requiredContents,
|
||||
List<ActivityRecordSortPredicate> sortPredicates, int limit)
|
||||
throws GuacamoleException {
|
||||
@@ -465,54 +436,19 @@ public class ConnectionService extends ModeledChildDirectoryObjectService<Modele
|
||||
|
||||
// Bypass permission checks if the user is privileged
|
||||
if (user.isPrivileged())
|
||||
searchResults = connectionRecordMapper.search(identifier, requiredContents,
|
||||
sortPredicates, limit);
|
||||
searchResults = connectionRecordMapper.search(identifier,
|
||||
recordIdentifier, requiredContents, sortPredicates, limit);
|
||||
|
||||
// Otherwise only return explicitly readable history records
|
||||
else
|
||||
searchResults = connectionRecordMapper.searchReadable(identifier,
|
||||
user.getUser().getModel(), requiredContents, sortPredicates,
|
||||
limit, user.getEffectiveUserGroups());
|
||||
user.getUser().getModel(), recordIdentifier,
|
||||
requiredContents, sortPredicates, limit,
|
||||
user.getEffectiveUserGroups());
|
||||
|
||||
return getObjectInstances(searchResults);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the connection history records matching the given criteria.
|
||||
* Retrieves up to <code>limit</code> connection history records matching
|
||||
* the given terms and sorted by the given predicates. Only history records
|
||||
* associated with data that the given user can read are returned.
|
||||
*
|
||||
* @param user
|
||||
* The user retrieving the connection history.
|
||||
*
|
||||
* @param requiredContents
|
||||
* The search terms that must be contained somewhere within each of the
|
||||
* returned records.
|
||||
*
|
||||
* @param sortPredicates
|
||||
* A list of predicates to sort the returned records by, in order of
|
||||
* priority.
|
||||
*
|
||||
* @param limit
|
||||
* The maximum number of records that should be returned.
|
||||
*
|
||||
* @return
|
||||
* The connection history of the given connection, including any
|
||||
* active connections.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If permission to read the connection history is denied.
|
||||
*/
|
||||
public List<ConnectionRecord> retrieveHistory(ModeledAuthenticatedUser user,
|
||||
Collection<ActivityRecordSearchTerm> requiredContents,
|
||||
List<ActivityRecordSortPredicate> sortPredicates, int limit)
|
||||
throws GuacamoleException {
|
||||
|
||||
return retrieveHistory(null, user, requiredContents, sortPredicates, limit);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the given connection as the given user, using the given
|
||||
|
@@ -43,7 +43,7 @@ public class ModeledConnectionRecord extends ModeledActivityRecord
|
||||
* The model object to use to back this connection record.
|
||||
*/
|
||||
public ModeledConnectionRecord(ConnectionRecordModel model) {
|
||||
super(model);
|
||||
super(ConnectionRecordSet.UUID_NAMESPACE, model);
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@@ -67,4 +67,9 @@ public class ModeledConnectionRecord extends ModeledActivityRecord
|
||||
return model.getSharingProfileName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionRecordModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -25,10 +25,12 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
|
||||
@@ -55,6 +57,7 @@ import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||
import org.apache.guacamole.token.TokenFilter;
|
||||
import org.mybatis.guice.transactional.Transactional;
|
||||
import org.apache.guacamole.auth.jdbc.connection.ConnectionParameterMapper;
|
||||
import org.apache.guacamole.auth.jdbc.sharing.SharedConnectionMap;
|
||||
import org.apache.guacamole.auth.jdbc.sharing.connection.SharedConnectionDefinition;
|
||||
import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile;
|
||||
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileParameterMapper;
|
||||
@@ -109,10 +112,10 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
|
||||
private ConnectionRecordMapper connectionRecordMapper;
|
||||
|
||||
/**
|
||||
* Provider for creating active connection records.
|
||||
* Map of all currently-shared connections.
|
||||
*/
|
||||
@Inject
|
||||
private Provider<ActiveConnectionRecord> activeConnectionRecordProvider;
|
||||
private SharedConnectionMap connectionMap;
|
||||
|
||||
/**
|
||||
* All active connections through the tunnel having a given UUID.
|
||||
@@ -252,33 +255,6 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the given ActiveConnectionRecord to the database. The end date of
|
||||
* the saved record will be populated with the current time.
|
||||
*
|
||||
* @param record
|
||||
* The record to save.
|
||||
*/
|
||||
private void saveConnectionRecord(ActiveConnectionRecord record) {
|
||||
|
||||
// Get associated models
|
||||
ConnectionRecordModel recordModel = new ConnectionRecordModel();
|
||||
|
||||
// Copy user information and timestamps into new record
|
||||
recordModel.setUsername(record.getUsername());
|
||||
recordModel.setConnectionIdentifier(record.getConnectionIdentifier());
|
||||
recordModel.setConnectionName(record.getConnectionName());
|
||||
recordModel.setRemoteHost(record.getRemoteHost());
|
||||
recordModel.setSharingProfileIdentifier(record.getSharingProfileIdentifier());
|
||||
recordModel.setSharingProfileName(record.getSharingProfileName());
|
||||
recordModel.setStartDate(record.getStartDate());
|
||||
recordModel.setEndDate(new Date());
|
||||
|
||||
// Insert connection record
|
||||
connectionRecordMapper.insert(recordModel);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unconfigured GuacamoleSocket that is already connected to
|
||||
* guacd as specified in guacamole.properties, using SSL if necessary.
|
||||
@@ -369,7 +345,9 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
|
||||
activeConnection.invalidate();
|
||||
|
||||
// Remove underlying tunnel from list of active tunnels
|
||||
activeTunnels.remove(activeConnection.getUUID().toString());
|
||||
UUID uuid = activeConnection.getUUID(); // May be null if record not successfully inserted
|
||||
if (uuid != null)
|
||||
activeTunnels.remove(uuid.toString());
|
||||
|
||||
// Get original user
|
||||
RemoteAuthenticatedUser user = activeConnection.getUser();
|
||||
@@ -392,9 +370,11 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
|
||||
// Release any associated group
|
||||
if (activeConnection.hasBalancingGroup())
|
||||
release(user, activeConnection.getBalancingGroup());
|
||||
|
||||
// Save history record to database
|
||||
saveConnectionRecord(activeConnection);
|
||||
|
||||
// Update history record with end date
|
||||
ConnectionRecordModel recordModel = activeConnection.getModel();
|
||||
recordModel.setEndDate(new Date());
|
||||
connectionRecordMapper.updateEndDate(recordModel);
|
||||
|
||||
}
|
||||
|
||||
@@ -438,7 +418,16 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
|
||||
|
||||
// Record new active connection
|
||||
Runnable cleanupTask = new ConnectionCleanupTask(activeConnection);
|
||||
activeTunnels.put(activeConnection.getUUID().toString(), activeConnection);
|
||||
try {
|
||||
connectionRecordMapper.insert(activeConnection.getModel()); // This MUST happen before getUUID() is invoked, to ensure the ID driving the UUID exists
|
||||
activeTunnels.put(activeConnection.getUUID().toString(), activeConnection);
|
||||
}
|
||||
|
||||
// Execute cleanup if connection history could not be updated
|
||||
catch (RuntimeException | Error e) {
|
||||
cleanupTask.run();
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -471,6 +460,10 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
|
||||
|
||||
}
|
||||
|
||||
// Include history record UUID as token
|
||||
tokens = new HashMap<>(tokens);
|
||||
tokens.put("HISTORY_UUID", activeConnection.getUUID().toString());
|
||||
|
||||
// Build token filter containing credential tokens
|
||||
TokenFilter tokenFilter = new TokenFilter();
|
||||
tokenFilter.setTokens(tokens);
|
||||
@@ -638,8 +631,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
|
||||
acquire(user, Collections.singletonList(connection), true);
|
||||
|
||||
// Connect only if the connection was successfully acquired
|
||||
ActiveConnectionRecord connectionRecord = activeConnectionRecordProvider.get();
|
||||
connectionRecord.init(user, connection);
|
||||
ActiveConnectionRecord connectionRecord = new ActiveConnectionRecord(connectionMap, user, connection);
|
||||
return assignGuacamoleTunnel(connectionRecord, info, tokens, false);
|
||||
|
||||
}
|
||||
@@ -685,8 +677,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
|
||||
try {
|
||||
|
||||
// Connect to acquired child
|
||||
ActiveConnectionRecord connectionRecord = activeConnectionRecordProvider.get();
|
||||
connectionRecord.init(user, connectionGroup, connection);
|
||||
ActiveConnectionRecord connectionRecord = new ActiveConnectionRecord(connectionMap, user, connectionGroup, connection);
|
||||
GuacamoleTunnel tunnel = assignGuacamoleTunnel(connectionRecord,
|
||||
info, tokens, connections.size() > 1);
|
||||
|
||||
@@ -741,9 +732,8 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
|
||||
throws GuacamoleException {
|
||||
|
||||
// Create a connection record which describes the shared connection
|
||||
ActiveConnectionRecord connectionRecord = activeConnectionRecordProvider.get();
|
||||
connectionRecord.init(user, definition.getActiveConnection(),
|
||||
definition.getSharingProfile());
|
||||
ActiveConnectionRecord connectionRecord = new ActiveConnectionRecord(connectionMap,
|
||||
user, definition.getActiveConnection(), definition.getSharingProfile());
|
||||
|
||||
// Connect to shared connection described by the created record
|
||||
GuacamoleTunnel tunnel = assignGuacamoleTunnel(connectionRecord, info, tokens, false);
|
||||
|
@@ -19,10 +19,11 @@
|
||||
|
||||
package org.apache.guacamole.auth.jdbc.tunnel;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel;
|
||||
import org.apache.guacamole.auth.jdbc.connection.ModeledConnection;
|
||||
import org.apache.guacamole.auth.jdbc.connection.ModeledConnectionRecord;
|
||||
import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
|
||||
import org.apache.guacamole.auth.jdbc.sharing.SharedConnectionMap;
|
||||
import org.apache.guacamole.auth.jdbc.sharing.SharedObjectManager;
|
||||
@@ -31,7 +32,6 @@ import org.apache.guacamole.auth.jdbc.user.RemoteAuthenticatedUser;
|
||||
import org.apache.guacamole.net.AbstractGuacamoleTunnel;
|
||||
import org.apache.guacamole.net.GuacamoleSocket;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
|
||||
|
||||
/**
|
||||
@@ -39,41 +39,31 @@ import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
* the associated connection has not yet ended, getEndDate() will always return
|
||||
* null. The associated start date will be the time of this objects creation.
|
||||
*/
|
||||
public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
public class ActiveConnectionRecord extends ModeledConnectionRecord {
|
||||
|
||||
/**
|
||||
* The user that connected to the connection associated with this connection
|
||||
* record.
|
||||
*/
|
||||
private RemoteAuthenticatedUser user;
|
||||
private final RemoteAuthenticatedUser user;
|
||||
|
||||
/**
|
||||
* The balancing group from which the associated connection was chosen, if
|
||||
* any. If no balancing group was used, this will be null.
|
||||
*/
|
||||
private ModeledConnectionGroup balancingGroup;
|
||||
private final ModeledConnectionGroup balancingGroup;
|
||||
|
||||
/**
|
||||
* The connection associated with this connection record.
|
||||
*/
|
||||
private ModeledConnection connection;
|
||||
private final ModeledConnection connection;
|
||||
|
||||
/**
|
||||
* The sharing profile that was used to access the connection associated
|
||||
* with this connection record. If the connection was accessed directly
|
||||
* (without involving a sharing profile), this will be null.
|
||||
*/
|
||||
private ModeledSharingProfile sharingProfile;
|
||||
|
||||
/**
|
||||
* The time this connection record was created.
|
||||
*/
|
||||
private final Date startDate = new Date();
|
||||
|
||||
/**
|
||||
* The UUID that will be assigned to the underlying tunnel.
|
||||
*/
|
||||
private final UUID uuid = UUID.randomUUID();
|
||||
private final ModeledSharingProfile sharingProfile;
|
||||
|
||||
/**
|
||||
* The connection ID of the connection as determined by guacd, not to be
|
||||
@@ -91,8 +81,7 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
/**
|
||||
* Map of all currently-shared connections.
|
||||
*/
|
||||
@Inject
|
||||
private SharedConnectionMap connectionMap;
|
||||
private final SharedConnectionMap connectionMap;
|
||||
|
||||
/**
|
||||
* Manager which tracks all share keys associated with this connection
|
||||
@@ -111,7 +100,57 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes this connection record, associating it with the given user,
|
||||
* Creates a new connection record model object, associating it with the
|
||||
* given user, connection, and sharing profile. The given sharing profile
|
||||
* MUST be the sharing profile that was used to share access to the given
|
||||
* connection. The start date of this connection record will be the time of
|
||||
* its creation. No end date will be assigned.
|
||||
*
|
||||
* @param user
|
||||
* The user that connected to the connection associated with this
|
||||
* connection record.
|
||||
*
|
||||
* @param connection
|
||||
* The connection to associate with this connection record.
|
||||
*
|
||||
* @param sharingProfile
|
||||
* The sharing profile that was used to share access to the given
|
||||
* connection, or null if no sharing profile was used.
|
||||
*
|
||||
* @return
|
||||
* A new connection record model object associated with the given user,
|
||||
* connection, and sharing profile, and having the current date/time as
|
||||
* its start date.
|
||||
*/
|
||||
private static ConnectionRecordModel createModel(RemoteAuthenticatedUser user,
|
||||
ModeledConnection connection,
|
||||
ModeledSharingProfile sharingProfile) {
|
||||
|
||||
// Create model object representing an active connection that started
|
||||
// at the current time ...
|
||||
ConnectionRecordModel recordModel = new ConnectionRecordModel();
|
||||
recordModel.setStartDate(new Date());
|
||||
|
||||
// ... was established by the given user ...
|
||||
recordModel.setUsername(user.getIdentifier());
|
||||
recordModel.setRemoteHost(user.getRemoteHost());
|
||||
|
||||
// ... to the given connection ...
|
||||
recordModel.setConnectionIdentifier(connection.getIdentifier());
|
||||
recordModel.setConnectionName(connection.getName());
|
||||
|
||||
// ... using the given sharing profile (if any)
|
||||
if (sharingProfile != null) {
|
||||
recordModel.setSharingProfileIdentifier(sharingProfile.getIdentifier());
|
||||
recordModel.setSharingProfileName(sharingProfile.getName());
|
||||
}
|
||||
|
||||
return recordModel;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ActiveConnectionRecord associated with the given user,
|
||||
* connection, balancing connection group, and sharing profile. The given
|
||||
* balancing connection group MUST be the connection group from which the
|
||||
* given connection was chosen, and the given sharing profile MUST be the
|
||||
@@ -119,6 +158,10 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
* The start date of this connection record will be the time of its
|
||||
* creation.
|
||||
*
|
||||
* @param connectionMap
|
||||
* The SharedConnectionMap instance tracking all active shared
|
||||
* connections.
|
||||
*
|
||||
* @param user
|
||||
* The user that connected to the connection associated with this
|
||||
* connection record.
|
||||
@@ -134,10 +177,13 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
* The sharing profile that was used to share access to the given
|
||||
* connection, or null if no sharing profile was used.
|
||||
*/
|
||||
private void init(RemoteAuthenticatedUser user,
|
||||
public ActiveConnectionRecord(SharedConnectionMap connectionMap,
|
||||
RemoteAuthenticatedUser user,
|
||||
ModeledConnectionGroup balancingGroup,
|
||||
ModeledConnection connection,
|
||||
ModeledSharingProfile sharingProfile) {
|
||||
super(createModel(user, connection, sharingProfile));
|
||||
this.connectionMap = connectionMap;
|
||||
this.user = user;
|
||||
this.balancingGroup = balancingGroup;
|
||||
this.connection = connection;
|
||||
@@ -145,12 +191,16 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this connection record, associating it with the given user,
|
||||
* Creates a new ActiveConnectionRecord associated with the given user,
|
||||
* connection, and balancing connection group. The given balancing
|
||||
* connection group MUST be the connection group from which the given
|
||||
* connection was chosen. The start date of this connection record will be
|
||||
* the time of its creation.
|
||||
*
|
||||
* @param connectionMap
|
||||
* The SharedConnectionMap instance tracking all active shared
|
||||
* connections.
|
||||
*
|
||||
* @param user
|
||||
* The user that connected to the connection associated with this
|
||||
* connection record.
|
||||
@@ -161,17 +211,22 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
* @param connection
|
||||
* The connection to associate with this connection record.
|
||||
*/
|
||||
public void init(RemoteAuthenticatedUser user,
|
||||
public ActiveConnectionRecord(SharedConnectionMap connectionMap,
|
||||
RemoteAuthenticatedUser user,
|
||||
ModeledConnectionGroup balancingGroup,
|
||||
ModeledConnection connection) {
|
||||
init(user, balancingGroup, connection, null);
|
||||
this(connectionMap, user, balancingGroup, connection, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this connection record, associating it with the given user
|
||||
* Creates a new ActiveConnectionRecord associated with the given user,
|
||||
* and connection. The start date of this connection record will be the time
|
||||
* of its creation.
|
||||
*
|
||||
* @param connectionMap
|
||||
* The SharedConnectionMap instance tracking all active shared
|
||||
* connections.
|
||||
*
|
||||
* @param user
|
||||
* The user that connected to the connection associated with this
|
||||
* connection record.
|
||||
@@ -179,18 +234,22 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
* @param connection
|
||||
* The connection to associate with this connection record.
|
||||
*/
|
||||
public void init(RemoteAuthenticatedUser user,
|
||||
ModeledConnection connection) {
|
||||
init(user, null, connection);
|
||||
public ActiveConnectionRecord(SharedConnectionMap connectionMap,
|
||||
RemoteAuthenticatedUser user, ModeledConnection connection) {
|
||||
this(connectionMap, user, null, connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this connection record, associating it with the given user,
|
||||
* Creates a new ActiveConnectionRecord associated with the given user,
|
||||
* active connection, and sharing profile. The given sharing profile MUST be
|
||||
* the sharing profile that was used to share access to the given
|
||||
* connection. The start date of this connection record will be the time of
|
||||
* its creation.
|
||||
*
|
||||
* @param connectionMap
|
||||
* The SharedConnectionMap instance tracking all active shared
|
||||
* connections.
|
||||
*
|
||||
* @param user
|
||||
* The user that connected to the connection associated with this
|
||||
* connection record.
|
||||
@@ -204,10 +263,11 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
* connection, or null if no sharing profile should be used (access to
|
||||
* the connection is unrestricted).
|
||||
*/
|
||||
public void init(RemoteAuthenticatedUser user,
|
||||
public ActiveConnectionRecord(SharedConnectionMap connectionMap,
|
||||
RemoteAuthenticatedUser user,
|
||||
ActiveConnectionRecord activeConnection,
|
||||
ModeledSharingProfile sharingProfile) {
|
||||
init(user, null, activeConnection.getConnection(), sharingProfile);
|
||||
this(connectionMap, user, null, activeConnection.getConnection(), sharingProfile);
|
||||
this.connectionID = activeConnection.getConnectionID();
|
||||
}
|
||||
|
||||
@@ -286,63 +346,6 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
return sharingProfile == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionIdentifier() {
|
||||
return connection.getIdentifier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionName() {
|
||||
return connection.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSharingProfileIdentifier() {
|
||||
|
||||
// Return sharing profile identifier if known
|
||||
if (sharingProfile != null)
|
||||
return sharingProfile.getIdentifier();
|
||||
|
||||
// No associated sharing profile
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSharingProfileName() {
|
||||
|
||||
// Return sharing profile name if known
|
||||
if (sharingProfile != null)
|
||||
return sharingProfile.getName();
|
||||
|
||||
// No associated sharing profile
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getEndDate() {
|
||||
|
||||
// Active connections have not yet ended
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteHost() {
|
||||
return user.getRemoteHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return user.getIdentifier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return tunnel != null && tunnel.isOpen();
|
||||
@@ -387,7 +390,7 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
return ActiveConnectionRecord.this.getUUID();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -401,18 +404,6 @@ public class ActiveConnectionRecord implements ConnectionRecord {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UUID of the underlying tunnel. If there is no underlying
|
||||
* tunnel, this will be the UUID assigned to the underlying tunnel when the
|
||||
* tunnel is set.
|
||||
*
|
||||
* @return
|
||||
* The current or future UUID of the underlying tunnel.
|
||||
*/
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection ID of the in-progress connection as determined by
|
||||
* guacd, not to be confused with the connection identifier determined by
|
||||
|
@@ -291,7 +291,7 @@ public class ModeledUserContext extends RestrictedObject
|
||||
// Record logout time only if login time was recorded
|
||||
if (userRecord != null) {
|
||||
userRecord.setEndDate(new Date());
|
||||
userRecordMapper.update(userRecord);
|
||||
userRecordMapper.updateEndDate(userRecord);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -19,124 +19,10 @@
|
||||
|
||||
package org.apache.guacamole.auth.jdbc.user;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordMapper;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordModel;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* Mapper for user login activity records.
|
||||
*/
|
||||
public interface UserRecordMapper {
|
||||
|
||||
/**
|
||||
* Returns a collection of all user login records associated with the user
|
||||
* having the given username.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user whose login records are to be retrieved.
|
||||
*
|
||||
* @return
|
||||
* A collection of all user login records associated with the user
|
||||
* having the given username. This collection will be empty if no such
|
||||
* user exists.
|
||||
*/
|
||||
List<ActivityRecordModel> select(@Param("username") String username);
|
||||
|
||||
/**
|
||||
* Inserts the given user login record.
|
||||
*
|
||||
* @param record
|
||||
* The user login record to insert.
|
||||
*
|
||||
* @return
|
||||
* The number of rows inserted.
|
||||
*/
|
||||
int insert(@Param("record") ActivityRecordModel record);
|
||||
|
||||
/**
|
||||
* Updates the given user login record.
|
||||
*
|
||||
* @param record
|
||||
* The user login record to update.
|
||||
*
|
||||
* @return
|
||||
* The number of rows updated.
|
||||
*/
|
||||
int update(@Param("record") ActivityRecordModel record);
|
||||
|
||||
/**
|
||||
* Searches for up to <code>limit</code> user login records that contain
|
||||
* the given terms, sorted by the given predicates, regardless of whether
|
||||
* the data they are associated with is is readable by any particular user.
|
||||
* This should only be called on behalf of a system administrator. If
|
||||
* records are needed by a non-administrative user who must have explicit
|
||||
* read rights, use {@link searchReadable()} instead.
|
||||
*
|
||||
* @param username
|
||||
* The optional username to which records should be limited, or null
|
||||
* if all records should be retrieved.
|
||||
*
|
||||
* @param terms
|
||||
* The search terms that must match the returned records.
|
||||
*
|
||||
* @param sortPredicates
|
||||
* A list of predicates to sort the returned records by, in order of
|
||||
* priority.
|
||||
*
|
||||
* @param limit
|
||||
* The maximum number of records that should be returned.
|
||||
*
|
||||
* @return
|
||||
* The results of the search performed with the given parameters.
|
||||
*/
|
||||
List<ActivityRecordModel> search(@Param("username") String username,
|
||||
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
|
||||
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
|
||||
@Param("limit") int limit);
|
||||
|
||||
/**
|
||||
* Searches for up to <code>limit</code> user login records that contain
|
||||
* the given terms, sorted by the given predicates. Only records that are
|
||||
* associated with data explicitly readable by the given user will be
|
||||
* returned. If records are needed by a system administrator (who, by
|
||||
* definition, does not need explicit read rights), use {@link search()}
|
||||
* instead.
|
||||
*
|
||||
* @param username
|
||||
* The optional username to which records should be limited, or null
|
||||
* if all readable records should be retrieved.
|
||||
*
|
||||
* @param user
|
||||
* The user whose permissions should determine whether a record is
|
||||
* returned.
|
||||
*
|
||||
* @param terms
|
||||
* The search terms that must match the returned records.
|
||||
*
|
||||
* @param sortPredicates
|
||||
* A list of predicates to sort the returned records by, in order of
|
||||
* priority.
|
||||
*
|
||||
* @param limit
|
||||
* The maximum number of records that should be returned.
|
||||
*
|
||||
* @param effectiveGroups
|
||||
* The identifiers of all groups that should be taken into account
|
||||
* when determining the permissions effectively granted to the user. If
|
||||
* no groups are given, only permissions directly granted to the user
|
||||
* will be used.
|
||||
*
|
||||
* @return
|
||||
* The results of the search performed with the given parameters.
|
||||
*/
|
||||
List<ActivityRecordModel> searchReadable(@Param("username") String username,
|
||||
@Param("user") UserModel user,
|
||||
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
|
||||
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
|
||||
@Param("limit") int limit,
|
||||
@Param("effectiveGroups") Collection<String> effectiveGroups);
|
||||
|
||||
}
|
||||
public interface UserRecordMapper extends ActivityRecordMapper<ActivityRecordModel> {}
|
||||
|
@@ -20,9 +20,9 @@
|
||||
package org.apache.guacamole.auth.jdbc.user;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm;
|
||||
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate;
|
||||
@@ -38,6 +38,15 @@ import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
*/
|
||||
public class UserRecordSet extends ModeledActivityRecordSet<ActivityRecord> {
|
||||
|
||||
/**
|
||||
* The namespace for the type 3 UUIDs generated for user history records.
|
||||
* This UUID namespace is itself a type 3 UUID within the "ns:OID"
|
||||
* namespace for the OID "1.3.6.1.4.1.18060.18.2.1.1", which has been
|
||||
* specifically allocated for Apache Guacamole database user history
|
||||
* records.
|
||||
*/
|
||||
public static final UUID UUID_NAMESPACE = UUID.fromString("e104741a-c949-3947-8d79-f3f2cdce7d6f");
|
||||
|
||||
/**
|
||||
* Service for managing user objects.
|
||||
*/
|
||||
@@ -70,14 +79,15 @@ public class UserRecordSet extends ModeledActivityRecordSet<ActivityRecord> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<ActivityRecord> retrieveHistory(
|
||||
AuthenticatedUser user, Set<ActivityRecordSearchTerm> requiredContents,
|
||||
protected List<ActivityRecord> retrieveHistory(
|
||||
AuthenticatedUser user, String recordIdentifier,
|
||||
Set<ActivityRecordSearchTerm> requiredContents,
|
||||
List<ActivityRecordSortPredicate> sortPredicates, int limit)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Retrieve history from database
|
||||
return userService.retrieveHistory(identifier, getCurrentUser(),
|
||||
requiredContents, sortPredicates, limit);
|
||||
recordIdentifier, requiredContents, sortPredicates, limit);
|
||||
|
||||
}
|
||||
|
||||
|
@@ -546,7 +546,7 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
|
||||
* A connection record object which is backed by the given model.
|
||||
*/
|
||||
protected ActivityRecord getObjectInstance(ActivityRecordModel model) {
|
||||
return new ModeledActivityRecord(model);
|
||||
return new ModeledActivityRecord(UserRecordSet.UUID_NAMESPACE, model);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -572,32 +572,6 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the login history of the given user, including any active
|
||||
* sessions.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The user retrieving the login history.
|
||||
*
|
||||
* @param user
|
||||
* The user whose history is being retrieved.
|
||||
*
|
||||
* @return
|
||||
* The login history of the given user, including any active sessions.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If permission to read the login history is denied.
|
||||
*/
|
||||
public List<ActivityRecord> retrieveHistory(ModeledAuthenticatedUser authenticatedUser,
|
||||
ModeledUser user) throws GuacamoleException {
|
||||
|
||||
String username = user.getIdentifier();
|
||||
|
||||
return retrieveHistory(username, authenticatedUser, Collections.emptyList(),
|
||||
Collections.emptyList(), Integer.MAX_VALUE);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves user login history records matching the given criteria.
|
||||
* Retrieves up to <code>limit</code> user history records matching the
|
||||
@@ -611,6 +585,11 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
|
||||
* @param user
|
||||
* The user retrieving the login history.
|
||||
*
|
||||
* @param recordIdentifier
|
||||
* The identifier of the specific history record to retrieve, if not
|
||||
* all matching records. Search terms, etc. will still be applied to
|
||||
* the single record.
|
||||
*
|
||||
* @param requiredContents
|
||||
* The search terms that must be contained somewhere within each of the
|
||||
* returned records.
|
||||
@@ -629,7 +608,7 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
|
||||
* If permission to read the user login history is denied.
|
||||
*/
|
||||
public List<ActivityRecord> retrieveHistory(String username,
|
||||
ModeledAuthenticatedUser user,
|
||||
ModeledAuthenticatedUser user, String recordIdentifier,
|
||||
Collection<ActivityRecordSearchTerm> requiredContents,
|
||||
List<ActivityRecordSortPredicate> sortPredicates, int limit)
|
||||
throws GuacamoleException {
|
||||
@@ -638,52 +617,18 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
|
||||
|
||||
// Bypass permission checks if the user is privileged
|
||||
if (user.isPrivileged())
|
||||
searchResults = userRecordMapper.search(username, requiredContents,
|
||||
sortPredicates, limit);
|
||||
searchResults = userRecordMapper.search(username, recordIdentifier,
|
||||
requiredContents, sortPredicates, limit);
|
||||
|
||||
// Otherwise only return explicitly readable history records
|
||||
else
|
||||
searchResults = userRecordMapper.searchReadable(username,
|
||||
user.getUser().getModel(),
|
||||
requiredContents, sortPredicates, limit, user.getEffectiveUserGroups());
|
||||
user.getUser().getModel(), recordIdentifier,
|
||||
requiredContents, sortPredicates, limit,
|
||||
user.getEffectiveUserGroups());
|
||||
|
||||
return getObjectInstances(searchResults);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves user login history records matching the given criteria.
|
||||
* Retrieves up to <code>limit</code> user history records matching the
|
||||
* given terms and sorted by the given predicates. Only history records
|
||||
* associated with data that the given user can read are returned.
|
||||
*
|
||||
* @param user
|
||||
* The user retrieving the login history.
|
||||
*
|
||||
* @param requiredContents
|
||||
* The search terms that must be contained somewhere within each of the
|
||||
* returned records.
|
||||
*
|
||||
* @param sortPredicates
|
||||
* A list of predicates to sort the returned records by, in order of
|
||||
* priority.
|
||||
*
|
||||
* @param limit
|
||||
* The maximum number of records that should be returned.
|
||||
*
|
||||
* @return
|
||||
* The login history of the given user, including any active sessions.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If permission to read the user login history is denied.
|
||||
*/
|
||||
public List<ActivityRecord> retrieveHistory(ModeledAuthenticatedUser user,
|
||||
Collection<ActivityRecordSearchTerm> requiredContents,
|
||||
List<ActivityRecordSortPredicate> sortPredicates, int limit)
|
||||
throws GuacamoleException {
|
||||
|
||||
return retrieveHistory(null, user, requiredContents, sortPredicates, limit);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@
|
||||
|
||||
<!-- Result mapper for system permissions -->
|
||||
<resultMap id="ConnectionRecordResultMap" type="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
<id column="history_id" property="recordID" jdbcType="INTEGER"/>
|
||||
<result column="connection_id" property="connectionIdentifier" jdbcType="INTEGER"/>
|
||||
<result column="connection_name" property="connectionName" jdbcType="VARCHAR"/>
|
||||
<result column="remote_host" property="remoteHost" jdbcType="VARCHAR"/>
|
||||
@@ -36,30 +37,9 @@
|
||||
<result column="end_date" property="endDate" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- Select all connection records from a given connection -->
|
||||
<select id="select" resultMap="ConnectionRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_connection_history.connection_id,
|
||||
guacamole_connection_history.connection_name,
|
||||
guacamole_connection_history.remote_host,
|
||||
guacamole_connection_history.sharing_profile_id,
|
||||
guacamole_connection_history.sharing_profile_name,
|
||||
guacamole_connection_history.user_id,
|
||||
guacamole_connection_history.username,
|
||||
guacamole_connection_history.start_date,
|
||||
guacamole_connection_history.end_date
|
||||
FROM guacamole_connection_history
|
||||
WHERE
|
||||
guacamole_connection_history.connection_id = #{identifier,jdbcType=VARCHAR}
|
||||
ORDER BY
|
||||
guacamole_connection_history.start_date DESC,
|
||||
guacamole_connection_history.end_date DESC
|
||||
|
||||
</select>
|
||||
|
||||
<!-- Insert the given connection record -->
|
||||
<insert id="insert" parameterType="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="record.recordID"
|
||||
parameterType="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
|
||||
INSERT INTO guacamole_connection_history (
|
||||
connection_id,
|
||||
@@ -90,10 +70,18 @@
|
||||
|
||||
</insert>
|
||||
|
||||
<!-- Update the given connection record, assigning an end date -->
|
||||
<update id="updateEndDate" parameterType="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
UPDATE guacamole_connection_history
|
||||
SET end_date = #{record.endDate,jdbcType=TIMESTAMP}
|
||||
WHERE history_id = #{record.recordID,jdbcType=INTEGER}
|
||||
</update>
|
||||
|
||||
<!-- Search for specific connection records -->
|
||||
<select id="search" resultMap="ConnectionRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_connection_history.history_id,
|
||||
guacamole_connection_history.connection_id,
|
||||
guacamole_connection_history.connection_name,
|
||||
guacamole_connection_history.remote_host,
|
||||
@@ -109,9 +97,13 @@
|
||||
|
||||
<!-- Search terms -->
|
||||
<where>
|
||||
|
||||
|
||||
<if test="recordIdentifier != null">
|
||||
guacamole_connection_history.history_id = #{recordIdentifier,jdbcType=VARCHAR}
|
||||
</if>
|
||||
|
||||
<if test="identifier != null">
|
||||
guacamole_connection_history.connection_id = #{identifier,jdbcType=VARCHAR}
|
||||
AND guacamole_connection_history.connection_id = #{identifier,jdbcType=VARCHAR}
|
||||
</if>
|
||||
|
||||
<foreach collection="terms" item="term" open=" AND " separator=" AND ">
|
||||
@@ -159,6 +151,7 @@
|
||||
<select id="searchReadable" resultMap="ConnectionRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_connection_history.history_id,
|
||||
guacamole_connection_history.connection_id,
|
||||
guacamole_connection_history.connection_name,
|
||||
guacamole_connection_history.remote_host,
|
||||
@@ -174,9 +167,13 @@
|
||||
|
||||
<!-- Search terms -->
|
||||
<where>
|
||||
|
||||
|
||||
<if test="recordIdentifier != null">
|
||||
guacamole_connection_history.history_id = #{recordIdentifier,jdbcType=VARCHAR}
|
||||
</if>
|
||||
|
||||
<!-- Restrict to readable connections -->
|
||||
guacamole_connection_history.connection_id IN (
|
||||
AND guacamole_connection_history.connection_id IN (
|
||||
<include refid="org.apache.guacamole.auth.jdbc.connection.ConnectionMapper.getReadableIDs">
|
||||
<property name="entityID" value="#{user.entityID,jdbcType=INTEGER}"/>
|
||||
<property name="groups" value="effectiveGroups"/>
|
||||
|
@@ -33,26 +33,6 @@
|
||||
<result column="end_date" property="endDate" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- Select all user records from a given user -->
|
||||
<select id="select" resultMap="UserRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_user_history.remote_host,
|
||||
guacamole_user_history.user_id,
|
||||
guacamole_user_history.username,
|
||||
guacamole_user_history.start_date,
|
||||
guacamole_user_history.end_date
|
||||
FROM guacamole_user_history
|
||||
JOIN guacamole_user ON guacamole_user_history.user_id = guacamole_user.user_id
|
||||
JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id
|
||||
WHERE
|
||||
guacamole_entity.name = #{username,jdbcType=VARCHAR}
|
||||
ORDER BY
|
||||
guacamole_user_history.start_date DESC,
|
||||
guacamole_user_history.end_date DESC
|
||||
|
||||
</select>
|
||||
|
||||
<!-- Insert the given user record -->
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="record.recordID"
|
||||
parameterType="org.apache.guacamole.auth.jdbc.base.ActivityRecordModel">
|
||||
@@ -78,18 +58,10 @@
|
||||
|
||||
</insert>
|
||||
|
||||
<!-- Update the given user record -->
|
||||
<update id="update" parameterType="org.apache.guacamole.auth.jdbc.base.ActivityRecordModel">
|
||||
<!-- Update the given user record, assigning an end date -->
|
||||
<update id="updateEndDate" parameterType="org.apache.guacamole.auth.jdbc.base.ActivityRecordModel">
|
||||
UPDATE guacamole_user_history
|
||||
SET remote_host = #{record.remoteHost,jdbcType=VARCHAR},
|
||||
user_id = (SELECT user_id FROM guacamole_user
|
||||
JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id
|
||||
WHERE
|
||||
guacamole_entity.name = #{record.username,jdbcType=VARCHAR}
|
||||
AND guacamole_entity.type = 'USER'),
|
||||
username = #{record.username,jdbcType=VARCHAR},
|
||||
start_date = #{record.startDate,jdbcType=TIMESTAMP},
|
||||
end_date = #{record.endDate,jdbcType=TIMESTAMP}
|
||||
SET end_date = #{record.endDate,jdbcType=TIMESTAMP}
|
||||
WHERE history_id = #{record.recordID,jdbcType=INTEGER}
|
||||
</update>
|
||||
|
||||
@@ -97,6 +69,7 @@
|
||||
<select id="search" resultMap="UserRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_user_history.history_id,
|
||||
guacamole_user_history.remote_host,
|
||||
guacamole_user_history.user_id,
|
||||
guacamole_user_history.username,
|
||||
@@ -107,8 +80,8 @@
|
||||
<!-- Search terms -->
|
||||
<where>
|
||||
|
||||
<if test="username != null">
|
||||
guacamole_user_history.username = #{username,jdbcType=VARCHAR}
|
||||
<if test="identifier != null">
|
||||
guacamole_user_history.username = #{identifier,jdbcType=VARCHAR}
|
||||
</if>
|
||||
|
||||
<foreach collection="terms" item="term" open=" AND " separator=" AND ">
|
||||
@@ -153,6 +126,7 @@
|
||||
<select id="searchReadable" resultMap="UserRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_user_history.history_id,
|
||||
guacamole_user_history.remote_host,
|
||||
guacamole_user_history.user_id,
|
||||
guacamole_user_history.username,
|
||||
@@ -171,8 +145,8 @@
|
||||
</include>
|
||||
)
|
||||
|
||||
<if test="username != null">
|
||||
AND guacamole_entity.name = #{username,jdbcType=VARCHAR}
|
||||
<if test="identifier != null">
|
||||
AND guacamole_entity.name = #{identifier,jdbcType=VARCHAR}
|
||||
</if>
|
||||
|
||||
<foreach collection="terms" item="term" open=" AND " separator=" AND ">
|
||||
|
@@ -25,6 +25,7 @@
|
||||
|
||||
<!-- Result mapper for system permissions -->
|
||||
<resultMap id="ConnectionRecordResultMap" type="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
<id column="history_id" property="recordID" jdbcType="INTEGER"/>
|
||||
<result column="connection_id" property="connectionIdentifier" jdbcType="INTEGER"/>
|
||||
<result column="connection_name" property="connectionName" jdbcType="VARCHAR"/>
|
||||
<result column="remote_host" property="remoteHost" jdbcType="VARCHAR"/>
|
||||
@@ -36,30 +37,9 @@
|
||||
<result column="end_date" property="endDate" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- Select all connection records from a given connection -->
|
||||
<select id="select" resultMap="ConnectionRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_connection_history.connection_id,
|
||||
guacamole_connection_history.connection_name,
|
||||
guacamole_connection_history.remote_host,
|
||||
guacamole_connection_history.sharing_profile_id,
|
||||
guacamole_connection_history.sharing_profile_name,
|
||||
guacamole_connection_history.user_id,
|
||||
guacamole_connection_history.username,
|
||||
guacamole_connection_history.start_date,
|
||||
guacamole_connection_history.end_date
|
||||
FROM guacamole_connection_history
|
||||
WHERE
|
||||
guacamole_connection_history.connection_id = #{identifier,jdbcType=INTEGER}::integer
|
||||
ORDER BY
|
||||
guacamole_connection_history.start_date DESC,
|
||||
guacamole_connection_history.end_date DESC
|
||||
|
||||
</select>
|
||||
|
||||
<!-- Insert the given connection record -->
|
||||
<insert id="insert" parameterType="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="record.recordID"
|
||||
parameterType="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
|
||||
INSERT INTO guacamole_connection_history (
|
||||
connection_id,
|
||||
@@ -90,10 +70,18 @@
|
||||
|
||||
</insert>
|
||||
|
||||
<!-- Update the given connection record, assigning an end date -->
|
||||
<update id="updateEndDate" parameterType="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
UPDATE guacamole_connection_history
|
||||
SET end_date = #{record.endDate,jdbcType=TIMESTAMP}
|
||||
WHERE history_id = #{record.recordID,jdbcType=INTEGER}::integer
|
||||
</update>
|
||||
|
||||
<!-- Search for specific connection records -->
|
||||
<select id="search" resultMap="ConnectionRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_connection_history.history_id,
|
||||
guacamole_connection_history.connection_id,
|
||||
guacamole_connection_history.connection_name,
|
||||
guacamole_connection_history.remote_host,
|
||||
@@ -107,11 +95,15 @@
|
||||
|
||||
<!-- Search terms -->
|
||||
<where>
|
||||
|
||||
<if test="identifier != null">
|
||||
guacamole_connection_history.connection_id = #{identifier,jdbcType=INTEGER}::integer
|
||||
|
||||
<if test="recordIdentifier != null">
|
||||
guacamole_connection_history.history_id = #{recordIdentifier,jdbcType=INTEGER}::integer
|
||||
</if>
|
||||
|
||||
|
||||
<if test="identifier != null">
|
||||
AND guacamole_connection_history.connection_id = #{identifier,jdbcType=INTEGER}::integer
|
||||
</if>
|
||||
|
||||
<foreach collection="terms" item="term" open=" AND " separator=" AND ">
|
||||
(
|
||||
|
||||
@@ -157,6 +149,7 @@
|
||||
<select id="searchReadable" resultMap="ConnectionRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_connection_history.history_id,
|
||||
guacamole_connection_history.connection_id,
|
||||
guacamole_connection_history.connection_name,
|
||||
guacamole_connection_history.remote_host,
|
||||
@@ -172,9 +165,13 @@
|
||||
|
||||
<!-- Search terms -->
|
||||
<where>
|
||||
|
||||
|
||||
<if test="recordIdentifier != null">
|
||||
guacamole_connection_history.history_id = #{recordIdentifier,jdbcType=INTEGER}::integer
|
||||
</if>
|
||||
|
||||
<!-- Restrict to readable connections -->
|
||||
guacamole_connection_history.connection_id IN (
|
||||
AND guacamole_connection_history.connection_id IN (
|
||||
<include refid="org.apache.guacamole.auth.jdbc.connection.ConnectionMapper.getReadableIDs">
|
||||
<property name="entityID" value="#{user.entityID,jdbcType=INTEGER}"/>
|
||||
<property name="groups" value="effectiveGroups"/>
|
||||
|
@@ -33,26 +33,6 @@
|
||||
<result column="end_date" property="endDate" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- Select all user records from a given user -->
|
||||
<select id="select" resultMap="UserRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_user_history.remote_host,
|
||||
guacamole_user_history.user_id,
|
||||
guacamole_user_history.username,
|
||||
guacamole_user_history.start_date,
|
||||
guacamole_user_history.end_date
|
||||
FROM guacamole_user_history
|
||||
JOIN guacamole_user ON guacamole_user_history.user_id = guacamole_user.user_id
|
||||
JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id
|
||||
WHERE
|
||||
guacamole_entity.name = #{username,jdbcType=VARCHAR}
|
||||
ORDER BY
|
||||
guacamole_user_history.start_date DESC,
|
||||
guacamole_user_history.end_date DESC
|
||||
|
||||
</select>
|
||||
|
||||
<!-- Insert the given user record -->
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="record.recordID"
|
||||
parameterType="org.apache.guacamole.auth.jdbc.base.ActivityRecordModel">
|
||||
@@ -78,18 +58,10 @@
|
||||
|
||||
</insert>
|
||||
|
||||
<!-- Update the given user record -->
|
||||
<update id="update" parameterType="org.apache.guacamole.auth.jdbc.base.ActivityRecordModel">
|
||||
<!-- Update the given user record, assigning an end date -->
|
||||
<update id="updateEndDate" parameterType="org.apache.guacamole.auth.jdbc.base.ActivityRecordModel">
|
||||
UPDATE guacamole_user_history
|
||||
SET remote_host = #{record.remoteHost,jdbcType=VARCHAR},
|
||||
user_id = (SELECT user_id FROM guacamole_user
|
||||
JOIN guacamole_entity ON guacamole_user.entity_id = guacamole_entity.entity_id
|
||||
WHERE
|
||||
guacamole_entity.name = #{record.username,jdbcType=VARCHAR}
|
||||
AND guacamole_entity.type = 'USER'::guacamole_entity_type),
|
||||
username = #{record.username,jdbcType=VARCHAR},
|
||||
start_date = #{record.startDate,jdbcType=TIMESTAMP},
|
||||
end_date = #{record.endDate,jdbcType=TIMESTAMP}
|
||||
SET end_date = #{record.endDate,jdbcType=TIMESTAMP}
|
||||
WHERE history_id = #{record.recordID,jdbcType=INTEGER}::integer
|
||||
</update>
|
||||
|
||||
@@ -97,6 +69,7 @@
|
||||
<select id="search" resultMap="UserRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_user_history.history_id,
|
||||
guacamole_user_history.remote_host,
|
||||
guacamole_user_history.user_id,
|
||||
guacamole_user_history.username,
|
||||
@@ -107,8 +80,8 @@
|
||||
<!-- Search terms -->
|
||||
<where>
|
||||
|
||||
<if test="username != null">
|
||||
guacamole_user_history.username = #{username,jdbcType=VARCHAR}
|
||||
<if test="identifier != null">
|
||||
guacamole_user_history.username = #{identifier,jdbcType=VARCHAR}
|
||||
</if>
|
||||
|
||||
<foreach collection="terms" item="term" open=" AND " separator=" AND ">
|
||||
@@ -153,6 +126,7 @@
|
||||
<select id="searchReadable" resultMap="UserRecordResultMap">
|
||||
|
||||
SELECT
|
||||
guacamole_user_history.history_id,
|
||||
guacamole_user_history.remote_host,
|
||||
guacamole_user_history.user_id,
|
||||
guacamole_user_history.username,
|
||||
@@ -171,8 +145,8 @@
|
||||
</include>
|
||||
)
|
||||
|
||||
<if test="username != null">
|
||||
AND guacamole_entity.name = #{username,jdbcType=VARCHAR}
|
||||
<if test="identifier != null">
|
||||
AND guacamole_entity.name = #{identifier,jdbcType=VARCHAR}
|
||||
</if>
|
||||
|
||||
<foreach collection="terms" item="term" open=" AND " separator=" AND ">
|
||||
|
@@ -25,6 +25,7 @@
|
||||
|
||||
<!-- Result mapper for system permissions -->
|
||||
<resultMap id="ConnectionRecordResultMap" type="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
<id column="history_id" property="recordID" jdbcType="INTEGER"/>
|
||||
<result column="connection_id" property="connectionIdentifier" jdbcType="INTEGER"/>
|
||||
<result column="connection_name" property="connectionName" jdbcType="VARCHAR"/>
|
||||
<result column="remote_host" property="remoteHost" jdbcType="VARCHAR"/>
|
||||
@@ -36,30 +37,16 @@
|
||||
<result column="end_date" property="endDate" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- Select all connection records from a given connection -->
|
||||
<select id="select" resultMap="ConnectionRecordResultMap">
|
||||
|
||||
SELECT
|
||||
[guacamole_connection_history].connection_id,
|
||||
[guacamole_connection_history].connection_name,
|
||||
[guacamole_connection_history].remote_host,
|
||||
[guacamole_connection_history].sharing_profile_id,
|
||||
[guacamole_connection_history].sharing_profile_name,
|
||||
[guacamole_connection_history].user_id,
|
||||
[guacamole_connection_history].username,
|
||||
[guacamole_connection_history].start_date,
|
||||
[guacamole_connection_history].end_date
|
||||
FROM [guacamole_connection_history]
|
||||
WHERE
|
||||
[guacamole_connection_history].connection_id = #{identifier,jdbcType=INTEGER}
|
||||
ORDER BY
|
||||
[guacamole_connection_history].start_date DESC,
|
||||
[guacamole_connection_history].end_date DESC
|
||||
|
||||
</select>
|
||||
<!-- Update the given connection record, assigning an end date -->
|
||||
<update id="updateEndDate" parameterType="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
UPDATE [guacamole_connection_history]
|
||||
SET end_date = #{record.endDate,jdbcType=TIMESTAMP}
|
||||
WHERE history_id = #{record.recordID,jdbcType=INTEGER}
|
||||
</update>
|
||||
|
||||
<!-- Insert the given connection record -->
|
||||
<insert id="insert" parameterType="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="record.recordID"
|
||||
parameterType="org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel">
|
||||
|
||||
INSERT INTO [guacamole_connection_history] (
|
||||
connection_id,
|
||||
@@ -94,6 +81,7 @@
|
||||
<select id="search" resultMap="ConnectionRecordResultMap">
|
||||
|
||||
SELECT TOP (#{limit,jdbcType=INTEGER})
|
||||
[guacamole_connection_history].history_id,
|
||||
[guacamole_connection_history].connection_id,
|
||||
[guacamole_connection_history].connection_name,
|
||||
[guacamole_connection_history].remote_host,
|
||||
@@ -107,9 +95,13 @@
|
||||
|
||||
<!-- Search terms -->
|
||||
<where>
|
||||
|
||||
|
||||
<if test="recordIdentifier != null">
|
||||
[guacamole_connection_history].history_id = #{recordIdentifier,jdbcType=INTEGER}
|
||||
</if>
|
||||
|
||||
<if test="identifier != null">
|
||||
[guacamole_connection_history].connection_id = #{identifier,jdbcType=INTEGER}
|
||||
AND [guacamole_connection_history].connection_id = #{identifier,jdbcType=INTEGER}
|
||||
</if>
|
||||
|
||||
<foreach collection="terms" item="term" open=" AND " separator=" AND ">
|
||||
@@ -155,6 +147,7 @@
|
||||
<select id="searchReadable" resultMap="ConnectionRecordResultMap">
|
||||
|
||||
SELECT TOP (#{limit,jdbcType=INTEGER})
|
||||
[guacamole_connection_history].history_id,
|
||||
[guacamole_connection_history].connection_id,
|
||||
[guacamole_connection_history].connection_name,
|
||||
[guacamole_connection_history].remote_host,
|
||||
@@ -170,9 +163,13 @@
|
||||
|
||||
<!-- Search terms -->
|
||||
<where>
|
||||
|
||||
|
||||
<if test="recordIdentifier != null">
|
||||
[guacamole_connection_history].history_id = #{recordIdentifier,jdbcType=INTEGER}
|
||||
</if>
|
||||
|
||||
<!-- Restrict to readable connections -->
|
||||
[guacamole_connection_history].connection_id IN (
|
||||
AND [guacamole_connection_history].connection_id IN (
|
||||
<include refid="org.apache.guacamole.auth.jdbc.connection.ConnectionMapper.getReadableIDs">
|
||||
<property name="entityID" value="#{user.entityID,jdbcType=INTEGER}"/>
|
||||
<property name="groups" value="effectiveGroups"/>
|
||||
|
@@ -33,26 +33,6 @@
|
||||
<result column="end_date" property="endDate" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- Select all user records from a given user -->
|
||||
<select id="select" resultMap="UserRecordResultMap">
|
||||
|
||||
SELECT
|
||||
[guacamole_user_history].remote_host,
|
||||
[guacamole_user_history].user_id,
|
||||
[guacamole_user_history].username,
|
||||
[guacamole_user_history].start_date,
|
||||
[guacamole_user_history].end_date
|
||||
FROM [guacamole_user_history]
|
||||
JOIN [guacamole_user] ON [guacamole_user_history].user_id = [guacamole_user].user_id
|
||||
JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id
|
||||
WHERE
|
||||
[guacamole_entity].name = #{username,jdbcType=VARCHAR}
|
||||
ORDER BY
|
||||
[guacamole_user_history].start_date DESC,
|
||||
[guacamole_user_history].end_date DESC
|
||||
|
||||
</select>
|
||||
|
||||
<!-- Insert the given user record -->
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="record.recordID"
|
||||
parameterType="org.apache.guacamole.auth.jdbc.base.ActivityRecordModel">
|
||||
@@ -78,18 +58,10 @@
|
||||
|
||||
</insert>
|
||||
|
||||
<!-- Update the given user record -->
|
||||
<update id="update" parameterType="org.apache.guacamole.auth.jdbc.base.ActivityRecordModel">
|
||||
<!-- Update the given user record, assigning an end date -->
|
||||
<update id="updateEndDate" parameterType="org.apache.guacamole.auth.jdbc.base.ActivityRecordModel">
|
||||
UPDATE [guacamole_user_history]
|
||||
SET remote_host = #{record.remoteHost,jdbcType=VARCHAR},
|
||||
user_id = (SELECT user_id FROM [guacamole_user]
|
||||
JOIN [guacamole_entity] ON [guacamole_user].entity_id = [guacamole_entity].entity_id
|
||||
WHERE
|
||||
[guacamole_entity].name = #{record.username,jdbcType=VARCHAR}
|
||||
AND [guacamole_entity].type = 'USER'),
|
||||
username = #{record.username,jdbcType=VARCHAR},
|
||||
start_date = #{record.startDate,jdbcType=TIMESTAMP},
|
||||
end_date = #{record.endDate,jdbcType=TIMESTAMP}
|
||||
SET end_date = #{record.endDate,jdbcType=TIMESTAMP}
|
||||
WHERE history_id = #{record.recordID,jdbcType=INTEGER}
|
||||
</update>
|
||||
|
||||
@@ -97,6 +69,7 @@
|
||||
<select id="search" resultMap="UserRecordResultMap">
|
||||
|
||||
SELECT TOP (#{limit,jdbcType=INTEGER})
|
||||
[guacamole_user_history].history_id,
|
||||
[guacamole_user_history].remote_host,
|
||||
[guacamole_user_history].user_id,
|
||||
[guacamole_user_history].username,
|
||||
@@ -107,8 +80,8 @@
|
||||
<!-- Search terms -->
|
||||
<where>
|
||||
|
||||
<if test="username != null">
|
||||
[guacamole_user_history].username = #{username,jdbcType=VARCHAR}
|
||||
<if test="identifier != null">
|
||||
[guacamole_user_history].username = #{identifier,jdbcType=VARCHAR}
|
||||
</if>
|
||||
|
||||
<foreach collection="terms" item="term" open=" AND " separator=" AND ">
|
||||
@@ -151,6 +124,7 @@
|
||||
<select id="searchReadable" resultMap="UserRecordResultMap">
|
||||
|
||||
SELECT TOP (#{limit,jdbcType=INTEGER})
|
||||
[guacamole_user_history].history_id,
|
||||
[guacamole_user_history].remote_host,
|
||||
[guacamole_user_history].user_id,
|
||||
[guacamole_user_history].username,
|
||||
@@ -169,8 +143,8 @@
|
||||
</include>
|
||||
)
|
||||
|
||||
<if test="username != null">
|
||||
AND [guacamole_entity].name = #{username,jdbcType=VARCHAR}
|
||||
<if test="identifier != null">
|
||||
AND [guacamole_entity].name = #{identifier,jdbcType=VARCHAR}
|
||||
</if>
|
||||
|
||||
<foreach collection="terms" item="term" open=" AND " separator=" AND ">
|
||||
|
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,89 @@
|
||||
/*
|
||||
* 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 java.io.File;
|
||||
import org.apache.guacamole.history.user.HistoryUserContext;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.environment.LocalEnvironment;
|
||||
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;
|
||||
import org.apache.guacamole.properties.FileGuacamoleProperty;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* 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";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the directory that should be searched for session recordings
|
||||
* associated with history entries.
|
||||
*
|
||||
* @return
|
||||
* The directory that should be searched for session recordings
|
||||
* associated with history entries.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the "recording-search-path" property cannot be parsed.
|
||||
*/
|
||||
public static File getRecordingSearchPath() throws GuacamoleException {
|
||||
Environment environment = LocalEnvironment.getInstance();
|
||||
return environment.getProperty(RECORDING_SEARCH_PATH,
|
||||
DEFAULT_RECORDING_SEARCH_PATH);
|
||||
}
|
||||
|
||||
@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,284 @@
|
||||
/*
|
||||
* 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.history.HistoryAuthenticationProvider;
|
||||
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.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 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);
|
||||
|
||||
String uuid = record.getUUID().toString();
|
||||
File recordingFile = new File(HistoryAuthenticationProvider.getRecordingSearchPath(), 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,122 @@
|
||||
/*
|
||||
* 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 java.util.Collections;
|
||||
import java.util.Map;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.history.HistoryAuthenticationProvider;
|
||||
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.ConnectionGroup;
|
||||
import org.apache.guacamole.net.auth.ConnectionRecord;
|
||||
import org.apache.guacamole.net.auth.DecoratingDirectory;
|
||||
import org.apache.guacamole.net.auth.Directory;
|
||||
import org.apache.guacamole.net.auth.TokenInjectingUserContext;
|
||||
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 TokenInjectingUserContext {
|
||||
|
||||
/**
|
||||
* The name of the parameter token that contains the automatically-searched
|
||||
* history recording/log path.
|
||||
*/
|
||||
private static final String HISTORY_PATH_TOKEN_NAME = "HISTORY_PATH";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tokens which should be added to an in-progress call to
|
||||
* connect() for any Connectable object.
|
||||
*
|
||||
* @return
|
||||
* The tokens which should be added to the in-progress call to
|
||||
* connect().
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the relevant tokens cannot be generated.
|
||||
*/
|
||||
private Map<String, String> getTokens() throws GuacamoleException {
|
||||
return Collections.singletonMap(HISTORY_PATH_TOKEN_NAME,
|
||||
HistoryAuthenticationProvider.getRecordingSearchPath().getAbsolutePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, String> getTokens(ConnectionGroup connectionGroup)
|
||||
throws GuacamoleException {
|
||||
return getTokens();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, String> getTokens(Connection connection)
|
||||
throws GuacamoleException {
|
||||
return getTokens();
|
||||
}
|
||||
|
||||
@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>
|
||||
|
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 org.apache.guacamole.language.TranslatableMessage;
|
||||
|
||||
/**
|
||||
* Base implementation of an ActivityLog, providing storage and simple
|
||||
* getters/setters for its main properties.
|
||||
*/
|
||||
public abstract class AbstractActivityLog implements ActivityLog {
|
||||
|
||||
/**
|
||||
* The type of this ActivityLog.
|
||||
*/
|
||||
private final Type type;
|
||||
|
||||
/**
|
||||
* A human-readable description of this log.
|
||||
*/
|
||||
private final TranslatableMessage description;
|
||||
|
||||
/**
|
||||
* Creates a new AbstractActivityLog having the given type and
|
||||
* human-readable description.
|
||||
*
|
||||
* @param type
|
||||
* The type of this ActivityLog.
|
||||
*
|
||||
* @param description
|
||||
* A human-readable message that describes this log.
|
||||
*/
|
||||
public AbstractActivityLog(Type type, TranslatableMessage description) {
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TranslatableMessage getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.io.InputStream;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.language.TranslatableMessage;
|
||||
|
||||
/**
|
||||
* An arbitrary log of an activity whose content may be exposed to a user with
|
||||
* sufficient privileges. Types of content that might be exposed in this way
|
||||
* include textual server logs, Guacamole session recordings, and typescripts.
|
||||
*/
|
||||
public interface ActivityLog {
|
||||
|
||||
/**
|
||||
* The value returned by {@link #getSize()} if the number of available
|
||||
* bytes within {@link #getContent()} is unknown.
|
||||
*/
|
||||
public static final long UNKNOWN_SIZE = -1;
|
||||
|
||||
/**
|
||||
* All possible types of {@link ActivityLog}.
|
||||
*/
|
||||
enum Type {
|
||||
|
||||
/**
|
||||
* A Guacamole session recording in the form of a Guacamole protocol
|
||||
* dump.
|
||||
*/
|
||||
GUACAMOLE_SESSION_RECORDING("application/octet-stream"),
|
||||
|
||||
/**
|
||||
* A text log from a server-side process, such as the Guacamole web
|
||||
* application or guacd.
|
||||
*/
|
||||
SERVER_LOG("text/plain"),
|
||||
|
||||
/**
|
||||
* A text session recording in the form of a standard typescript.
|
||||
*/
|
||||
TYPESCRIPT("application/octet-stream"),
|
||||
|
||||
/**
|
||||
* The timing file related to a typescript.
|
||||
*/
|
||||
TYPESCRIPT_TIMING("text/plain");
|
||||
|
||||
/**
|
||||
* The MIME type of the content of an activity log of this type.
|
||||
*/
|
||||
private final String contentType;
|
||||
|
||||
/**
|
||||
* Creates a new Type that may be associated with content having the
|
||||
* given MIME type.
|
||||
*
|
||||
* @param contentType
|
||||
* The MIME type of the content of an activity log of this type.
|
||||
*/
|
||||
Type(String contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MIME type of the content of an activity log of this
|
||||
* type, as might be sent via the HTTP "Content-Type" header.
|
||||
*
|
||||
* @return
|
||||
* The MIME type of the content of an activity log of this type.
|
||||
*/
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of this activity log. The type of an activity log
|
||||
* dictates how its content should be interpreted or exposed.
|
||||
*
|
||||
* @return
|
||||
* The type of this activity log.
|
||||
*/
|
||||
Type getType();
|
||||
|
||||
/**
|
||||
* Returns a human-readable message that describes this log. This message
|
||||
* should provide sufficient information for a user with access to this
|
||||
* log to understand its context and/or purpose.
|
||||
*
|
||||
* @return
|
||||
* A human-readable message that describes this log.
|
||||
*/
|
||||
TranslatableMessage getDescription();
|
||||
|
||||
/**
|
||||
* Returns the number of bytes available for reading within the content of
|
||||
* this log. If this value is unknown, -1 ({@link #UNKNOWN_SIZE}) should be
|
||||
* returned.
|
||||
*
|
||||
* @return
|
||||
* The number of bytes available for reading within the content of
|
||||
* this log, or -1 ({@link #UNKNOWN_SIZE}) if this value is unknown.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the size of the content of this log cannot be determined due to
|
||||
* an error.
|
||||
*/
|
||||
long getSize() throws GuacamoleException;
|
||||
|
||||
/**
|
||||
* Returns an InputStream that allows the content of this log to be read.
|
||||
* Multiple instances of this InputStream may be open at any given time. It
|
||||
* is the responsibility of the caller to close the returned InputStream.
|
||||
*
|
||||
* @return
|
||||
* An InputStream that allows the content of this log to be read.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the content of this log cannot be read due to an error.
|
||||
*/
|
||||
InputStream getContent() throws GuacamoleException;
|
||||
|
||||
}
|
@@ -19,13 +19,16 @@
|
||||
|
||||
package org.apache.guacamole.net.auth;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A logging record describing when a user started and ended a particular
|
||||
* activity.
|
||||
*/
|
||||
public interface ActivityRecord {
|
||||
public interface ActivityRecord extends ReadableAttributes {
|
||||
|
||||
/**
|
||||
* Returns the date and time the activity began.
|
||||
@@ -75,4 +78,52 @@ public interface ActivityRecord {
|
||||
*/
|
||||
public boolean isActive();
|
||||
|
||||
/**
|
||||
* Returns the unique identifier assigned to this record, if any. If this
|
||||
* record is not uniquely identifiable, this may be null. If provided, this
|
||||
* unique identifier MUST be unique across all {@link ActivityRecord}
|
||||
* objects within the same {@link ActivityRecordSet}.
|
||||
*
|
||||
* @return
|
||||
* The unique identifier assigned to this record, or null if this
|
||||
* record has no such identifier.
|
||||
*/
|
||||
public default String getIdentifier() {
|
||||
UUID uuid = getUUID();
|
||||
return uuid != null ? uuid.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a UUID that uniquely identifies this record. If provided, this
|
||||
* UUID MUST be deterministic and unique across all {@link ActivityRecord}
|
||||
* objects within the same {@link ActivityRecordSet}, and SHOULD be unique
|
||||
* across all {@link ActivityRecord} objects.
|
||||
*
|
||||
* @return
|
||||
* A UUID that uniquely identifies this record, or null if no such
|
||||
* unique identifier exists.
|
||||
*/
|
||||
public default UUID getUUID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map of logs related to this record and accessible by the
|
||||
* current user, such as Guacamole session recordings. Each log is
|
||||
* associated with a corresponding, arbitrary, unique name. If the user
|
||||
* does not have access to any logs, or if no logs are available, this may
|
||||
* be an empty map.
|
||||
*
|
||||
* @return
|
||||
* A Map of logs related to this record.
|
||||
*/
|
||||
public default Map<String, ActivityLog> getLogs() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public default Map<String, String> getAttributes() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -57,6 +57,30 @@ public interface ActivityRecordSet<RecordType extends ActivityRecord> {
|
||||
*/
|
||||
Collection<RecordType> asCollection() throws GuacamoleException;
|
||||
|
||||
/**
|
||||
* Returns the record having the given unique identifier, if records within
|
||||
* this set have unique identifiers. If records within this set do not have
|
||||
* defined unique identifiers, this function has no effect.
|
||||
*
|
||||
* @param identifier
|
||||
* The unique identifier of the record to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The record having the given unique identifier, or null if there is
|
||||
* no such record.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the record.
|
||||
*/
|
||||
default RecordType get(String identifier) throws GuacamoleException {
|
||||
return asCollection().stream()
|
||||
.filter((record) -> {
|
||||
String recordIdentifier = record.getIdentifier();
|
||||
return recordIdentifier != null && recordIdentifier.equals(identifier);
|
||||
})
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subset of records which contain the given value. The
|
||||
* properties and semantics involved with determining whether a particular
|
||||
|
@@ -22,21 +22,10 @@ package org.apache.guacamole.net.auth;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An object which is associated with a set of arbitrary attributes, defined
|
||||
* as name/value pairs.
|
||||
* An object which is associated with a set of arbitrary attributes that may
|
||||
* be modifiable, defined as name/value pairs.
|
||||
*/
|
||||
public interface Attributes {
|
||||
|
||||
/**
|
||||
* Returns all attributes associated with this object. The returned map
|
||||
* may not be modifiable.
|
||||
*
|
||||
* @return
|
||||
* A map of all attribute identifiers to their corresponding values,
|
||||
* for all attributes associated with this object, which may not be
|
||||
* modifiable.
|
||||
*/
|
||||
Map<String, String> getAttributes();
|
||||
public interface Attributes extends ReadableAttributes {
|
||||
|
||||
/**
|
||||
* Sets the given attributes. If an attribute within the map is not
|
||||
|
@@ -19,6 +19,9 @@
|
||||
|
||||
package org.apache.guacamole.net.auth;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
|
||||
/**
|
||||
* A logging record describing when a user started and ended usage of a
|
||||
* particular connection.
|
||||
@@ -70,4 +73,15 @@ public interface ConnectionRecord extends ActivityRecord {
|
||||
*/
|
||||
public String getSharingProfileName();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>If implemented, this UUID SHOULD be identical to the UUID of the
|
||||
* {@link GuacamoleTunnel} originally returned when the connection was
|
||||
* established to allow extensions and/or the web application to
|
||||
* automatically associate connection information with corresponding
|
||||
* history records, such as log messages and session recordings.
|
||||
*/
|
||||
@Override
|
||||
public UUID getUUID();
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
|
||||
/**
|
||||
* ActivityRecordSet implementation which simplifies decorating the records
|
||||
* within an underlying ActivityRecordSet. The decorate() function must be
|
||||
* implemented to define how each record is decorated. As ActivityRecordSets
|
||||
* are read-only, there is no need to define an undecorate() function as
|
||||
* required by {@link DecoratingDirectory}.
|
||||
*
|
||||
* @param <RecordType>
|
||||
* The type of records stored within this ActivityRecordSet.
|
||||
*/
|
||||
public abstract class DecoratingActivityRecordSet<RecordType extends ActivityRecord>
|
||||
extends DelegatingActivityRecordSet<RecordType> {
|
||||
|
||||
/**
|
||||
* Creates a new DecoratingActivityRecordSet which decorates the records
|
||||
* within the given set.
|
||||
*
|
||||
* @param recordSet
|
||||
* The ActivityRecordSet whose records are being decorated.
|
||||
*/
|
||||
public DecoratingActivityRecordSet(ActivityRecordSet<RecordType> recordSet) {
|
||||
super(recordSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a record retrieved from a ActivityRecordSet which originates from
|
||||
* a different AuthenticationProvider, returns an identical type of record
|
||||
* optionally wrapped with additional information, functionality, etc. If
|
||||
* this record set chooses to decorate the record provided, it is up to the
|
||||
* implementation of that decorated record to properly pass through
|
||||
* operations as appropriate. All records retrieved from this
|
||||
* DecoratingActivityRecordSet will first be passed through this function.
|
||||
*
|
||||
* @param record
|
||||
* A record from a ActivityRecordSet which originates from a different
|
||||
* AuthenticationProvider.
|
||||
*
|
||||
* @return
|
||||
* A record which may have been decorated by this
|
||||
* DecoratingActivityRecordSet. If the record was not decorated, the
|
||||
* original, unmodified record may be returned instead.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the provided record cannot be decorated due to an error.
|
||||
*/
|
||||
protected abstract RecordType decorate(RecordType record)
|
||||
throws GuacamoleException;
|
||||
|
||||
/**
|
||||
* Given an ActivityRecordSet which originates from a different
|
||||
* AuthenticationProvider, returns an identical type of record set
|
||||
* optionally wrapped with additional information, functionality, etc. If
|
||||
* this record set chooses to decorate the record set provided, it is up to
|
||||
* the implementation of that decorated record set to properly pass through
|
||||
* operations as appropriate. All record sets retrieved from this
|
||||
* DecoratingActivityRecordSet will first be passed through this function,
|
||||
* such as those returned by {@link #limit(int)} and similar functions.
|
||||
* <p>
|
||||
* By default, this function will wrap any provided ActivityRecordSet in a
|
||||
* simple, anonymous instance of DecoratingActivityRecordSet that delegates
|
||||
* to the decorate() implementations of this DecoratingActivityRecordSet.
|
||||
* <strong>This default behavior may need to be overridden if the
|
||||
* DecoratingActivityRecordSet implementation maintains any internal
|
||||
* state.</strong>
|
||||
*
|
||||
* @param recordSet
|
||||
* An ActivityRecordSet which originates from a different
|
||||
* AuthenticationProvider.
|
||||
*
|
||||
* @return
|
||||
* A record set which may have been decorated by this
|
||||
* DecoratingActivityRecordSet. If the record set was not decorated, the
|
||||
* original, unmodified record set may be returned instead, however
|
||||
* beware that this may result in records within the set no longer
|
||||
* being decorated.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the provided record set cannot be decorated due to an error.
|
||||
*/
|
||||
protected ActivityRecordSet<RecordType> decorate(ActivityRecordSet<RecordType> recordSet)
|
||||
throws GuacamoleException {
|
||||
final DecoratingActivityRecordSet<RecordType> decorator = this;
|
||||
return new DecoratingActivityRecordSet<RecordType>(recordSet) {
|
||||
|
||||
@Override
|
||||
protected RecordType decorate(RecordType record) throws GuacamoleException {
|
||||
return decorator.decorate(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActivityRecordSet<RecordType> decorate(ActivityRecordSet<RecordType> recordSet)
|
||||
throws GuacamoleException {
|
||||
return decorator.decorate(recordSet);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecordType get(String string) throws GuacamoleException {
|
||||
|
||||
RecordType record = super.get(string);
|
||||
if (record != null)
|
||||
return decorate(record);
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityRecordSet<RecordType> sort(SortableProperty property,
|
||||
boolean desc) throws GuacamoleException {
|
||||
return decorate(super.sort(property, desc));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityRecordSet<RecordType> limit(int limit) throws GuacamoleException {
|
||||
return decorate(super.limit(limit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityRecordSet<RecordType> contains(String value) throws GuacamoleException {
|
||||
return decorate(super.contains(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<RecordType> asCollection() throws GuacamoleException {
|
||||
|
||||
Collection<RecordType> records = super.asCollection();
|
||||
|
||||
List<RecordType> decoratedRecords = new ArrayList<>(records.size());
|
||||
for (RecordType record : records)
|
||||
decoratedRecords.add(decorate(record));
|
||||
|
||||
return decoratedRecords;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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.auth;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* ActivityRecord implementation which simply delegates all function calls to an
|
||||
* underlying ActivityRecord.
|
||||
*/
|
||||
public class DelegatingActivityRecord implements ActivityRecord {
|
||||
|
||||
/**
|
||||
* The wrapped ActivityRecord.
|
||||
*/
|
||||
private final ActivityRecord record;
|
||||
|
||||
/**
|
||||
* Wraps the given ActivityRecord such that all function calls against this
|
||||
* DelegatingActivityRecord will be delegated to it.
|
||||
*
|
||||
* @param record
|
||||
* The record to wrap.
|
||||
*/
|
||||
public DelegatingActivityRecord(ActivityRecord record) {
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying ActivityRecord wrapped by this
|
||||
* DelegatingActivityRecord.
|
||||
*
|
||||
* @return
|
||||
* The ActivityRecord wrapped by this DelegatingActivityRecord.
|
||||
*/
|
||||
protected ActivityRecord getDelegateActivityRecord() {
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getStartDate() {
|
||||
return record.getStartDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getEndDate() {
|
||||
return record.getEndDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteHost() {
|
||||
return record.getRemoteHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return record.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return record.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return record.getIdentifier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return record.getUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ActivityLog> getLogs() {
|
||||
return record.getLogs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return record.getAttributes();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.util.Collection;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
|
||||
/**
|
||||
* ActivityRecordSet implementation which simply delegates all function calls
|
||||
* to an underlying ActivityRecordSet.
|
||||
*
|
||||
* @param <RecordType>
|
||||
* The type of ActivityRecord contained within this set.
|
||||
*/
|
||||
public class DelegatingActivityRecordSet<RecordType extends ActivityRecord>
|
||||
implements ActivityRecordSet<RecordType> {
|
||||
|
||||
/**
|
||||
* The wrapped ActivityRecordSet.
|
||||
*/
|
||||
private final ActivityRecordSet<RecordType> recordSet;
|
||||
|
||||
/**
|
||||
* Wraps the given ActivityRecordSet such that all function calls against this
|
||||
* DelegatingActivityRecordSet will be delegated to it.
|
||||
*
|
||||
* @param recordSet
|
||||
* The ActivityRecordSet to wrap.
|
||||
*/
|
||||
public DelegatingActivityRecordSet(ActivityRecordSet<RecordType> recordSet) {
|
||||
this.recordSet = recordSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying ActivityRecordSet wrapped by this
|
||||
* DelegatingActivityRecordSet.
|
||||
*
|
||||
* @return
|
||||
* The ActivityRecordSet wrapped by this DelegatingActivityRecordSet.
|
||||
*/
|
||||
protected ActivityRecordSet<RecordType> getDelegateActivityRecordSet() {
|
||||
return recordSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecordType get(String identifier) throws GuacamoleException {
|
||||
return recordSet.get(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<RecordType> asCollection() throws GuacamoleException {
|
||||
return recordSet.asCollection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityRecordSet<RecordType> contains(String value) throws GuacamoleException {
|
||||
return recordSet.contains(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityRecordSet<RecordType> limit(int limit) throws GuacamoleException {
|
||||
return recordSet.limit(limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityRecordSet<RecordType> sort(SortableProperty property,
|
||||
boolean desc) throws GuacamoleException {
|
||||
return recordSet.sort(property, desc);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* ConnectionRecord implementation which simply delegates all function calls to
|
||||
* an underlying ConnectionRecord.
|
||||
*/
|
||||
public class DelegatingConnectionRecord extends DelegatingActivityRecord
|
||||
implements ConnectionRecord {
|
||||
|
||||
/**
|
||||
* The wrapped ConnectionRecord.
|
||||
*/
|
||||
private final ConnectionRecord record;
|
||||
|
||||
/**
|
||||
* Wraps the given ConnectionRecord such that all function calls against
|
||||
* this DelegatingConnectionRecord will be delegated to it.
|
||||
*
|
||||
* @param record
|
||||
* The record to wrap.
|
||||
*/
|
||||
public DelegatingConnectionRecord(ConnectionRecord record) {
|
||||
super(record);
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying ConnectionRecord wrapped by this
|
||||
* DelegatingConnectionRecord.
|
||||
*
|
||||
* @return
|
||||
* The ConnectionRecord wrapped by this DelegatingConnectionRecord.
|
||||
*/
|
||||
protected ConnectionRecord getDelegateConnectionRecord() {
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionIdentifier() {
|
||||
return record.getConnectionIdentifier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionName() {
|
||||
return record.getConnectionName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSharingProfileIdentifier() {
|
||||
return record.getSharingProfileIdentifier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSharingProfileName() {
|
||||
return record.getSharingProfileName();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.apache.guacamole.language.TranslatableMessage;
|
||||
|
||||
/**
|
||||
* ActivityLog implementation that exposes the content of a local file.
|
||||
*/
|
||||
public class FileActivityLog extends AbstractActivityLog {
|
||||
|
||||
/**
|
||||
* The File providing the content of this log.
|
||||
*/
|
||||
private final File content;
|
||||
|
||||
/**
|
||||
* Creates a new FileActivityLog that exposes the content of the given
|
||||
* local file as an {@link ActivityLog}.
|
||||
*
|
||||
* @param type
|
||||
* The type of this ActivityLog.
|
||||
*
|
||||
* @param description
|
||||
* A human-readable message that describes this log.
|
||||
*
|
||||
* @param content
|
||||
* The File that should be used to provide the content of this log.
|
||||
*/
|
||||
public FileActivityLog(Type type, TranslatableMessage description, File content) {
|
||||
super(type, description);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() throws GuacamoleException {
|
||||
return content.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getContent() throws GuacamoleException {
|
||||
try {
|
||||
return new FileInputStream(content);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new GuacamoleResourceNotFoundException("Associated file "
|
||||
+ "does not exist or cannot be read.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.util.Map;
|
||||
|
||||
/**
|
||||
* An object which is associated with a set of arbitrary attributes, defined
|
||||
* as name/value pairs.
|
||||
*/
|
||||
public interface ReadableAttributes {
|
||||
|
||||
/**
|
||||
* Returns all attributes associated with this object. The returned map
|
||||
* may not be modifiable.
|
||||
*
|
||||
* @return
|
||||
* A map of all attribute identifiers to their corresponding values,
|
||||
* for all attributes associated with this object, which may not be
|
||||
* modifiable.
|
||||
*/
|
||||
Map<String, String> getAttributes();
|
||||
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Service which defines the ActivityLog class.
|
||||
*/
|
||||
angular.module('rest').factory('ActivityLog', [function defineActivityLog() {
|
||||
|
||||
/**
|
||||
* The object returned by REST API calls when representing a log or
|
||||
* recording associated with a connection's usage history, such as a
|
||||
* session recording or typescript.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ActivityLog|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ActivityLog.
|
||||
*/
|
||||
var ActivityLog = function ActivityLog(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* The type of this ActivityLog.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = template.type;
|
||||
|
||||
/**
|
||||
* A human-readable description of this log.
|
||||
*
|
||||
* @type {TranslatableMessage}
|
||||
*/
|
||||
this.description = template.description;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* All possible types of ActivityLog.
|
||||
*
|
||||
* @type {!object.<string, string>}
|
||||
*/
|
||||
ActivityLog.Type = {
|
||||
|
||||
/**
|
||||
* A Guacamole session recording in the form of a Guacamole protocol
|
||||
* dump.
|
||||
*/
|
||||
GUACAMOLE_SESSION_RECORDING : 'GUACAMOLE_SESSION_RECORDING',
|
||||
|
||||
/**
|
||||
* A text log from a server-side process, such as the Guacamole web
|
||||
* application or guacd.
|
||||
*/
|
||||
SERVER_LOG : 'SERVER_LOG',
|
||||
|
||||
/**
|
||||
* A text session recording in the form of a standard typescript.
|
||||
*/
|
||||
TYPESCRIPT : 'TYPESCRIPT',
|
||||
|
||||
/**
|
||||
* The timing file related to a typescript.
|
||||
*/
|
||||
TYPESCRIPT_TIMING : 'TYPESCRIPT_TIMING'
|
||||
|
||||
};
|
||||
|
||||
return ActivityLog;
|
||||
|
||||
}]);
|
@@ -38,6 +38,23 @@ angular.module('rest').factory('ConnectionHistoryEntry', [function defineConnect
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* An arbitrary identifier that uniquely identifies this record
|
||||
* relative to other records in the same set, or null if no such unique
|
||||
* identifier exists.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.identifier = template.identifier;
|
||||
|
||||
/**
|
||||
* A UUID that uniquely identifies this record, or null if no such
|
||||
* unique identifier exists.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.uuid = template.uuid;
|
||||
|
||||
/**
|
||||
* The identifier of the connection associated with this history entry.
|
||||
*
|
||||
@@ -103,6 +120,23 @@ angular.module('rest').factory('ConnectionHistoryEntry', [function defineConnect
|
||||
*/
|
||||
this.active = template.active;
|
||||
|
||||
/**
|
||||
* Arbitrary name/value pairs which further describe this history
|
||||
* entry. The semantics and validity of these attributes are dictated
|
||||
* by the extension which defines them.
|
||||
*
|
||||
* @type {!object.<string, string>}
|
||||
*/
|
||||
this.attributes = template.attributes;
|
||||
|
||||
/**
|
||||
* All logs associated and accessible via this record, stored by their
|
||||
* corresponding unique names.
|
||||
*
|
||||
* @type {!object.<string, ActivityLog>}
|
||||
*/
|
||||
this.logs = template.logs;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.rest.history;
|
||||
|
||||
import org.apache.guacamole.language.TranslatableMessage;
|
||||
import org.apache.guacamole.net.auth.ActivityLog;
|
||||
|
||||
/**
|
||||
* A activity log which may be exposed through the REST endpoints.
|
||||
*/
|
||||
public class APIActivityLog {
|
||||
|
||||
/**
|
||||
* The type of this ActivityLog.
|
||||
*/
|
||||
private final ActivityLog.Type type;
|
||||
|
||||
/**
|
||||
* A human-readable description of this log.
|
||||
*/
|
||||
private final TranslatableMessage description;
|
||||
|
||||
/**
|
||||
* Creates a new APIActivityLog, copying the data from the given activity
|
||||
* log.
|
||||
*
|
||||
* @param log
|
||||
* The log to copy data from.
|
||||
*/
|
||||
public APIActivityLog(ActivityLog log) {
|
||||
this.type = log.getType();
|
||||
this.description = log.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of this activity log. The type of an activity log
|
||||
* dictates how its content should be interpreted or exposed, however the
|
||||
* content of a log is not directly exposed by this class.
|
||||
*
|
||||
* @return
|
||||
* The type of this activity log.
|
||||
*/
|
||||
public ActivityLog.Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable message that describes this log.
|
||||
*
|
||||
* @return
|
||||
* A human-readable message that describes this log.
|
||||
*/
|
||||
public TranslatableMessage getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
}
|
@@ -20,6 +20,9 @@
|
||||
package org.apache.guacamole.rest.history;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.guacamole.net.auth.ActivityRecord;
|
||||
|
||||
/**
|
||||
@@ -55,6 +58,30 @@ public class APIActivityRecord {
|
||||
*/
|
||||
private final boolean active;
|
||||
|
||||
/**
|
||||
* The unique identifier assigned to this record, or null if this record
|
||||
* has no such identifier.
|
||||
*/
|
||||
private final String identifier;
|
||||
|
||||
/**
|
||||
* A UUID that uniquely identifies this record, or null if no such unique
|
||||
* identifier exists.
|
||||
*/
|
||||
private final UUID uuid;
|
||||
|
||||
/**
|
||||
* A map of all attribute identifiers to their corresponding values, for
|
||||
* all attributes associated with this record.
|
||||
*/
|
||||
private final Map<String, String> attributes;
|
||||
|
||||
/**
|
||||
* A map of all logs associated and accessible via this record, associated
|
||||
* with their corresponding unique names.
|
||||
*/
|
||||
private final Map<String, APIActivityLog> logs;
|
||||
|
||||
/**
|
||||
* Creates a new APIActivityRecord, copying the data from the given activity
|
||||
* record.
|
||||
@@ -63,11 +90,21 @@ public class APIActivityRecord {
|
||||
* The record to copy data from.
|
||||
*/
|
||||
public APIActivityRecord(ActivityRecord record) {
|
||||
|
||||
this.startDate = record.getStartDate();
|
||||
this.endDate = record.getEndDate();
|
||||
this.remoteHost = record.getRemoteHost();
|
||||
this.username = record.getUsername();
|
||||
this.active = record.isActive();
|
||||
this.identifier = record.getIdentifier();
|
||||
this.uuid = record.getUUID();
|
||||
this.attributes = record.getAttributes();
|
||||
|
||||
this.logs = record.getLogs().entrySet().stream().collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
(entry) -> new APIActivityLog(entry.getValue())
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,4 +165,51 @@ public class APIActivityRecord {
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique identifier assigned to this record, if any. If this
|
||||
* record is not uniquely identifiable, this may be null.
|
||||
*
|
||||
* @return
|
||||
* The unique identifier assigned to this record, or null if this
|
||||
* record has no such identifier.
|
||||
*/
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a UUID that uniquely identifies this record. If not implemented
|
||||
* by the extension exposing this history record, this may be null.
|
||||
*
|
||||
* @return
|
||||
* A UUID that uniquely identifies this record, or null if no such
|
||||
* unique identifier exists.
|
||||
*/
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all attributes associated with this record.
|
||||
*
|
||||
* @return
|
||||
* A map of all attribute identifiers to their corresponding values,
|
||||
* for all attributes associated with this record.
|
||||
*/
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map of logs related to this record and accessible by the
|
||||
* current user, such as Guacamole session recordings. Each log is
|
||||
* associated with a corresponding, arbitrary, unique name.
|
||||
*
|
||||
* @return
|
||||
* A Map of logs related to this record.
|
||||
*/
|
||||
public Map<String, APIActivityLog> getLogs() {
|
||||
return logs;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.rest.history;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.ActivityLog;
|
||||
|
||||
/**
|
||||
* A REST resource which exposes the contents of a given ActivityLog.
|
||||
*/
|
||||
public class ActivityLogResource {
|
||||
|
||||
/**
|
||||
* The ActivityLog whose contents are being exposed.
|
||||
*/
|
||||
private final ActivityLog log;
|
||||
|
||||
/**
|
||||
* Creates a new ActivityLogResource which exposes the records within the
|
||||
* given ActivityLog.
|
||||
*
|
||||
* @param log
|
||||
* The ActivityLog whose contents should be exposed.
|
||||
*/
|
||||
public ActivityLogResource(ActivityLog log) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw contents of the underlying ActivityLog. If the size of
|
||||
* the ActivityLog is known, this size is included as the "Content-Length"
|
||||
* of the response.
|
||||
*
|
||||
* @return
|
||||
* A Response containing the raw contents of the underlying
|
||||
* ActivityLog.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error prevents retrieving the content of the log or its size.
|
||||
*/
|
||||
@GET
|
||||
public Response getContents() throws GuacamoleException {
|
||||
|
||||
// Build base response exposing the raw contents of the underlying log
|
||||
ResponseBuilder response = Response.ok(log.getContent(),
|
||||
log.getType().getContentType());
|
||||
|
||||
// Include size, if known
|
||||
long size = log.getSize();
|
||||
if (size >= 0)
|
||||
response.header("Content-Length", size);
|
||||
|
||||
return response.build();
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.rest.history;
|
||||
|
||||
import java.util.function.Function;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.apache.guacamole.net.auth.ActivityLog;
|
||||
import org.apache.guacamole.net.auth.ActivityRecord;
|
||||
|
||||
/**
|
||||
* A REST resource which exposes a single ActivityRecord, allowing any
|
||||
* associated and accessible logs to be retrieved.
|
||||
*/
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class ActivityRecordResource {
|
||||
|
||||
/**
|
||||
* The ActivityRecord being exposed.
|
||||
*/
|
||||
private final ActivityRecord record;
|
||||
|
||||
/**
|
||||
* The REST API object representing the ActivityRecord being exposed.
|
||||
*/
|
||||
private final APIActivityRecord externalRecord;
|
||||
|
||||
/**
|
||||
* Creates a new ActivityRecordResource which exposes the given record.
|
||||
*
|
||||
* @param record
|
||||
* The ActivityRecord that should be exposed.
|
||||
*
|
||||
* @param externalRecord
|
||||
* The REST API object representing the ActivityRecord being exposed.
|
||||
*/
|
||||
public ActivityRecordResource(ActivityRecord record,
|
||||
APIActivityRecord externalRecord) {
|
||||
this.record = record;
|
||||
this.externalRecord = externalRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the record represented by this ActivityRecordResource, in a
|
||||
* format intended for interchange.
|
||||
*
|
||||
* @return
|
||||
* The record that this ActivityRecordResource represents, in a format
|
||||
* intended for interchange.
|
||||
*/
|
||||
@GET
|
||||
public APIActivityRecord getRecord() {
|
||||
return externalRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ActivityLogResource representing the log associated with the
|
||||
* underlying ActivityRecord and having the given name. If no such log
|
||||
* can be retrieved, either because it does not exist or the current user
|
||||
* does not have access, an exception is thrown.
|
||||
*
|
||||
* @param logName
|
||||
* The unique name of the log to retrieve.
|
||||
*
|
||||
* @return
|
||||
* An ActivityLogResource representing the log having the given name.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If no such log can be retrieved.
|
||||
*/
|
||||
@Path("logs/{name}")
|
||||
public ActivityLogResource getLog(@PathParam("name") String logName)
|
||||
throws GuacamoleException {
|
||||
|
||||
ActivityLog log = record.getLogs().get(logName);
|
||||
if (log != null)
|
||||
return new ActivityLogResource(log);
|
||||
|
||||
throw new GuacamoleResourceNotFoundException("No such log.");
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -23,10 +23,13 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
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.GuacamoleResourceNotFoundException;
|
||||
import org.apache.guacamole.net.auth.ActivityRecord;
|
||||
import org.apache.guacamole.net.auth.ActivityRecordSet;
|
||||
|
||||
@@ -86,6 +89,45 @@ public abstract class ActivityRecordSetResource<InternalRecordType extends Activ
|
||||
*/
|
||||
protected abstract ExternalRecordType toExternalRecord(InternalRecordType record);
|
||||
|
||||
/**
|
||||
* Applies the given search and sorting criteria to the ActivityRecordSet
|
||||
* exposed by this ActivityRecordSetResource. The ActivityRecordSet stored
|
||||
* as {@link #history} is modified as a result of this call.
|
||||
*
|
||||
* @param requiredContents
|
||||
* The set of strings that each must occur somewhere within the
|
||||
* returned records, whether within the associated username,
|
||||
* the name of some associated object (such as a connection), or any
|
||||
* associated date. If non-empty, any record not matching each of the
|
||||
* strings within the collection will be excluded from the results.
|
||||
*
|
||||
* @param sortPredicates
|
||||
* A list of predicates to apply while sorting the resulting records,
|
||||
* describing the properties involved and the sort order for those
|
||||
* properties.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while applying the given filter criteria or
|
||||
* sort predicates.
|
||||
*/
|
||||
private void applyCriteria(List<String> requiredContents,
|
||||
List<APISortPredicate> sortPredicates) throws GuacamoleException {
|
||||
|
||||
// Restrict to records which contain the specified strings
|
||||
for (String required : requiredContents) {
|
||||
if (!required.isEmpty())
|
||||
history = history.contains(required);
|
||||
}
|
||||
|
||||
// Sort according to specified ordering
|
||||
for (APISortPredicate predicate : sortPredicates)
|
||||
history = history.sort(predicate.getProperty(), predicate.isDescending());
|
||||
|
||||
// Limit to maximum result size
|
||||
history = history.limit(MAXIMUM_HISTORY_SIZE);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of activity records stored within the underlying
|
||||
* ActivityRecordSet which match the given, arbitrary criteria. If
|
||||
@@ -118,19 +160,9 @@ public abstract class ActivityRecordSetResource<InternalRecordType extends Activ
|
||||
@QueryParam("order") List<APISortPredicate> sortPredicates)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Restrict to records which contain the specified strings
|
||||
for (String required : requiredContents) {
|
||||
if (!required.isEmpty())
|
||||
history = history.contains(required);
|
||||
}
|
||||
|
||||
// Sort according to specified ordering
|
||||
for (APISortPredicate predicate : sortPredicates)
|
||||
history = history.sort(predicate.getProperty(), predicate.isDescending());
|
||||
|
||||
// Limit to maximum result size
|
||||
history = history.limit(MAXIMUM_HISTORY_SIZE);
|
||||
|
||||
// Apply search/sort criteria
|
||||
applyCriteria(requiredContents, sortPredicates);
|
||||
|
||||
// Convert record set to collection of API records
|
||||
List<ExternalRecordType> apiRecords = new ArrayList<>();
|
||||
for (InternalRecordType record : history.asCollection())
|
||||
@@ -141,4 +173,30 @@ public abstract class ActivityRecordSetResource<InternalRecordType extends Activ
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves record having the given identifier from among the set of
|
||||
* activity records stored within the underlying ActivityRecordSet.
|
||||
*
|
||||
* @param identifier
|
||||
* The unique identifier of the record to retrieve.
|
||||
*
|
||||
* @return
|
||||
* A resource representing the record having the given identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while locating the requested record, or if the
|
||||
* requested record cannot be found.
|
||||
*/
|
||||
@Path("{identifier}")
|
||||
public ActivityRecordResource getRecord(@PathParam("identifier") String identifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
InternalRecordType record = history.get(identifier);
|
||||
if (record == null)
|
||||
throw new GuacamoleResourceNotFoundException("Not found: \"" + identifier + "\"");
|
||||
|
||||
return new ActivityRecordResource(record, toExternalRecord(record));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user