From 805c647cddc19ccf5b6e8263e3ba00e7432170b1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 23 Sep 2015 15:22:58 -0700 Subject: [PATCH] GUAC-1193: Implement history REST service. --- .../net/basic/rest/RESTServletModule.java | 2 + .../connection/ConnectionRESTService.java | 1 + .../APIConnectionRecord.java | 8 +- .../APIConnectionRecordSortPredicate.java | 166 ++++++++++++++++++ .../rest/history/HistoryRESTService.java | 147 ++++++++++++++++ .../net/basic/rest/history/package-info.java | 28 +++ 6 files changed, 348 insertions(+), 4 deletions(-) rename guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/{connection => history}/APIConnectionRecord.java (98%) create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecordSortPredicate.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/HistoryRESTService.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/package-info.java diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java index 478c2182e..b48ff9d37 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java @@ -31,6 +31,7 @@ import org.glyptodon.guacamole.net.basic.rest.auth.TokenRESTService; import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionRESTService; import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupRESTService; import org.glyptodon.guacamole.net.basic.rest.activeconnection.ActiveConnectionRESTService; +import org.glyptodon.guacamole.net.basic.rest.history.HistoryRESTService; import org.glyptodon.guacamole.net.basic.rest.language.LanguageRESTService; import org.glyptodon.guacamole.net.basic.rest.schema.SchemaRESTService; import org.glyptodon.guacamole.net.basic.rest.user.UserRESTService; @@ -59,6 +60,7 @@ public class RESTServletModule extends ServletModule { bind(ActiveConnectionRESTService.class); bind(ConnectionGroupRESTService.class); bind(ConnectionRESTService.class); + bind(HistoryRESTService.class); bind(LanguageRESTService.class); bind(SchemaRESTService.class); bind(TokenRESTService.class); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java index 270e3c302..2d122cd01 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java @@ -52,6 +52,7 @@ import org.glyptodon.guacamole.net.basic.GuacamoleSession; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; +import org.glyptodon.guacamole.net.basic.rest.history.APIConnectionRecord; import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionRecord.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecord.java similarity index 98% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionRecord.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecord.java index e3912ecbe..510ebaec9 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionRecord.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecord.java @@ -20,14 +20,14 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.rest.connection; +package org.glyptodon.guacamole.net.basic.rest.history; import java.util.Date; import org.glyptodon.guacamole.net.auth.ConnectionRecord; /** * A connection record which may be exposed through the REST endpoints. - * + * * @author Michael Jumper */ public class APIConnectionRecord { @@ -47,7 +47,7 @@ public class APIConnectionRecord { * The host from which the connection originated, if known. */ private final String remoteHost; - + /** * The name of the user who used or is using the connection. */ @@ -126,5 +126,5 @@ public class APIConnectionRecord { public boolean isActive() { return active; } - + } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecordSortPredicate.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecordSortPredicate.java new file mode 100644 index 000000000..5c4ebbb94 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/APIConnectionRecordSortPredicate.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.history; + +import org.glyptodon.guacamole.net.auth.ConnectionRecordSet; +import org.glyptodon.guacamole.net.basic.rest.APIError; +import org.glyptodon.guacamole.net.basic.rest.APIException; + +/** + * A sort predicate which species the property to use when sorting connection + * records, along with the sort order. + * + * @author Michael Jumper + */ +public class APIConnectionRecordSortPredicate { + + /** + * The prefix which will be included before the name of a sortable property + * to indicate that the sort order is descending, not ascending. + */ + public static final String DESCENDING_PREFIX = "-"; + + /** + * All possible property name strings and their corresponding + * ConnectionRecordSet.SortableProperty values. + */ + public enum SortableProperty { + + /** + * The name (not identifier) of the connection associated with the + * connection record. + */ + connection(ConnectionRecordSet.SortableProperty.CONNECTION_NAME), + + /** + * The username (identifier) of the user associated with the connection + * record. + */ + username(ConnectionRecordSet.SortableProperty.USER_IDENTIFIER), + + /** + * The date that the connection associated with the connection record + * began (connected). + */ + startDate(ConnectionRecordSet.SortableProperty.START_DATE), + + /** + * The date that the connection associated with the connection record + * ended (disconnected). + */ + endDate(ConnectionRecordSet.SortableProperty.END_DATE); + + /** + * The ConnectionRecordSet.SortableProperty that this property name + * string represents. + */ + public final ConnectionRecordSet.SortableProperty recordProperty; + + /** + * Creates a new SortableProperty which associates the property name + * string (identical to its own name) with the given + * ConnectionRecordSet.SortableProperty value. + * + * @param recordProperty + * The ConnectionRecordSet.SortableProperty value to associate with + * the new SortableProperty. + */ + SortableProperty(ConnectionRecordSet.SortableProperty recordProperty) { + this.recordProperty = recordProperty; + } + + } + + /** + * The property to use when sorting ConnectionRecords. + */ + private ConnectionRecordSet.SortableProperty property; + + /** + * Whether the requested sort order is descending (true) or ascending + * (false). + */ + private boolean descending; + + /** + * Parses the given string value, determining the requested sort property + * and ordering. Possible values consist of any valid property name, and + * may include an optional prefix to denote descending sort order. Each + * possible property name is enumerated by the SortableValue enum. + * + * @param value + * The sort predicate string to parse, which must consist ONLY of a + * valid property name, possibly preceded by the DESCENDING_PREFIX. + * + * @throws APIException + * If the provided sort predicate string is invalid. + */ + public APIConnectionRecordSortPredicate(String value) + throws APIException { + + // Parse whether sort order is descending + if (value.startsWith(DESCENDING_PREFIX)) { + descending = true; + value = value.substring(DESCENDING_PREFIX.length()); + } + + // Parse sorting property into ConnectionRecordSet.SortableProperty + try { + this.property = SortableProperty.valueOf(value).recordProperty; + } + + // Bail out if sort property is not valid + catch (IllegalArgumentException e) { + throw new APIException( + APIError.Type.BAD_REQUEST, + String.format("Invalid sort property: \"%s\"", value) + ); + } + + } + + /** + * Returns the SortableProperty defined by ConnectionRecordSet which + * represents the property requested. + * + * @return + * The ConnectionRecordSet.SortableProperty which refers to the same + * property as the string originally provided when this + * APIConnectionRecordSortPredicate was created. + */ + public ConnectionRecordSet.SortableProperty getProperty() { + return property; + } + + /** + * Returns whether the requested sort order is descending. + * + * @return + * true if the sort order is descending, false if the sort order is + * ascending. + */ + public boolean isDescending() { + return descending; + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/HistoryRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/HistoryRESTService.java new file mode 100644 index 000000000..0534d3b47 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/HistoryRESTService.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.history; + +import com.google.inject.Inject; +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.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.ConnectionRecord; +import org.glyptodon.guacamole.net.auth.ConnectionRecordSet; +import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.GuacamoleSession; +import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; +import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService; +import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A REST Service for retrieving and managing the history records of Guacamole + * objects. + * + * @author Michael Jumper + */ +@Path("/data/{dataSource}/history") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class HistoryRESTService { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(HistoryRESTService.class); + + /** + * The maximum number of history records to return in any one response. + */ + private static final int MAXIMUM_HISTORY_SIZE = 10000; + + /** + * A service for authenticating users from auth tokens. + */ + @Inject + private AuthenticationService authenticationService; + + /** + * Service for convenient retrieval of objects. + */ + @Inject + private ObjectRetrievalService retrievalService; + + /** + * Retrieves the usage history for all connections, restricted by optional + * filter parameters. + * + * @param authToken + * The authentication token that is used to authenticate the user + * performing the operation. + * + * @param authProviderIdentifier + * The unique identifier of the AuthenticationProvider associated with + * the UserContext containing the connection whose history is to be + * retrieved. + * + * @param requiredContents + * 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. + * + * @param sortPredicates + * A list of predicates to apply while sorting the resulting connection + * records, describing the properties involved and the sort order for + * those properties. + * + * @return + * A list of connection records, describing the start and end times of + * various usages of this connection. + * + * @throws GuacamoleException + * If an error occurs while retrieving the connection history. + */ + @GET + @Path("/connections") + @AuthProviderRESTExposure + public List getConnectionHistory(@QueryParam("token") String authToken, + @PathParam("dataSource") String authProviderIdentifier, + @QueryParam("contains") List requiredContents, + @QueryParam("order") List sortPredicates) + throws GuacamoleException { + + GuacamoleSession session = authenticationService.getGuacamoleSession(authToken); + UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier); + + // Retrieve overall connection history + ConnectionRecordSet history = userContext.getConnectionHistory(); + + // Restrict to records which contain the specified strings + for (String required : requiredContents) + history = history.contains(required); + + // Sort according to specified ordering + for (APIConnectionRecordSortPredicate predicate : sortPredicates) + history = history.sort(predicate.getProperty(), predicate.isDescending()); + + // Limit to maximum result size + history = history.limit(MAXIMUM_HISTORY_SIZE); + + // Convert record set to collection of API connection records + List apiRecords = new ArrayList(); + for (ConnectionRecord record : history.asCollection()) + apiRecords.add(new APIConnectionRecord(record)); + + // Return the converted history + return apiRecords; + + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/package-info.java new file mode 100644 index 000000000..0cd154b8f --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/history/package-info.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Classes related to retrieval or maintenance of history records using the + * Guacamole REST API. + */ +package org.glyptodon.guacamole.net.basic.rest.history; +