GUACAMOLE-394: Merge add support for recording user login/logout history to database auth.

This commit is contained in:
Nick Couchman
2018-01-05 10:38:12 -05:00
33 changed files with 1747 additions and 322 deletions

View File

@@ -76,6 +76,7 @@ import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileParameterMapp
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileService;
import org.apache.guacamole.auth.jdbc.tunnel.RestrictedGuacamoleTunnelService;
import org.apache.guacamole.auth.jdbc.user.PasswordRecordMapper;
import org.apache.guacamole.auth.jdbc.user.UserRecordMapper;
import org.mybatis.guice.MyBatisModule;
import org.mybatis.guice.datasource.builtin.PooledDataSourceProvider;
@@ -126,6 +127,7 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule {
addMapperClass(SharingProfilePermissionMapper.class);
addMapperClass(UserMapper.class);
addMapperClass(UserPermissionMapper.class);
addMapperClass(UserRecordMapper.class);
// Bind core implementations of guacamole-ext classes
bind(ActiveConnectionDirectory.class);

View File

@@ -0,0 +1,193 @@
/*
* 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.Date;
/**
* A single activity record representing an arbitrary activity performed by a
* user.
*/
public class ActivityRecordModel {
/**
* The ID of this object in the database, if any.
*/
private Integer recordID;
/**
* The database ID of the user associated with this activity record.
*/
private Integer userID;
/**
* The username of the user that performed the activity.
*/
private String username;
/**
* The remote host associated with the user that performed the activity.
*/
private String remoteHost;
/**
* The time the activity was initiated by the associated user.
*/
private Date startDate;
/**
* The time the activity ended, or null if the end time is not known or
* the activity is still in progress.
*/
private Date endDate;
/**
* Returns the ID of this record in the database, if it exists.
*
* @return
* The ID of this record in the database, or null if this record was
* not retrieved from the database.
*/
public Integer getRecordID() {
return recordID;
}
/**
* Sets the database ID of this record to the given value.
*
* @param recordID
* The ID to assign to this object.
*/
public void setRecordID(Integer recordID) {
this.recordID = recordID;
}
/**
* Returns the database ID of the user associated with this activity
* record.
*
* @return
* The database ID of the user associated with this activity record.
*/
public Integer getUserID() {
return userID;
}
/**
* Sets the database ID of the user associated with this activity record.
*
* @param userID
* The database ID of the user to associate with this activity
* record.
*/
public void setUserID(Integer userID) {
this.userID = userID;
}
/**
* Returns the username of the user that performed the activity associated
* with this record.
*
* @return
* The username of the user that performed the activity associated with
* this record.
*/
public String getUsername() {
return username;
}
/**
* Sets the username of the user that performed the activity associated
* with this record.
*
* @param username
* The username of the user that performed the activity associated with
* this record.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Returns the remote host associated with the user that performed the
* activity.
*
* @return
* The remote host associated with the user that performed the activity.
*/
public String getRemoteHost() {
return remoteHost;
}
/**
* Sets the remote host associated with the user that performed the
* activity.
*
* @param remoteHost
* The remote host associated with the user that performed the activity.
*/
public void setRemoteHost(String remoteHost) {
this.remoteHost = remoteHost;
}
/**
* Returns the time the activity was initiated by the associated user.
*
* @return
* The time the activity was initiated by the associated user.
*/
public Date getStartDate() {
return startDate;
}
/**
* Sets the time the activity was initiated by the associated user.
*
* @param startDate
* The time the activity was initiated by the associated user.
*/
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
/**
* Returns the time the activity ended, or null if the end time is not
* known or the activity is still in progress.
*
* @return
* The time the activity ended, or null if the end time is not known or
* the activity is still in progress.
*/
public Date getEndDate() {
return endDate;
}
/**
* Sets the time the activity ended, if known.
*
* @param endDate
* The time the activity ended, or null if the end time is not known or
* the activity is still in progress.
*/
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
}

View File

@@ -17,7 +17,7 @@
* under the License.
*/
package org.apache.guacamole.auth.jdbc.connection;
package org.apache.guacamole.auth.jdbc.base;
import java.util.Calendar;
import java.util.Date;
@@ -25,11 +25,11 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A search term for querying historical connection records. This will contain
* a the search term in string form and, if that string appears to be a date. a
* corresponding date range.
* A search term for querying historical records of arbitrary activities. This
* will contain a the search term in string form and, if that string appears to
* be a date. a corresponding date range.
*/
public class ConnectionRecordSearchTerm {
public class ActivityRecordSearchTerm {
/**
* A pattern that can match a year, year and month, or year and month and
@@ -180,7 +180,7 @@ public class ConnectionRecordSearchTerm {
}
/**
* Creates a new ConnectionRecordSearchTerm representing the given string.
* Creates a new ActivityRecordSearchTerm representing the given string.
* If the given string appears to be a date, the start and end dates of the
* implied date range will be automatically determined and made available
* via getStartDate() and getEndDate() respectively.
@@ -188,7 +188,7 @@ public class ConnectionRecordSearchTerm {
* @param term
* The string that should be searched for.
*/
public ConnectionRecordSearchTerm(String term) {
public ActivityRecordSearchTerm(String term) {
// Search terms absolutely must not be null
if (term == null)
@@ -281,10 +281,10 @@ public class ConnectionRecordSearchTerm {
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof ConnectionRecordSearchTerm))
if (obj == null || !(obj instanceof ActivityRecordSearchTerm))
return false;
return ((ConnectionRecordSearchTerm) obj).getTerm().equals(getTerm());
return ((ActivityRecordSearchTerm) obj).getTerm().equals(getTerm());
}

View File

@@ -17,18 +17,18 @@
* under the License.
*/
package org.apache.guacamole.auth.jdbc.connection;
package org.apache.guacamole.auth.jdbc.base;
import org.apache.guacamole.net.auth.ActivityRecordSet;
/**
* A sort predicate which species the property to use when sorting connection
* A sort predicate which species the property to use when sorting activity
* records, along with the sort order.
*/
public class ConnectionRecordSortPredicate {
public class ActivityRecordSortPredicate {
/**
* The property to use when sorting ConnectionRecords.
* The property to use when sorting ActivityRecords.
*/
private final ActivityRecordSet.SortableProperty property;
@@ -38,26 +38,26 @@ public class ConnectionRecordSortPredicate {
private final boolean descending;
/**
* Creates a new ConnectionRecordSortPredicate with the given sort property
* Creates a new ActivityRecordSortPredicate with the given sort property
* and sort order.
*
* @param property
* The property to use when sorting ConnectionRecords.
* The property to use when sorting ActivityRecords.
*
* @param descending
* Whether the sort order is descending (true) or ascending (false).
*/
public ConnectionRecordSortPredicate(ActivityRecordSet.SortableProperty property,
public ActivityRecordSortPredicate(ActivityRecordSet.SortableProperty property,
boolean descending) {
this.property = property;
this.descending = descending;
}
/**
* Returns the property that should be used when sorting ConnectionRecords.
* Returns the property that should be used when sorting ActivityRecords.
*
* @return
* The property that should be used when sorting ConnectionRecords.
* The property that should be used when sorting ActivityRecords.
*/
public ActivityRecordSet.SortableProperty getProperty() {
return property;

View File

@@ -0,0 +1,73 @@
/*
* 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.Date;
import org.apache.guacamole.net.auth.ActivityRecord;
/**
* An ActivityRecord which is backed by a database model.
*/
public class ModeledActivityRecord implements ActivityRecord {
/**
* The model object backing this activity record.
*/
private final ActivityRecordModel model;
/**
* 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 model
* The model object to use to back this activity record.
*/
public ModeledActivityRecord(ActivityRecordModel model) {
this.model = model;
}
@Override
public Date getStartDate() {
return model.getStartDate();
}
@Override
public Date getEndDate() {
return model.getEndDate();
}
@Override
public String getRemoteHost() {
return model.getRemoteHost();
}
@Override
public String getUsername() {
return model.getUsername();
}
@Override
public boolean isActive() {
return false;
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
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;
/**
* A JDBC implementation of ActivityRecordSet. Calls to asCollection() will
* query history records using an implementation-specific mechanism. Which
* records are returned will be determined by the values passed in earlier.
*
* @param <RecordType>
* The type of ActivityRecord contained within this set.
*/
public abstract class ModeledActivityRecordSet<RecordType extends ActivityRecord>
extends RestrictedObject implements ActivityRecordSet<RecordType> {
/**
* The set of strings that each must occur somewhere within the returned
* records, whether within the associated username, an associated date, or
* other related data. If non-empty, any record not matching each of the
* strings within the collection will be excluded from the results.
*/
private final Set<ActivityRecordSearchTerm> requiredContents =
new HashSet<ActivityRecordSearchTerm>();
/**
* The maximum number of history records that should be returned by a call
* to asCollection().
*/
private int limit = Integer.MAX_VALUE;
/**
* A list of predicates to apply while sorting the resulting records,
* describing the properties involved and the sort order for those
* properties.
*/
private final List<ActivityRecordSortPredicate> sortPredicates =
new ArrayList<ActivityRecordSortPredicate>();
/**
* Retrieves the history records matching the given criteria. Retrieves up
* to <code>limit</code> 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 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
* A collection of all history records matching the given criteria.
*
* @throws GuacamoleException
* If permission to read the history records is denied.
*/
protected abstract Collection<RecordType> retrieveHistory(
AuthenticatedUser user,
Set<ActivityRecordSearchTerm> requiredContents,
List<ActivityRecordSortPredicate> sortPredicates,
int limit) throws GuacamoleException;
@Override
public Collection<RecordType> asCollection()
throws GuacamoleException {
return retrieveHistory(getCurrentUser(), requiredContents,
sortPredicates, limit);
}
@Override
public ModeledActivityRecordSet<RecordType> contains(String value)
throws GuacamoleException {
requiredContents.add(new ActivityRecordSearchTerm(value));
return this;
}
@Override
public ModeledActivityRecordSet<RecordType> limit(int limit) throws GuacamoleException {
this.limit = Math.min(this.limit, limit);
return this;
}
@Override
public ModeledActivityRecordSet<RecordType> sort(SortableProperty property, boolean desc)
throws GuacamoleException {
sortPredicates.add(new ActivityRecordSortPredicate(
property,
desc
));
return this;
}
}

View File

@@ -19,6 +19,7 @@
package org.apache.guacamole.auth.jdbc.connection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.base.ChildObjectModel;
@@ -92,6 +93,12 @@ public class ConnectionModel extends ChildObjectModel {
*/
private EncryptionMethod proxyEncryptionMethod;
/**
* The date and time that this connection was last used, or null if this
* connection has never been used.
*/
private Date lastActive;
/**
* Creates a new, empty connection.
*/
@@ -341,6 +348,32 @@ public class ConnectionModel extends ChildObjectModel {
this.sharingProfileIdentifiers = sharingProfileIdentifiers;
}
/**
* Returns the date and time that this connection was last used, or null if
* this connection has never been used.
*
* @return
* The date and time that this connection was last used, or null if this
* connection has never been used.
*/
public Date getLastActive() {
return lastActive;
}
/**
* Sets the date and time that this connection was last used. This value is
* expected to be set automatically via queries, derived from connection
* history records. It does NOT correspond to an actual column, and values
* set manually through invoking this function will not persist.
*
* @param lastActive
* The date and time that this connection was last used, or null if this
* connection has never been used.
*/
public void setLastActive(Date lastActive) {
this.lastActive = lastActive;
}
@Override
public String getIdentifier() {

View File

@@ -21,6 +21,8 @@ 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;
@@ -75,8 +77,8 @@ public interface ConnectionRecordMapper {
* @return
* The results of the search performed with the given parameters.
*/
List<ConnectionRecordModel> search(@Param("terms") Collection<ConnectionRecordSearchTerm> terms,
@Param("sortPredicates") List<ConnectionRecordSortPredicate> sortPredicates,
List<ConnectionRecordModel> search(@Param("terms") Collection<ActivityRecordSearchTerm> terms,
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
@Param("limit") int limit);
/**
@@ -104,8 +106,8 @@ public interface ConnectionRecordMapper {
* The results of the search performed with the given parameters.
*/
List<ConnectionRecordModel> searchReadable(@Param("user") UserModel user,
@Param("terms") Collection<ConnectionRecordSearchTerm> terms,
@Param("sortPredicates") List<ConnectionRecordSortPredicate> sortPredicates,
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
@Param("limit") int limit);
}

View File

@@ -19,14 +19,14 @@
package org.apache.guacamole.auth.jdbc.connection;
import java.util.Date;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordModel;
/**
* A single connection record representing a past usage of a particular
* connection. If the connection was being shared, the sharing profile used to
* join the connection is included in the record.
*/
public class ConnectionRecordModel {
public class ConnectionRecordModel extends ActivityRecordModel {
/**
* The identifier of the connection associated with this connection record.
@@ -53,32 +53,6 @@ public class ConnectionRecordModel {
*/
private String sharingProfileName;
/**
* The database ID of the user associated with this connection record.
*/
private Integer userID;
/**
* The username of the user associated with this connection record.
*/
private String username;
/**
* The remote host associated with this connection record.
*/
private String remoteHost;
/**
* The time the connection was initiated by the associated user.
*/
private Date startDate;
/**
* The time the connection ended, or null if the end time is not known or
* the connection is still running.
*/
private Date endDate;
/**
* Returns the identifier of the connection associated with this connection
* record.
@@ -179,109 +153,4 @@ public class ConnectionRecordModel {
this.sharingProfileName = sharingProfileName;
}
/**
* Returns the database ID of the user associated with this connection
* record.
*
* @return
* The database ID of the user associated with this connection record.
*/
public Integer getUserID() {
return userID;
}
/**
* Sets the database ID of the user associated with this connection record.
*
* @param userID
* The database ID of the user to associate with this connection
* record.
*/
public void setUserID(Integer userID) {
this.userID = userID;
}
/**
* Returns the username of the user associated with this connection record.
*
* @return
* The username of the user associated with this connection record.
*/
public String getUsername() {
return username;
}
/**
* Sets the username of the user associated with this connection record.
*
* @param username
* The username of the user to associate with this connection record.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Returns the remote host associated with this connection record.
*
* @return
* The remote host associated with this connection record.
*/
public String getRemoteHost() {
return remoteHost;
}
/**
* Sets the remote host associated with this connection record.
*
* @param remoteHost
* The remote host to associate with this connection record.
*/
public void setRemoteHost(String remoteHost) {
this.remoteHost = remoteHost;
}
/**
* Returns the date that the associated connection was established.
*
* @return
* The date the associated connection was established.
*/
public Date getStartDate() {
return startDate;
}
/**
* Sets the date that the associated connection was established.
*
* @param startDate
* The date that the associated connection was established.
*/
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
/**
* Returns the date that the associated connection ended, or null if no
* end date was recorded. The lack of an end date does not necessarily
* mean that the connection is still active.
*
* @return
* The date the associated connection ended, or null if no end date was
* recorded.
*/
public Date getEndDate() {
return endDate;
}
/**
* Sets the date that the associated connection ended.
*
* @param endDate
* The date that the associated connection ended.
*/
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
}

View File

@@ -20,15 +20,14 @@
package org.apache.guacamole.auth.jdbc.connection;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.RestrictedObject;
import org.apache.guacamole.net.auth.ActivityRecordSet;
import org.apache.guacamole.net.auth.ActivityRecordSet.SortableProperty;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate;
import org.apache.guacamole.auth.jdbc.base.ModeledActivityRecordSet;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.ConnectionRecord;
/**
@@ -36,8 +35,7 @@ import org.apache.guacamole.net.auth.ConnectionRecord;
* asCollection() will query connection history records from the database. Which
* records are returned will be determined by the values passed in earlier.
*/
public class ConnectionRecordSet extends RestrictedObject
implements ActivityRecordSet<ConnectionRecord> {
public class ConnectionRecordSet extends ModeledActivityRecordSet<ConnectionRecord> {
/**
* Service for managing connection objects.
@@ -45,60 +43,15 @@ public class ConnectionRecordSet extends RestrictedObject
@Inject
private ConnectionService connectionService;
/**
* The set of strings that each must occur somewhere within the returned
* connection records, whether within the associated username, the name of
* the associated connection, or any associated date. If non-empty, any
* connection record not matching each of the strings within the collection
* will be excluded from the results.
*/
private final Set<ConnectionRecordSearchTerm> requiredContents =
new HashSet<ConnectionRecordSearchTerm>();
/**
* The maximum number of connection history records that should be returned
* by a call to asCollection().
*/
private int limit = Integer.MAX_VALUE;
/**
* A list of predicates to apply while sorting the resulting connection
* records, describing the properties involved and the sort order for those
* properties.
*/
private final List<ConnectionRecordSortPredicate> connectionRecordSortPredicates =
new ArrayList<ConnectionRecordSortPredicate>();
@Override
public Collection<ConnectionRecord> asCollection()
protected Collection<ConnectionRecord> retrieveHistory(
AuthenticatedUser user, Set<ActivityRecordSearchTerm> requiredContents,
List<ActivityRecordSortPredicate> sortPredicates, int limit)
throws GuacamoleException {
// Retrieve history from database
return connectionService.retrieveHistory(getCurrentUser(),
requiredContents, connectionRecordSortPredicates, limit);
}
@Override
public ConnectionRecordSet contains(String value)
throws GuacamoleException {
requiredContents.add(new ConnectionRecordSearchTerm(value));
return this;
}
@Override
public ConnectionRecordSet limit(int limit) throws GuacamoleException {
this.limit = Math.min(this.limit, limit);
return this;
}
@Override
public ConnectionRecordSet sort(SortableProperty property, boolean desc)
throws GuacamoleException {
connectionRecordSortPredicates.add(new ConnectionRecordSortPredicate(
property,
desc
));
return this;
requiredContents, sortPredicates, limit);
}

View File

@@ -34,6 +34,8 @@ import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate;
import org.apache.guacamole.auth.jdbc.base.ModeledChildDirectoryObjectService;
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
@@ -460,8 +462,8 @@ public class ConnectionService extends ModeledChildDirectoryObjectService<Modele
* If permission to read the connection history is denied.
*/
public List<ConnectionRecord> retrieveHistory(ModeledAuthenticatedUser user,
Collection<ConnectionRecordSearchTerm> requiredContents,
List<ConnectionRecordSortPredicate> sortPredicates, int limit)
Collection<ActivityRecordSearchTerm> requiredContents,
List<ActivityRecordSortPredicate> sortPredicates, int limit)
throws GuacamoleException {
List<ConnectionRecordModel> searchResults;

View File

@@ -235,7 +235,7 @@ public class ModeledConnection extends ModeledChildDirectoryObject<ConnectionMod
@Override
public Date getLastActive() {
return null;
return getModel().getLastActive();
}
@Override

View File

@@ -20,13 +20,14 @@
package org.apache.guacamole.auth.jdbc.connection;
import java.util.Date;
import org.apache.guacamole.auth.jdbc.base.ModeledActivityRecord;
import org.apache.guacamole.net.auth.ConnectionRecord;
/**
* A ConnectionRecord which is backed by a database model.
*/
public class ModeledConnectionRecord implements ConnectionRecord {
public class ModeledConnectionRecord extends ModeledActivityRecord
implements ConnectionRecord {
/**
* The model object backing this connection record.
@@ -42,6 +43,7 @@ public class ModeledConnectionRecord implements ConnectionRecord {
* The model object to use to back this connection record.
*/
public ModeledConnectionRecord(ConnectionRecordModel model) {
super(model);
this.model = model;
}
@@ -65,29 +67,4 @@ public class ModeledConnectionRecord implements ConnectionRecord {
return model.getSharingProfileName();
}
@Override
public Date getStartDate() {
return model.getStartDate();
}
@Override
public Date getEndDate() {
return model.getEndDate();
}
@Override
public String getRemoteHost() {
return model.getRemoteHost();
}
@Override
public String getUsername() {
return model.getUsername();
}
@Override
public boolean isActive() {
return false;
}
}

View File

@@ -144,6 +144,12 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
ACCOUNT_RESTRICTIONS
));
/**
* Service for managing users.
*/
@Inject
private UserService userService;
/**
* Service for hashing passwords.
*/
@@ -795,13 +801,13 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
}
@Override
public Date getLastActive() {
return null;
public Timestamp getLastActive() {
return getModel().getLastActive();
}
@Override
public List<ActivityRecord> getHistory() throws GuacamoleException {
return Collections.<ActivityRecord>emptyList();
return userService.retrieveHistory(getCurrentUser(), this);
}
}

View File

@@ -26,9 +26,11 @@ import org.apache.guacamole.auth.jdbc.connection.ConnectionDirectory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Collection;
import java.util.Date;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.RestrictedObject;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionDirectory;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordModel;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordSet;
import org.apache.guacamole.auth.jdbc.connection.ModeledConnection;
import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
@@ -44,7 +46,6 @@ import org.apache.guacamole.net.auth.ConnectionGroup;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.SharingProfile;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.simple.SimpleActivityRecordSet;
/**
* UserContext implementation which is driven by an arbitrary, underlying
@@ -99,7 +100,24 @@ public class ModeledUserContext extends RestrictedObject
*/
@Inject
private Provider<ConnectionRecordSet> connectionRecordSetProvider;
/**
* Provider for creating user record sets.
*/
@Inject
private Provider<UserRecordSet> userRecordSetProvider;
/**
* Mapper for user login records.
*/
@Inject
private UserRecordMapper userRecordMapper;
/**
* The activity record associated with this user's Guacamole session.
*/
private ActivityRecordModel userRecord;
@Override
public void init(ModeledAuthenticatedUser currentUser) {
@@ -112,6 +130,15 @@ public class ModeledUserContext extends RestrictedObject
sharingProfileDirectory.init(currentUser);
activeConnectionDirectory.init(currentUser);
// Create login record for user
userRecord = new ActivityRecordModel();
userRecord.setUsername(currentUser.getIdentifier());
userRecord.setStartDate(new Date());
userRecord.setRemoteHost(currentUser.getCredentials().getRemoteHostname());
// Insert record representing login
userRecordMapper.insert(userRecord);
}
@Override
@@ -167,7 +194,9 @@ public class ModeledUserContext extends RestrictedObject
@Override
public ActivityRecordSet<ActivityRecord> getUserHistory()
throws GuacamoleException {
return new SimpleActivityRecordSet<ActivityRecord>();
UserRecordSet userRecordSet = userRecordSetProvider.get();
userRecordSet.init(getCurrentUser());
return userRecordSet;
}
@Override
@@ -202,7 +231,11 @@ public class ModeledUserContext extends RestrictedObject
@Override
public void invalidate() {
// Nothing to invalidate
// Record logout time
userRecord.setEndDate(new Date());
userRecordMapper.update(userRecord);
}
}

View File

@@ -114,6 +114,12 @@ public class UserModel extends ObjectModel {
*/
private String organizationalRole;
/**
* The date and time that this user was last active, or null if this user
* has never logged in.
*/
private Timestamp lastActive;
/**
* Creates a new, empty user.
*/
@@ -465,4 +471,30 @@ public class UserModel extends ObjectModel {
this.organizationalRole = organizationalRole;
}
/**
* Returns the date and time that this user was last active, or null if
* this user has never logged in.
*
* @return
* The date and time that this user was last active, or null if this
* user has never logged in.
*/
public Timestamp getLastActive() {
return lastActive;
}
/**
* Sets the date and time that this user was last active. This value is
* expected to be set automatically via queries, derived from user history
* records. It does NOT correspond to an actual column, and values set
* manually through invoking this function will not persist.
*
* @param lastActive
* The date and time that this user was last active, or null if this
* user has never logged in.
*/
public void setLastActive(Timestamp lastActive) {
this.lastActive = lastActive;
}
}

View File

@@ -0,0 +1,124 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.user;
import java.util.Collection;
import java.util.List;
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 searchReadable() instead.
*
* @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("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 search() instead.
*
* @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.
*
* @return
* The results of the search performed with the given parameters.
*/
List<ActivityRecordModel> searchReadable(@Param("user") UserModel user,
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
@Param("limit") int limit);
}

View File

@@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.user;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate;
import org.apache.guacamole.auth.jdbc.base.ModeledActivityRecordSet;
import org.apache.guacamole.net.auth.ActivityRecord;
import org.apache.guacamole.net.auth.AuthenticatedUser;
/**
* A JDBC implementation of ActivityRecordSet for retrieving user login history.
* Calls to asCollection() will query user login records from the database.
* Which records are returned will be determined by the values passed in
* earlier.
*/
public class UserRecordSet extends ModeledActivityRecordSet<ActivityRecord> {
/**
* Service for managing user objects.
*/
@Inject
private UserService userService;
@Override
protected Collection<ActivityRecord> retrieveHistory(
AuthenticatedUser user, Set<ActivityRecordSearchTerm> requiredContents,
List<ActivityRecordSortPredicate> sortPredicates, int limit)
throws GuacamoleException {
// Retrieve history from database
return userService.retrieveHistory(getCurrentUser(),
requiredContents, sortPredicates, limit);
}
}

View File

@@ -21,16 +21,24 @@ package org.apache.guacamole.auth.jdbc.user;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper;
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectService;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.GuacamoleUnsupportedException;
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.guacamole.auth.jdbc.base.ModeledActivityRecord;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionModel;
import org.apache.guacamole.auth.jdbc.permission.UserPermissionMapper;
@@ -38,8 +46,10 @@ import org.apache.guacamole.auth.jdbc.security.PasswordEncryptionService;
import org.apache.guacamole.auth.jdbc.security.PasswordPolicyService;
import org.apache.guacamole.form.Field;
import org.apache.guacamole.form.PasswordField;
import org.apache.guacamole.net.auth.ActivityRecord;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.ConnectionRecord;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
@@ -116,7 +126,13 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
*/
@Inject
private UserPermissionMapper userPermissionMapper;
/**
* Mapper for accessing user login history.
*/
@Inject
private UserRecordMapper userRecordMapper;
/**
* Provider for creating users.
*/
@@ -460,4 +476,119 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
}
/**
* Returns a ActivityRecord object which is backed by the given model.
*
* @param model
* The model object to use to back the returned connection record
* object.
*
* @return
* A connection record object which is backed by the given model.
*/
protected ActivityRecord getObjectInstance(ActivityRecordModel model) {
return new ModeledActivityRecord(model);
}
/**
* Returns a list of ActivityRecord objects which are backed by the
* models in the given list.
*
* @param models
* The model objects to use to back the activity record objects
* within the returned list.
*
* @return
* A list of activity record objects which are backed by the models
* in the given list.
*/
protected List<ActivityRecord> getObjectInstances(List<ActivityRecordModel> models) {
// Create new list of records by manually converting each model
List<ActivityRecord> objects = new ArrayList<ActivityRecord>(models.size());
for (ActivityRecordModel model : models)
objects.add(getObjectInstance(model));
return objects;
}
/**
* 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();
// Retrieve history only if READ permission is granted
if (hasObjectPermission(authenticatedUser, username, ObjectPermission.Type.READ))
return getObjectInstances(userRecordMapper.select(username));
// The user does not have permission to read the history
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* 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 {
List<ActivityRecordModel> searchResults;
// Bypass permission checks if the user is a system admin
if (user.getUser().isAdministrator())
searchResults = userRecordMapper.search(requiredContents,
sortPredicates, limit);
// Otherwise only return explicitly readable history records
else
searchResults = userRecordMapper.searchReadable(user.getUser().getModel(),
requiredContents, sortPredicates, limit);
return getObjectInstances(searchResults);
}
}