mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
Merge pull request #252 from glyptodon/decouple-auth
GUAC-586: Support multiple auth providers within web UI
This commit is contained in:
@@ -123,6 +123,7 @@ public class ConnectionService extends ModeledGroupedDirectoryObjectService<Mode
|
||||
connection.setParentIdentifier(object.getParentIdentifier());
|
||||
connection.setName(object.getName());
|
||||
connection.setConfiguration(object.getConfiguration());
|
||||
connection.setAttributes(object.getAttributes());
|
||||
|
||||
return model;
|
||||
|
||||
|
@@ -106,6 +106,7 @@ public class ConnectionGroupService extends ModeledGroupedDirectoryObjectService
|
||||
connectionGroup.setParentIdentifier(object.getParentIdentifier());
|
||||
connectionGroup.setName(object.getName());
|
||||
connectionGroup.setType(object.getType());
|
||||
connectionGroup.setAttributes(object.getAttributes());
|
||||
|
||||
return model;
|
||||
|
||||
|
@@ -162,6 +162,7 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
|
||||
// Set model contents through ModeledUser, copying the provided user
|
||||
user.setIdentifier(object.getIdentifier());
|
||||
user.setPassword(object.getPassword());
|
||||
user.setAttributes(object.getAttributes());
|
||||
|
||||
return model;
|
||||
|
||||
|
@@ -33,6 +33,14 @@
|
||||
|
||||
},
|
||||
|
||||
"DATA_SOURCE_MYSQL" : {
|
||||
"NAME" : "MySQL"
|
||||
},
|
||||
|
||||
"DATA_SOURCE_POSTGRESQL" : {
|
||||
"NAME" : "PostgreSQL"
|
||||
},
|
||||
|
||||
"USER_ATTRIBUTES" : {
|
||||
|
||||
"FIELD_HEADER_DISABLED" : "Login disabled:",
|
||||
|
@@ -7,6 +7,10 @@
|
||||
|
||||
"authProviders" : [
|
||||
"net.sourceforge.guacamole.net.auth.ldap.LDAPAuthenticationProvider"
|
||||
],
|
||||
|
||||
"translations" : [
|
||||
"translations/en.json"
|
||||
]
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,7 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_LDAP" : {
|
||||
"NAME" : "LDAP"
|
||||
}
|
||||
|
||||
}
|
@@ -7,6 +7,10 @@
|
||||
|
||||
"authProviders" : [
|
||||
"net.sourceforge.guacamole.net.auth.noauth.NoAuthenticationProvider"
|
||||
],
|
||||
|
||||
"translations" : [
|
||||
"translations/en.json"
|
||||
]
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,7 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_NOAUTH" : {
|
||||
"NAME" : "NoAuth"
|
||||
}
|
||||
|
||||
}
|
@@ -202,7 +202,7 @@ public abstract class SimpleAuthenticationProvider
|
||||
throws GuacamoleException {
|
||||
|
||||
// Pull cached configurations, if any
|
||||
if (authenticatedUser instanceof SimpleAuthenticatedUser)
|
||||
if (authenticatedUser instanceof SimpleAuthenticatedUser && authenticatedUser.getAuthenticationProvider() == this)
|
||||
return ((SimpleAuthenticatedUser) authenticatedUser).getAuthorizedConfigurations();
|
||||
|
||||
// Otherwise, pull using credentials
|
||||
|
@@ -115,40 +115,6 @@ public class GuacamoleSession {
|
||||
this.authenticatedUser = authenticatedUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UserContext associated with this session.
|
||||
*
|
||||
* @return The UserContext associated with this session.
|
||||
*/
|
||||
public UserContext getUserContext() {
|
||||
|
||||
// Warn of deprecation
|
||||
logger.debug(
|
||||
"\n****************************************************************"
|
||||
+ "\n"
|
||||
+ "\n !!!! PLEASE DO NOT USE getUserContext() !!!!"
|
||||
+ "\n"
|
||||
+ "\n getUserContext() has been replaced by getUserContexts(), which"
|
||||
+ "\n properly handles multiple authentication providers. All use of"
|
||||
+ "\n the old getUserContext() must be removed before GUAC-586 can"
|
||||
+ "\n be considered complete."
|
||||
+ "\n"
|
||||
+ "\n****************************************************************"
|
||||
);
|
||||
|
||||
// Return the UserContext associated with the AuthenticationProvider
|
||||
// that authenticated the current user.
|
||||
String authProviderIdentifier = authenticatedUser.getAuthenticationProvider().getIdentifier();
|
||||
for (UserContext userContext : userContexts) {
|
||||
if (userContext.getAuthenticationProvider().getIdentifier().equals(authProviderIdentifier))
|
||||
return userContext;
|
||||
}
|
||||
|
||||
// If not found, return null
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all UserContexts associated with this session. Each
|
||||
* AuthenticationProvider currently loaded by Guacamole may provide its own
|
||||
|
@@ -31,7 +31,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class HTTPTunnelRequest implements TunnelRequest {
|
||||
public class HTTPTunnelRequest extends TunnelRequest {
|
||||
|
||||
/**
|
||||
* The wrapped HttpServletRequest.
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Glyptodon LLC
|
||||
* 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
|
||||
@@ -23,68 +23,105 @@
|
||||
package org.glyptodon.guacamole.net.basic;
|
||||
|
||||
import java.util.List;
|
||||
import org.glyptodon.guacamole.GuacamoleClientException;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
|
||||
/**
|
||||
* Request interface which provides only the functions absolutely required
|
||||
* to retrieve and connect to a tunnel.
|
||||
* A request object which provides only the functions absolutely required to
|
||||
* retrieve and connect to a tunnel.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public interface TunnelRequest {
|
||||
public abstract class TunnelRequest {
|
||||
|
||||
/**
|
||||
* All supported identifier types.
|
||||
* The name of the request parameter containing the user's authentication
|
||||
* token.
|
||||
*/
|
||||
public static enum IdentifierType {
|
||||
public static final String AUTH_TOKEN_PARAMETER = "token";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the identifier of the
|
||||
* AuthenticationProvider associated with the UserContext containing the
|
||||
* object to which a tunnel is being requested.
|
||||
*/
|
||||
public static final String AUTH_PROVIDER_IDENTIFIER_PARAMETER = "GUAC_DATA_SOURCE";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying the type of object to which a
|
||||
* tunnel is being requested. Currently, this may be "c" for a Guacamole
|
||||
* connection, or "g" for a Guacamole connection group.
|
||||
*/
|
||||
public static final String TYPE_PARAMETER = "GUAC_TYPE";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the unique identifier of the object
|
||||
* to which a tunnel is being requested.
|
||||
*/
|
||||
public static final String IDENTIFIER_PARAMETER = "GUAC_ID";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the desired display width, in
|
||||
* pixels.
|
||||
*/
|
||||
public static final String WIDTH_PARAMETER = "GUAC_WIDTH";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the desired display height, in
|
||||
* pixels.
|
||||
*/
|
||||
public static final String HEIGHT_PARAMETER = "GUAC_HEIGHT";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the desired display resolution, in
|
||||
* DPI.
|
||||
*/
|
||||
public static final String DPI_PARAMETER = "GUAC_DPI";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying one supported audio mimetype. This
|
||||
* will normally appear multiple times within a single tunnel request -
|
||||
* once for each mimetype.
|
||||
*/
|
||||
public static final String AUDIO_PARAMETER = "GUAC_AUDIO";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying one supported video mimetype. This
|
||||
* will normally appear multiple times within a single tunnel request -
|
||||
* once for each mimetype.
|
||||
*/
|
||||
public static final String VIDEO_PARAMETER = "GUAC_VIDEO";
|
||||
|
||||
/**
|
||||
* All supported object types that can be used as the destination of a
|
||||
* tunnel.
|
||||
*/
|
||||
public static enum Type {
|
||||
|
||||
/**
|
||||
* The unique identifier of a connection.
|
||||
* A Guacamole connection.
|
||||
*/
|
||||
CONNECTION("c/"),
|
||||
CONNECTION("c"),
|
||||
|
||||
/**
|
||||
* The unique identifier of a connection group.
|
||||
* A Guacamole connection group.
|
||||
*/
|
||||
CONNECTION_GROUP("g/");
|
||||
CONNECTION_GROUP("g");
|
||||
|
||||
/**
|
||||
* The prefix which precedes an identifier of this type.
|
||||
* The parameter value which denotes a destination object of this type.
|
||||
*/
|
||||
final String PREFIX;
|
||||
final String PARAMETER_VALUE;
|
||||
|
||||
/**
|
||||
* Defines an IdentifierType having the given prefix.
|
||||
* @param prefix The prefix which will precede any identifier of this
|
||||
* type, thus differentiating it from other identifier
|
||||
* types.
|
||||
*/
|
||||
IdentifierType(String prefix) {
|
||||
PREFIX = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an identifier, determines the corresponding identifier type.
|
||||
* Defines a Type having the given corresponding parameter value.
|
||||
*
|
||||
* @param identifier The identifier whose type should be identified.
|
||||
* @return The identified identifier type.
|
||||
* @param value
|
||||
* The parameter value which denotes a destination object of this
|
||||
* type.
|
||||
*/
|
||||
static IdentifierType getType(String identifier) {
|
||||
|
||||
// If null, no known identifier
|
||||
if (identifier == null)
|
||||
return null;
|
||||
|
||||
// Connection identifiers
|
||||
if (identifier.startsWith(CONNECTION.PREFIX))
|
||||
return CONNECTION;
|
||||
|
||||
// Connection group identifiers
|
||||
if (identifier.startsWith(CONNECTION_GROUP.PREFIX))
|
||||
return CONNECTION_GROUP;
|
||||
|
||||
// Otherwise, unknown
|
||||
return null;
|
||||
|
||||
Type(String value) {
|
||||
PARAMETER_VALUE = value;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -92,19 +129,225 @@ public interface TunnelRequest {
|
||||
/**
|
||||
* Returns the value of the parameter having the given name.
|
||||
*
|
||||
* @param name The name of the parameter to return.
|
||||
* @return The value of the parameter having the given name, or null
|
||||
* if no such parameter was specified.
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* The value of the parameter having the given name, or null if no such
|
||||
* parameter was specified.
|
||||
*/
|
||||
public String getParameter(String name);
|
||||
public abstract String getParameter(String name);
|
||||
|
||||
/**
|
||||
* Returns a list of all values specified for the given parameter.
|
||||
*
|
||||
* @param name The name of the parameter to return.
|
||||
* @return All values of the parameter having the given name , or null
|
||||
* if no such parameter was specified.
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* All values of the parameter having the given name , or null if no
|
||||
* such parameter was specified.
|
||||
*/
|
||||
public List<String> getParameterValues(String name);
|
||||
public abstract List<String> getParameterValues(String name);
|
||||
|
||||
/**
|
||||
* Returns the value of the parameter having the given name, throwing an
|
||||
* exception if the parameter is missing.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* The value of the parameter having the given name.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the parameter is not present in the request.
|
||||
*/
|
||||
public String getRequiredParameter(String name) throws GuacamoleException {
|
||||
|
||||
// Pull requested parameter, aborting if absent
|
||||
String value = getParameter(name);
|
||||
if (value == null)
|
||||
throw new GuacamoleClientException("Parameter \"" + name + "\" is required.");
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer value of the parameter having the given name,
|
||||
* throwing an exception if the parameter cannot be parsed.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* The integer value of the parameter having the given name, or null if
|
||||
* the parameter is missing.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the parameter is not a valid integer.
|
||||
*/
|
||||
public Integer getIntegerParameter(String name) throws GuacamoleException {
|
||||
|
||||
// Pull requested parameter
|
||||
String value = getParameter(name);
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
// Attempt to parse as an integer
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
// Rethrow any parsing error as a GuacamoleClientException
|
||||
catch (NumberFormatException e) {
|
||||
throw new GuacamoleClientException("Parameter \"" + name + "\" must be a valid integer.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authentication token associated with this tunnel request.
|
||||
*
|
||||
* @return
|
||||
* The authentication token associated with this tunnel request, or
|
||||
* null if no authentication token is present.
|
||||
*/
|
||||
public String getAuthenticationToken() {
|
||||
return getParameter(AUTH_TOKEN_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of the AuthenticationProvider associated with the
|
||||
* UserContext from which the connection or connection group is to be
|
||||
* retrieved when the tunnel is created. In the context of the REST API and
|
||||
* the JavaScript side of the web application, this is referred to as the
|
||||
* data source identifier.
|
||||
*
|
||||
* @return
|
||||
* The identifier of the AuthenticationProvider associated with the
|
||||
* UserContext from which the connection or connection group is to be
|
||||
* retrieved when the tunnel is created.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the identifier was not present in the request.
|
||||
*/
|
||||
public String getAuthenticationProviderIdentifier()
|
||||
throws GuacamoleException {
|
||||
return getRequiredParameter(AUTH_PROVIDER_IDENTIFIER_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of object for which the tunnel is being requested.
|
||||
*
|
||||
* @return
|
||||
* The type of object for which the tunnel is being requested.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the type was not present in the request, or if the type requested
|
||||
* is in the wrong format.
|
||||
*/
|
||||
public Type getType() throws GuacamoleException {
|
||||
|
||||
String type = getRequiredParameter(TYPE_PARAMETER);
|
||||
|
||||
// For each possible object type
|
||||
for (Type possibleType : Type.values()) {
|
||||
|
||||
// Match against defined parameter value
|
||||
if (type.equals(possibleType.PARAMETER_VALUE))
|
||||
return possibleType;
|
||||
|
||||
}
|
||||
|
||||
throw new GuacamoleClientException("Illegal identifier - unknown type.");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of the destination of the tunnel being requested.
|
||||
* As there are multiple types of destination objects available, and within
|
||||
* multiple data sources, the associated object type and data source are
|
||||
* also necessary to determine what this identifier refers to.
|
||||
*
|
||||
* @return
|
||||
* The identifier of the destination of the tunnel being requested.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the identifier was not present in the request.
|
||||
*/
|
||||
public String getIdentifier() throws GuacamoleException {
|
||||
return getRequiredParameter(IDENTIFIER_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display width desired for the Guacamole session over the
|
||||
* tunnel being requested.
|
||||
*
|
||||
* @return
|
||||
* The display width desired for the Guacamole session over the tunnel
|
||||
* being requested, or null if no width was given.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the width specified was not a valid integer.
|
||||
*/
|
||||
public Integer getWidth() throws GuacamoleException {
|
||||
return getIntegerParameter(WIDTH_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display height desired for the Guacamole session over the
|
||||
* tunnel being requested.
|
||||
*
|
||||
* @return
|
||||
* The display height desired for the Guacamole session over the tunnel
|
||||
* being requested, or null if no width was given.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the height specified was not a valid integer.
|
||||
*/
|
||||
public Integer getHeight() throws GuacamoleException {
|
||||
return getIntegerParameter(HEIGHT_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display resolution desired for the Guacamole session over
|
||||
* the tunnel being requested, in DPI.
|
||||
*
|
||||
* @return
|
||||
* The display resolution desired for the Guacamole session over the
|
||||
* tunnel being requested, or null if no resolution was given.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the resolution specified was not a valid integer.
|
||||
*/
|
||||
public Integer getDPI() throws GuacamoleException {
|
||||
return getIntegerParameter(DPI_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all audio mimetypes declared as supported within the
|
||||
* tunnel request.
|
||||
*
|
||||
* @return
|
||||
* A list of all audio mimetypes declared as supported within the
|
||||
* tunnel request, or null if no mimetypes were specified.
|
||||
*/
|
||||
public List<String> getAudioMimetypes() {
|
||||
return getParameterValues(AUDIO_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all video mimetypes declared as supported within the
|
||||
* tunnel request.
|
||||
*
|
||||
* @return
|
||||
* A list of all video mimetypes declared as supported within the
|
||||
* tunnel request, or null if no mimetypes were specified.
|
||||
*/
|
||||
public List<String> getVideoMimetypes() {
|
||||
return getParameterValues(VIDEO_PARAMETER);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -25,16 +25,15 @@ package org.glyptodon.guacamole.net.basic;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import java.util.List;
|
||||
import org.glyptodon.guacamole.GuacamoleClientException;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
||||
import org.glyptodon.guacamole.environment.Environment;
|
||||
import org.glyptodon.guacamole.net.DelegatingGuacamoleTunnel;
|
||||
import org.glyptodon.guacamole.net.GuacamoleTunnel;
|
||||
import org.glyptodon.guacamole.net.auth.Connection;
|
||||
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
|
||||
import org.glyptodon.guacamole.net.auth.Directory;
|
||||
import org.glyptodon.guacamole.net.auth.UserContext;
|
||||
import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService;
|
||||
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
|
||||
import org.slf4j.Logger;
|
||||
@@ -53,12 +52,6 @@ import org.slf4j.LoggerFactory;
|
||||
@Singleton
|
||||
public class TunnelRequestService {
|
||||
|
||||
/**
|
||||
* The Guacamole server environment.
|
||||
*/
|
||||
@Inject
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
@@ -70,6 +63,12 @@ public class TunnelRequestService {
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Service for convenient retrieval of objects.
|
||||
*/
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Reads and returns the client information provided within the given
|
||||
* request.
|
||||
@@ -80,35 +79,40 @@ public class TunnelRequestService {
|
||||
* @return GuacamoleClientInformation
|
||||
* An object containing information about the client sending the tunnel
|
||||
* request.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the parameters of the tunnel request are invalid.
|
||||
*/
|
||||
protected GuacamoleClientInformation getClientInformation(TunnelRequest request) {
|
||||
protected GuacamoleClientInformation getClientInformation(TunnelRequest request)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Get client information
|
||||
GuacamoleClientInformation info = new GuacamoleClientInformation();
|
||||
|
||||
// Set width if provided
|
||||
String width = request.getParameter("width");
|
||||
Integer width = request.getWidth();
|
||||
if (width != null)
|
||||
info.setOptimalScreenWidth(Integer.parseInt(width));
|
||||
info.setOptimalScreenWidth(width);
|
||||
|
||||
// Set height if provided
|
||||
String height = request.getParameter("height");
|
||||
Integer height = request.getHeight();
|
||||
if (height != null)
|
||||
info.setOptimalScreenHeight(Integer.parseInt(height));
|
||||
info.setOptimalScreenHeight(height);
|
||||
|
||||
// Set resolution if provided
|
||||
String dpi = request.getParameter("dpi");
|
||||
Integer dpi = request.getDPI();
|
||||
if (dpi != null)
|
||||
info.setOptimalResolution(Integer.parseInt(dpi));
|
||||
info.setOptimalResolution(dpi);
|
||||
|
||||
// Add audio mimetypes
|
||||
List<String> audio_mimetypes = request.getParameterValues("audio");
|
||||
if (audio_mimetypes != null)
|
||||
info.getAudioMimetypes().addAll(audio_mimetypes);
|
||||
List<String> audioMimetypes = request.getAudioMimetypes();
|
||||
if (audioMimetypes != null)
|
||||
info.getAudioMimetypes().addAll(audioMimetypes);
|
||||
|
||||
// Add video mimetypes
|
||||
List<String> video_mimetypes = request.getParameterValues("video");
|
||||
if (video_mimetypes != null)
|
||||
info.getVideoMimetypes().addAll(video_mimetypes);
|
||||
List<String> videoMimetypes = request.getVideoMimetypes();
|
||||
if (videoMimetypes != null)
|
||||
info.getVideoMimetypes().addAll(videoMimetypes);
|
||||
|
||||
return info;
|
||||
}
|
||||
@@ -122,7 +126,7 @@ public class TunnelRequestService {
|
||||
* The UserContext associated with the user for whom the tunnel is
|
||||
* being created.
|
||||
*
|
||||
* @param idType
|
||||
* @param type
|
||||
* The type of object being connected to (connection or group).
|
||||
*
|
||||
* @param id
|
||||
@@ -138,13 +142,13 @@ public class TunnelRequestService {
|
||||
* If an error occurs while creating the tunnel.
|
||||
*/
|
||||
protected GuacamoleTunnel createConnectedTunnel(UserContext context,
|
||||
final TunnelRequest.IdentifierType idType, String id,
|
||||
final TunnelRequest.Type type, String id,
|
||||
GuacamoleClientInformation info)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Create connected tunnel from identifier
|
||||
GuacamoleTunnel tunnel = null;
|
||||
switch (idType) {
|
||||
switch (type) {
|
||||
|
||||
// Connection identifiers
|
||||
case CONNECTION: {
|
||||
@@ -205,7 +209,7 @@ public class TunnelRequestService {
|
||||
* @param session
|
||||
* The Guacamole session to associate the tunnel with.
|
||||
*
|
||||
* @param idType
|
||||
* @param type
|
||||
* The type of object being connected to (connection or group).
|
||||
*
|
||||
* @param id
|
||||
@@ -220,7 +224,7 @@ public class TunnelRequestService {
|
||||
* If an error occurs while obtaining the tunnel.
|
||||
*/
|
||||
protected GuacamoleTunnel createAssociatedTunnel(final GuacamoleSession session,
|
||||
GuacamoleTunnel tunnel, final TunnelRequest.IdentifierType idType,
|
||||
GuacamoleTunnel tunnel, final TunnelRequest.Type type,
|
||||
final String id) throws GuacamoleException {
|
||||
|
||||
// Monitor tunnel closure and data
|
||||
@@ -239,7 +243,7 @@ public class TunnelRequestService {
|
||||
long duration = connectionEndTime - connectionStartTime;
|
||||
|
||||
// Log closure
|
||||
switch (idType) {
|
||||
switch (type) {
|
||||
|
||||
// Connection identifiers
|
||||
case CONNECTION:
|
||||
@@ -289,27 +293,20 @@ public class TunnelRequestService {
|
||||
public GuacamoleTunnel createTunnel(TunnelRequest request)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Get auth token and session
|
||||
final String authToken = request.getParameter("authToken");
|
||||
final GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Get client information and connection ID from request
|
||||
String id = request.getParameter("id");
|
||||
final GuacamoleClientInformation info = getClientInformation(request);
|
||||
|
||||
// Determine ID type
|
||||
TunnelRequest.IdentifierType idType = TunnelRequest.IdentifierType.getType(id);
|
||||
if (idType == null)
|
||||
throw new GuacamoleClientException("Illegal identifier - unknown type.");
|
||||
|
||||
// Remove prefix
|
||||
id = id.substring(idType.PREFIX.length());
|
||||
// Parse request parameters
|
||||
String authToken = request.getAuthenticationToken();
|
||||
String id = request.getIdentifier();
|
||||
TunnelRequest.Type type = request.getType();
|
||||
String authProviderIdentifier = request.getAuthenticationProviderIdentifier();
|
||||
GuacamoleClientInformation info = getClientInformation(request);
|
||||
|
||||
// Create connected tunnel using provided connection ID and client information
|
||||
final GuacamoleTunnel tunnel = createConnectedTunnel(session.getUserContext(), idType, id, info);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info);
|
||||
|
||||
// Associate tunnel with session
|
||||
return createAssociatedTunnel(session, tunnel, idType, id);
|
||||
return createAssociatedTunnel(session, tunnel, type, id);
|
||||
|
||||
}
|
||||
|
||||
|
@@ -334,6 +334,9 @@ public class ExtensionModule extends ServletModule {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort files lexicographically
|
||||
Arrays.sort(extensionFiles);
|
||||
|
||||
// Load each extension within the extension directory
|
||||
for (File extensionFile : extensionFiles) {
|
||||
|
||||
|
@@ -25,6 +25,7 @@ package org.glyptodon.guacamole.net.basic.rest;
|
||||
import java.util.List;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.glyptodon.guacamole.net.auth.Connection;
|
||||
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
|
||||
import org.glyptodon.guacamole.net.auth.Directory;
|
||||
@@ -48,9 +49,9 @@ public class ObjectRetrievalService {
|
||||
* @param session
|
||||
* The GuacamoleSession to retrieve the UserContext from.
|
||||
*
|
||||
* @param identifier
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider that created the
|
||||
* UserContext being retrieved. Only one UserContext per
|
||||
* UserContext being retrieved. Only one UserContext per User per
|
||||
* AuthenticationProvider can exist.
|
||||
*
|
||||
* @return
|
||||
@@ -62,7 +63,7 @@ public class ObjectRetrievalService {
|
||||
* UserContext does not exist.
|
||||
*/
|
||||
public UserContext retrieveUserContext(GuacamoleSession session,
|
||||
String identifier) throws GuacamoleException {
|
||||
String authProviderIdentifier) throws GuacamoleException {
|
||||
|
||||
// Get list of UserContexts
|
||||
List<UserContext> userContexts = session.getUserContexts();
|
||||
@@ -70,11 +71,17 @@ public class ObjectRetrievalService {
|
||||
// Locate and return the UserContext associated with the
|
||||
// AuthenticationProvider having the given identifier, if any
|
||||
for (UserContext userContext : userContexts) {
|
||||
if (userContext.getAuthenticationProvider().getIdentifier().equals(identifier))
|
||||
|
||||
// Get AuthenticationProvider associated with current UserContext
|
||||
AuthenticationProvider authProvider = userContext.getAuthenticationProvider();
|
||||
|
||||
// If AuthenticationProvider identifier matches, done
|
||||
if (authProvider.getIdentifier().equals(authProviderIdentifier))
|
||||
return userContext;
|
||||
|
||||
}
|
||||
|
||||
throw new GuacamoleResourceNotFoundException("Session not associated with authentication provider \"" + identifier + "\".");
|
||||
throw new GuacamoleResourceNotFoundException("Session not associated with authentication provider \"" + authProviderIdentifier + "\".");
|
||||
|
||||
}
|
||||
|
||||
@@ -109,6 +116,35 @@ public class ObjectRetrievalService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single user from the given GuacamoleSession.
|
||||
*
|
||||
* @param session
|
||||
* The GuacamoleSession to retrieve the user from.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider that created the
|
||||
* UserContext from which the user should be retrieved. Only one
|
||||
* UserContext per User per AuthenticationProvider can exist.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier of the user to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The user having the given identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the user, or if the
|
||||
* user does not exist.
|
||||
*/
|
||||
public User retrieveUser(GuacamoleSession session, String authProviderIdentifier,
|
||||
String identifier) throws GuacamoleException {
|
||||
|
||||
UserContext userContext = retrieveUserContext(session, authProviderIdentifier);
|
||||
return retrieveUser(userContext, identifier);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single connection from the given user context.
|
||||
*
|
||||
@@ -140,6 +176,36 @@ public class ObjectRetrievalService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single connection from the given GuacamoleSession.
|
||||
*
|
||||
* @param session
|
||||
* The GuacamoleSession to retrieve the connection from.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider that created the
|
||||
* UserContext from which the connection should be retrieved. Only one
|
||||
* UserContext per User per AuthenticationProvider can exist.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier of the connection to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The connection having the given identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the connection, or if the
|
||||
* connection does not exist.
|
||||
*/
|
||||
public Connection retrieveConnection(GuacamoleSession session,
|
||||
String authProviderIdentifier, String identifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = retrieveUserContext(session, authProviderIdentifier);
|
||||
return retrieveConnection(userContext, identifier);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single connection group from the given user context. If
|
||||
* the given identifier the REST API root identifier, the root connection
|
||||
@@ -178,4 +244,37 @@ public class ObjectRetrievalService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single connection group from the given GuacamoleSession. If
|
||||
* the given identifier is the REST API root identifier, the root
|
||||
* connection group will be returned. The underlying authentication
|
||||
* provider may additionally use a different identifier for root.
|
||||
*
|
||||
* @param session
|
||||
* The GuacamoleSession to retrieve the connection group from.
|
||||
*
|
||||
* @param authProviderIdentifier
|
||||
* The unique identifier of the AuthenticationProvider that created the
|
||||
* UserContext from which the connection group should be retrieved.
|
||||
* Only one UserContext per User per AuthenticationProvider can exist.
|
||||
*
|
||||
* @param identifier
|
||||
* The identifier of the connection group to retrieve.
|
||||
*
|
||||
* @return
|
||||
* The connection group having the given identifier, or the root
|
||||
* connection group if the identifier is the root identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the connection group, or if the
|
||||
* connection group does not exist.
|
||||
*/
|
||||
public ConnectionGroup retrieveConnectionGroup(GuacamoleSession session,
|
||||
String authProviderIdentifier, String identifier) throws GuacamoleException {
|
||||
|
||||
UserContext userContext = retrieveUserContext(session, authProviderIdentifier);
|
||||
return retrieveConnectionGroup(userContext, identifier);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ import java.util.Map;
|
||||
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;
|
||||
@@ -44,8 +45,10 @@ import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
|
||||
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
|
||||
import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
|
||||
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
|
||||
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
|
||||
import org.glyptodon.guacamole.net.basic.rest.APIPatch;
|
||||
import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
|
||||
import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService;
|
||||
import org.glyptodon.guacamole.net.basic.rest.PATCH;
|
||||
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.slf4j.Logger;
|
||||
@@ -56,7 +59,7 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Path("/activeConnections")
|
||||
@Path("/data/{dataSource}/activeConnections")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class ActiveConnectionRESTService {
|
||||
@@ -72,6 +75,12 @@ public class ActiveConnectionRESTService {
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Service for convenient retrieval of objects.
|
||||
*/
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Gets a list of active connections in the system, filtering the returned
|
||||
* list by the given permissions, if specified.
|
||||
@@ -80,6 +89,10 @@ public class ActiveConnectionRESTService {
|
||||
* 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 active connections to be retrieved.
|
||||
*
|
||||
* @param permissions
|
||||
* The set of permissions to filter with. A user must have one or more
|
||||
* of these permissions for a user to appear in the result.
|
||||
@@ -96,10 +109,12 @@ public class ActiveConnectionRESTService {
|
||||
@GET
|
||||
@AuthProviderRESTExposure
|
||||
public Map<String, APIActiveConnection> getActiveConnections(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@QueryParam("permission") List<ObjectPermission.Type> permissions)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
User self = userContext.self();
|
||||
|
||||
// Do not filter on permissions if no permissions are specified
|
||||
@@ -140,6 +155,10 @@ public class ActiveConnectionRESTService {
|
||||
* 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 active connections to be deleted.
|
||||
*
|
||||
* @param patches
|
||||
* The active connection patches to apply for this request.
|
||||
*
|
||||
@@ -149,9 +168,11 @@ public class ActiveConnectionRESTService {
|
||||
@PATCH
|
||||
@AuthProviderRESTExposure
|
||||
public void patchTunnels(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
List<APIPatch<String>> patches) throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the directory
|
||||
Directory<ActiveConnection> activeConnectionDirectory = userContext.getActiveConnectionDirectory();
|
||||
|
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.auth;
|
||||
|
||||
/**
|
||||
* A simple object to represent an auth token/username pair in the API.
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
public class APIAuthenticationResponse {
|
||||
|
||||
/**
|
||||
* The auth token.
|
||||
*/
|
||||
private final String authToken;
|
||||
|
||||
|
||||
/**
|
||||
* The username of the user that authenticated.
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* The unique identifier of the data source from which this user account
|
||||
* came. Although this user account may exist across several data sources
|
||||
* (AuthenticationProviders), this will be the unique identifier of the
|
||||
* AuthenticationProvider that authenticated this user for the current
|
||||
* session.
|
||||
*/
|
||||
private final String dataSource;
|
||||
|
||||
/**
|
||||
* Returns the unique authentication token which identifies the current
|
||||
* session.
|
||||
*
|
||||
* @return
|
||||
* The user's authentication token.
|
||||
*/
|
||||
public String getAuthToken() {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user identified by the authentication token associated with
|
||||
* the current session.
|
||||
*
|
||||
* @return
|
||||
* The user identified by this authentication token.
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique identifier of the data source associated with the user
|
||||
* account associated with this auth token.
|
||||
*
|
||||
* @return
|
||||
* The unique identifier of the data source associated with the user
|
||||
* account associated with this auth token.
|
||||
*/
|
||||
public String getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new APIAuthToken Object with the given auth token.
|
||||
*
|
||||
* @param dataSource
|
||||
* The unique identifier of the AuthenticationProvider which
|
||||
* authenticated the user.
|
||||
*
|
||||
* @param authToken
|
||||
* The auth token to create the new APIAuthToken with.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user owning the given token.
|
||||
*/
|
||||
public APIAuthenticationResponse(String dataSource, String authToken, String username) {
|
||||
this.dataSource = dataSource;
|
||||
this.authToken = authToken;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
}
|
@@ -66,19 +66,6 @@ public class AuthenticationService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the UserContext for a given auth token, if the auth token represents
|
||||
* a currently logged in user. Throws an unauthorized error otherwise.
|
||||
*
|
||||
* @param authToken The auth token to check against the map of logged in users.
|
||||
* @return The user context that corresponds to the provided auth token.
|
||||
* @throws GuacamoleException If the auth token does not correspond to any
|
||||
* logged in user.
|
||||
*/
|
||||
public UserContext getUserContext(String authToken) throws GuacamoleException {
|
||||
return getGuacamoleSession(authToken).getUserContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all UserContexts associated with a given auth token, if the auth
|
||||
* token represents a currently logged in user. Throws an unauthorized
|
||||
|
@@ -48,6 +48,7 @@ import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
|
||||
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
|
||||
import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
|
||||
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
|
||||
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;
|
||||
@@ -60,7 +61,7 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
@Path("/connections")
|
||||
@Path("/data/{dataSource}/connections")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class ConnectionRESTService {
|
||||
@@ -89,6 +90,10 @@ public class ConnectionRESTService {
|
||||
* 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 to be retrieved.
|
||||
*
|
||||
* @param connectionID
|
||||
* The identifier of the connection to retrieve.
|
||||
*
|
||||
@@ -102,12 +107,14 @@ public class ConnectionRESTService {
|
||||
@Path("/{connectionID}")
|
||||
@AuthProviderRESTExposure
|
||||
public APIConnection getConnection(@QueryParam("token") String authToken,
|
||||
@PathParam("connectionID") String connectionID) throws GuacamoleException {
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionID") String connectionID)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Retrieve the requested connection
|
||||
return new APIConnection(retrievalService.retrieveConnection(userContext, connectionID));
|
||||
return new APIConnection(retrievalService.retrieveConnection(session, authProviderIdentifier, connectionID));
|
||||
|
||||
}
|
||||
|
||||
@@ -118,6 +125,11 @@ public class ConnectionRESTService {
|
||||
* 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 parameters are to be
|
||||
* retrieved.
|
||||
*
|
||||
* @param connectionID
|
||||
* The identifier of the connection.
|
||||
*
|
||||
@@ -131,9 +143,12 @@ public class ConnectionRESTService {
|
||||
@Path("/{connectionID}/parameters")
|
||||
@AuthProviderRESTExposure
|
||||
public Map<String, String> getConnectionParameters(@QueryParam("token") String authToken,
|
||||
@PathParam("connectionID") String connectionID) throws GuacamoleException {
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionID") String connectionID)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
User self = userContext.self();
|
||||
|
||||
// Retrieve permission sets
|
||||
@@ -163,6 +178,11 @@ public class ConnectionRESTService {
|
||||
* 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 connectionID
|
||||
* The identifier of the connection.
|
||||
*
|
||||
@@ -177,12 +197,14 @@ public class ConnectionRESTService {
|
||||
@Path("/{connectionID}/history")
|
||||
@AuthProviderRESTExposure
|
||||
public List<APIConnectionRecord> getConnectionHistory(@QueryParam("token") String authToken,
|
||||
@PathParam("connectionID") String connectionID) throws GuacamoleException {
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionID") String connectionID)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Retrieve the requested connection
|
||||
Connection connection = retrievalService.retrieveConnection(userContext, connectionID);
|
||||
Connection connection = retrievalService.retrieveConnection(session, authProviderIdentifier, connectionID);
|
||||
|
||||
// Retrieve the requested connection's history
|
||||
List<APIConnectionRecord> apiRecords = new ArrayList<APIConnectionRecord>();
|
||||
@@ -201,6 +223,10 @@ public class ConnectionRESTService {
|
||||
* 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 to be deleted.
|
||||
*
|
||||
* @param connectionID
|
||||
* The identifier of the connection to delete.
|
||||
*
|
||||
@@ -210,10 +236,13 @@ public class ConnectionRESTService {
|
||||
@DELETE
|
||||
@Path("/{connectionID}")
|
||||
@AuthProviderRESTExposure
|
||||
public void deleteConnection(@QueryParam("token") String authToken, @PathParam("connectionID") String connectionID)
|
||||
public void deleteConnection(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionID") String connectionID)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the connection directory
|
||||
Directory<Connection> connectionDirectory = userContext.getConnectionDirectory();
|
||||
@@ -231,6 +260,10 @@ public class ConnectionRESTService {
|
||||
* 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 in which the connection is to be created.
|
||||
*
|
||||
* @param connection
|
||||
* The connection to create.
|
||||
*
|
||||
@@ -244,9 +277,11 @@ public class ConnectionRESTService {
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@AuthProviderRESTExposure
|
||||
public String createConnection(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
APIConnection connection) throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Validate that connection data was provided
|
||||
if (connection == null)
|
||||
@@ -270,6 +305,10 @@ public class ConnectionRESTService {
|
||||
* 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 to be updated.
|
||||
*
|
||||
* @param connectionID
|
||||
* The identifier of the connection to update.
|
||||
*
|
||||
@@ -283,9 +322,12 @@ public class ConnectionRESTService {
|
||||
@Path("/{connectionID}")
|
||||
@AuthProviderRESTExposure
|
||||
public void updateConnection(@QueryParam("token") String authToken,
|
||||
@PathParam("connectionID") String connectionID, APIConnection connection) throws GuacamoleException {
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionID") String connectionID,
|
||||
APIConnection connection) throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Validate that connection data was provided
|
||||
if (connection == null)
|
||||
|
@@ -40,6 +40,7 @@ import org.glyptodon.guacamole.net.auth.ConnectionGroup;
|
||||
import org.glyptodon.guacamole.net.auth.Directory;
|
||||
import org.glyptodon.guacamole.net.auth.UserContext;
|
||||
import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
|
||||
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;
|
||||
@@ -51,7 +52,7 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* @author James Muehlner
|
||||
*/
|
||||
@Path("/connectionGroups")
|
||||
@Path("/data/{dataSource}/connectionGroups")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class ConnectionGroupRESTService {
|
||||
@@ -80,6 +81,10 @@ public class ConnectionGroupRESTService {
|
||||
* 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 group to be retrieved.
|
||||
*
|
||||
* @param connectionGroupID
|
||||
* The ID of the connection group to retrieve.
|
||||
*
|
||||
@@ -93,12 +98,14 @@ public class ConnectionGroupRESTService {
|
||||
@Path("/{connectionGroupID}")
|
||||
@AuthProviderRESTExposure
|
||||
public APIConnectionGroup getConnectionGroup(@QueryParam("token") String authToken,
|
||||
@PathParam("connectionGroupID") String connectionGroupID) throws GuacamoleException {
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionGroupID") String connectionGroupID)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Retrieve the requested connection group
|
||||
return new APIConnectionGroup(retrievalService.retrieveConnectionGroup(userContext, connectionGroupID));
|
||||
return new APIConnectionGroup(retrievalService.retrieveConnectionGroup(session, authProviderIdentifier, connectionGroupID));
|
||||
|
||||
}
|
||||
|
||||
@@ -109,6 +116,10 @@ public class ConnectionGroupRESTService {
|
||||
* 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 group to be retrieved.
|
||||
*
|
||||
* @param connectionGroupID
|
||||
* The ID of the connection group to retrieve.
|
||||
*
|
||||
@@ -129,11 +140,13 @@ public class ConnectionGroupRESTService {
|
||||
@Path("/{connectionGroupID}/tree")
|
||||
@AuthProviderRESTExposure
|
||||
public APIConnectionGroup getConnectionGroupTree(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionGroupID") String connectionGroupID,
|
||||
@QueryParam("permission") List<ObjectPermission.Type> permissions)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Retrieve the requested tree, filtering by the given permissions
|
||||
ConnectionGroup treeRoot = retrievalService.retrieveConnectionGroup(userContext, connectionGroupID);
|
||||
@@ -151,6 +164,10 @@ public class ConnectionGroupRESTService {
|
||||
* 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 group to be deleted.
|
||||
*
|
||||
* @param connectionGroupID
|
||||
* The identifier of the connection group to delete.
|
||||
*
|
||||
@@ -161,9 +178,12 @@ public class ConnectionGroupRESTService {
|
||||
@Path("/{connectionGroupID}")
|
||||
@AuthProviderRESTExposure
|
||||
public void deleteConnectionGroup(@QueryParam("token") String authToken,
|
||||
@PathParam("connectionGroupID") String connectionGroupID) throws GuacamoleException {
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionGroupID") String connectionGroupID)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the connection group directory
|
||||
Directory<ConnectionGroup> connectionGroupDirectory = userContext.getConnectionGroupDirectory();
|
||||
@@ -183,6 +203,10 @@ public class ConnectionGroupRESTService {
|
||||
* 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 in which the connection group is to be created.
|
||||
*
|
||||
* @param connectionGroup
|
||||
* The connection group to create.
|
||||
*
|
||||
@@ -196,9 +220,11 @@ public class ConnectionGroupRESTService {
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@AuthProviderRESTExposure
|
||||
public String createConnectionGroup(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
APIConnectionGroup connectionGroup) throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Validate that connection group data was provided
|
||||
if (connectionGroup == null)
|
||||
@@ -222,6 +248,10 @@ public class ConnectionGroupRESTService {
|
||||
* 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 group to be updated.
|
||||
*
|
||||
* @param connectionGroupID
|
||||
* The identifier of the existing connection group to update.
|
||||
*
|
||||
@@ -235,10 +265,13 @@ public class ConnectionGroupRESTService {
|
||||
@Path("/{connectionGroupID}")
|
||||
@AuthProviderRESTExposure
|
||||
public void updateConnectionGroup(@QueryParam("token") String authToken,
|
||||
@PathParam("connectionGroupID") String connectionGroupID, APIConnectionGroup connectionGroup)
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("connectionGroupID") String connectionGroupID,
|
||||
APIConnectionGroup connectionGroup)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Validate that connection group data was provided
|
||||
if (connectionGroup == null)
|
||||
|
@@ -28,6 +28,7 @@ import java.util.Map;
|
||||
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;
|
||||
@@ -36,7 +37,9 @@ import org.glyptodon.guacamole.environment.Environment;
|
||||
import org.glyptodon.guacamole.environment.LocalEnvironment;
|
||||
import org.glyptodon.guacamole.form.Form;
|
||||
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.glyptodon.guacamole.protocols.ProtocolInfo;
|
||||
|
||||
@@ -46,7 +49,7 @@ import org.glyptodon.guacamole.protocols.ProtocolInfo;
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Path("/schema")
|
||||
@Path("/schema/{dataSource}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class SchemaRESTService {
|
||||
@@ -57,6 +60,12 @@ public class SchemaRESTService {
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Service for convenient retrieval of objects.
|
||||
*/
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Retrieves the possible attributes of a user object.
|
||||
*
|
||||
@@ -64,6 +73,10 @@ public class SchemaRESTService {
|
||||
* 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 dictating the available user attributes.
|
||||
*
|
||||
* @return
|
||||
* A collection of forms which describe the possible attributes of a
|
||||
* user object.
|
||||
@@ -74,10 +87,13 @@ public class SchemaRESTService {
|
||||
@GET
|
||||
@Path("/users/attributes")
|
||||
@AuthProviderRESTExposure
|
||||
public Collection<Form> getUserAttributes(@QueryParam("token") String authToken) throws GuacamoleException {
|
||||
public Collection<Form> getUserAttributes(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Retrieve all possible user attributes
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
return userContext.getUserAttributes();
|
||||
|
||||
}
|
||||
@@ -89,6 +105,10 @@ public class SchemaRESTService {
|
||||
* 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 dictating the available connection attributes.
|
||||
*
|
||||
* @return
|
||||
* A collection of forms which describe the possible attributes of a
|
||||
* connection object.
|
||||
@@ -99,10 +119,13 @@ public class SchemaRESTService {
|
||||
@GET
|
||||
@Path("/connections/attributes")
|
||||
@AuthProviderRESTExposure
|
||||
public Collection<Form> getConnectionAttributes(@QueryParam("token") String authToken) throws GuacamoleException {
|
||||
public Collection<Form> getConnectionAttributes(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Retrieve all possible connection attributes
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
return userContext.getConnectionAttributes();
|
||||
|
||||
}
|
||||
@@ -114,6 +137,11 @@ public class SchemaRESTService {
|
||||
* 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 dictating the available connection group
|
||||
* attributes.
|
||||
*
|
||||
* @return
|
||||
* A collection of forms which describe the possible attributes of a
|
||||
* connection group object.
|
||||
@@ -124,10 +152,13 @@ public class SchemaRESTService {
|
||||
@GET
|
||||
@Path("/connectionGroups/attributes")
|
||||
@AuthProviderRESTExposure
|
||||
public Collection<Form> getConnectionGroupAttributes(@QueryParam("token") String authToken) throws GuacamoleException {
|
||||
public Collection<Form> getConnectionGroupAttributes(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Retrieve all possible connection group attributes
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
return userContext.getConnectionGroupAttributes();
|
||||
|
||||
}
|
||||
@@ -139,6 +170,13 @@ public class SchemaRESTService {
|
||||
* 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 dictating the protocols available. Currently, the
|
||||
* UserContext actually does not dictate this, the the same set of
|
||||
* protocols will be retrieved for all users, though the identifier
|
||||
* given here will be validated.
|
||||
*
|
||||
* @return
|
||||
* A map of protocol information, where each key is the unique name
|
||||
* associated with that protocol.
|
||||
@@ -149,10 +187,13 @@ public class SchemaRESTService {
|
||||
@GET
|
||||
@Path("/protocols")
|
||||
@AuthProviderRESTExposure
|
||||
public Map<String, ProtocolInfo> getProtocols(@QueryParam("token") String authToken) throws GuacamoleException {
|
||||
public Map<String, ProtocolInfo> getProtocols(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Verify the given auth token is valid
|
||||
authenticationService.getUserContext(authToken);
|
||||
// Verify the given auth token and auth provider identifier are valid
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get and return a map of all protocols.
|
||||
Environment env = new LocalEnvironment();
|
||||
|
@@ -41,6 +41,7 @@ import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.glyptodon.guacamole.net.auth.Credentials;
|
||||
import org.glyptodon.guacamole.net.auth.Directory;
|
||||
import org.glyptodon.guacamole.net.auth.User;
|
||||
@@ -51,6 +52,7 @@ import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
|
||||
import org.glyptodon.guacamole.net.auth.permission.Permission;
|
||||
import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
|
||||
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
|
||||
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
|
||||
import org.glyptodon.guacamole.net.basic.rest.APIError;
|
||||
import org.glyptodon.guacamole.net.basic.rest.APIPatch;
|
||||
import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.add;
|
||||
@@ -68,8 +70,9 @@ import org.slf4j.LoggerFactory;
|
||||
* A REST Service for handling user CRUD operations.
|
||||
*
|
||||
* @author James Muehlner
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Path("/users")
|
||||
@Path("/data/{dataSource}/users")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class UserRESTService {
|
||||
@@ -122,13 +125,17 @@ public class UserRESTService {
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* Gets a list of users in the system, filtering the returned list by the
|
||||
* given permission, if specified.
|
||||
* Gets a list of users in the given data source (UserContext), filtering
|
||||
* the returned list by the given permission, if specified.
|
||||
*
|
||||
* @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 from which the users are to be retrieved.
|
||||
*
|
||||
* @param permissions
|
||||
* The set of permissions to filter with. A user must have one or more
|
||||
* of these permissions for a user to appear in the result.
|
||||
@@ -145,17 +152,15 @@ public class UserRESTService {
|
||||
@GET
|
||||
@AuthProviderRESTExposure
|
||||
public List<APIUser> getUsers(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@QueryParam("permission") List<ObjectPermission.Type> permissions)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
User self = userContext.self();
|
||||
|
||||
// Do not filter on permissions if no permissions are specified
|
||||
if (permissions != null && permissions.isEmpty())
|
||||
permissions = null;
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// An admin user has access to any user
|
||||
User self = userContext.self();
|
||||
SystemPermissionSet systemPermissions = self.getSystemPermissions();
|
||||
boolean isAdmin = systemPermissions.hasPermission(SystemPermission.Type.ADMINISTER);
|
||||
|
||||
@@ -164,7 +169,7 @@ public class UserRESTService {
|
||||
|
||||
// Filter users, if requested
|
||||
Collection<String> userIdentifiers = userDirectory.getIdentifiers();
|
||||
if (!isAdmin && permissions != null) {
|
||||
if (!isAdmin && permissions != null && !permissions.isEmpty()) {
|
||||
ObjectPermissionSet userPermissions = self.getUserPermissions();
|
||||
userIdentifiers = userPermissions.getAccessibleObjects(permissions, userIdentifiers);
|
||||
}
|
||||
@@ -174,7 +179,6 @@ public class UserRESTService {
|
||||
for (User user : userDirectory.getAll(userIdentifiers))
|
||||
apiUsers.add(new APIUser(user));
|
||||
|
||||
// Return the converted user list
|
||||
return apiUsers;
|
||||
|
||||
}
|
||||
@@ -186,6 +190,10 @@ public class UserRESTService {
|
||||
* 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 from which the requested user is to be retrieved.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to retrieve.
|
||||
*
|
||||
@@ -198,33 +206,48 @@ public class UserRESTService {
|
||||
@GET
|
||||
@Path("/{username}")
|
||||
@AuthProviderRESTExposure
|
||||
public APIUser getUser(@QueryParam("token") String authToken, @PathParam("username") String username)
|
||||
public APIUser getUser(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Retrieve the requested user
|
||||
User user = retrievalService.retrieveUser(userContext, username);
|
||||
User user = retrievalService.retrieveUser(session, authProviderIdentifier, username);
|
||||
return new APIUser(user);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user and returns the username.
|
||||
* @param authToken The authentication token that is used to authenticate
|
||||
* the user performing the operation.
|
||||
* @param user The new user to create.
|
||||
* @throws GuacamoleException If a problem is encountered while creating the user.
|
||||
*
|
||||
* @return The username of the newly created user.
|
||||
* @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 in which the requested user is to be created.
|
||||
*
|
||||
* @param user
|
||||
* The new user to create.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If a problem is encountered while creating the user.
|
||||
*
|
||||
* @return
|
||||
* The username of the newly created user.
|
||||
*/
|
||||
@POST
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@AuthProviderRESTExposure
|
||||
public String createUser(@QueryParam("token") String authToken, APIUser user)
|
||||
public String createUser(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier, APIUser user)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the directory
|
||||
Directory<User> userDirectory = userContext.getUserDirectory();
|
||||
@@ -247,6 +270,10 @@ public class UserRESTService {
|
||||
* 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 in which the requested user is to be updated.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to update.
|
||||
*
|
||||
@@ -260,10 +287,12 @@ public class UserRESTService {
|
||||
@Path("/{username}")
|
||||
@AuthProviderRESTExposure
|
||||
public void updateUser(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username, APIUser user)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the directory
|
||||
Directory<User> userDirectory = userContext.getUserDirectory();
|
||||
@@ -301,6 +330,10 @@ public class UserRESTService {
|
||||
* 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 in which the requested user is to be updated.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to update.
|
||||
*
|
||||
@@ -318,11 +351,13 @@ public class UserRESTService {
|
||||
@Path("/{username}/password")
|
||||
@AuthProviderRESTExposure
|
||||
public void updatePassword(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username,
|
||||
APIUserPasswordUpdate userPasswordUpdate,
|
||||
@Context HttpServletRequest request) throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Build credentials
|
||||
Credentials credentials = new Credentials();
|
||||
@@ -333,7 +368,8 @@ public class UserRESTService {
|
||||
|
||||
// Verify that the old password was correct
|
||||
try {
|
||||
if (userContext.getAuthenticationProvider().authenticateUser(credentials) == null) {
|
||||
AuthenticationProvider authProvider = userContext.getAuthenticationProvider();
|
||||
if (authProvider.authenticateUser(credentials) == null) {
|
||||
throw new APIException(APIError.Type.PERMISSION_DENIED,
|
||||
"Permission denied.");
|
||||
}
|
||||
@@ -366,6 +402,10 @@ public class UserRESTService {
|
||||
* 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 from which the requested user is to be deleted.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to delete.
|
||||
*
|
||||
@@ -376,10 +416,12 @@ public class UserRESTService {
|
||||
@Path("/{username}")
|
||||
@AuthProviderRESTExposure
|
||||
public void deleteUser(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the directory
|
||||
Directory<User> userDirectory = userContext.getUserDirectory();
|
||||
@@ -401,6 +443,10 @@ public class UserRESTService {
|
||||
* 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 in which the requested user is to be found.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to retrieve permissions for.
|
||||
*
|
||||
@@ -414,10 +460,12 @@ public class UserRESTService {
|
||||
@Path("/{username}/permissions")
|
||||
@AuthProviderRESTExposure
|
||||
public APIPermissionSet getPermissions(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
User user;
|
||||
|
||||
@@ -490,6 +538,10 @@ public class UserRESTService {
|
||||
* 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 in which the requested user is to be found.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to modify the permissions of.
|
||||
*
|
||||
@@ -503,10 +555,12 @@ public class UserRESTService {
|
||||
@Path("/{username}/permissions")
|
||||
@AuthProviderRESTExposure
|
||||
public void patchPermissions(@QueryParam("token") String authToken,
|
||||
@PathParam("dataSource") String authProviderIdentifier,
|
||||
@PathParam("username") String username,
|
||||
List<APIPatch<String>> patches) throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||
|
||||
// Get the user
|
||||
User user = userContext.getUserDirectory().get(username);
|
||||
|
@@ -32,7 +32,7 @@ import org.glyptodon.guacamole.net.basic.TunnelRequest;
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelRequest implements TunnelRequest {
|
||||
public class WebSocketTunnelRequest extends TunnelRequest {
|
||||
|
||||
/**
|
||||
* All parameters passed via HTTP to the WebSocket handshake.
|
||||
|
@@ -33,7 +33,7 @@ import org.glyptodon.guacamole.net.basic.TunnelRequest;
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelRequest implements TunnelRequest {
|
||||
public class WebSocketTunnelRequest extends TunnelRequest {
|
||||
|
||||
/**
|
||||
* All parameters passed via HTTP to the WebSocket handshake.
|
||||
|
@@ -99,9 +99,10 @@ angular.module('auth').factory('authenticationService', ['$injector',
|
||||
|
||||
// Store auth data
|
||||
$cookieStore.put(AUTH_COOKIE_ID, {
|
||||
authToken : data.authToken,
|
||||
username : data.username,
|
||||
dataSource : data.dataSource
|
||||
'authToken' : data.authToken,
|
||||
'username' : data.username,
|
||||
'dataSource' : data.dataSource,
|
||||
'availableDataSources' : data.availableDataSources
|
||||
});
|
||||
|
||||
// Process is complete
|
||||
@@ -320,7 +321,7 @@ angular.module('auth').factory('authenticationService', ['$injector',
|
||||
*
|
||||
* @returns {String[]}
|
||||
* The identifiers of all data sources availble to the current user,
|
||||
* or null if no authentication data is present.
|
||||
* or an empty array if no authentication data is present.
|
||||
*/
|
||||
service.getAvailableDataSources = function getAvailableDataSources() {
|
||||
|
||||
@@ -330,7 +331,7 @@ angular.module('auth').factory('authenticationService', ['$injector',
|
||||
return authData.availableDataSources;
|
||||
|
||||
// No auth data present
|
||||
return null;
|
||||
return [];
|
||||
|
||||
};
|
||||
|
||||
|
@@ -158,7 +158,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
var RECONNECT_ACTION = {
|
||||
name : "CLIENT.ACTION_RECONNECT",
|
||||
callback : function reconnectCallback() {
|
||||
$scope.client = guacClientManager.replaceManagedClient(uniqueId, $routeParams.params);
|
||||
$scope.client = guacClientManager.replaceManagedClient($routeParams.id, $routeParams.params);
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
@@ -220,12 +220,12 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
||||
$scope.clipboardData = clipboardData;
|
||||
});
|
||||
|
||||
/*
|
||||
* Parse the type, name, and id out of the url paramteres,
|
||||
* as well as any extra parameters if set.
|
||||
/**
|
||||
* The client which should be attached to the client UI.
|
||||
*
|
||||
* @type ManagedClient
|
||||
*/
|
||||
var uniqueId = $routeParams.type + '/' + $routeParams.id;
|
||||
$scope.client = guacClientManager.getManagedClient(uniqueId, $routeParams.params);
|
||||
$scope.client = guacClientManager.getManagedClient($routeParams.id, $routeParams.params);
|
||||
|
||||
var keysCurrentlyPressed = {};
|
||||
|
||||
|
@@ -28,6 +28,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
|
||||
// Required types
|
||||
var ClientProperties = $injector.get('ClientProperties');
|
||||
var ClientIdentifier = $injector.get('ClientIdentifier');
|
||||
var ManagedClientState = $injector.get('ManagedClientState');
|
||||
var ManagedDisplay = $injector.get('ManagedDisplay');
|
||||
var ManagedFileDownload = $injector.get('ManagedFileDownload');
|
||||
@@ -153,8 +154,8 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
* desired connection ID, display resolution, and supported audio/video
|
||||
* codecs.
|
||||
*
|
||||
* @param {String} id
|
||||
* The ID of the connection or group to connect to.
|
||||
* @param {ClientIdentifier} identifier
|
||||
* The identifier representing the connection or group to connect to.
|
||||
*
|
||||
* @param {String} [connectionParameters]
|
||||
* Any additional HTTP parameters to pass while connecting.
|
||||
@@ -163,7 +164,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
* The string of connection parameters to be passed to the Guacamole
|
||||
* client.
|
||||
*/
|
||||
var getConnectString = function getConnectString(id, connectionParameters) {
|
||||
var getConnectString = function getConnectString(identifier, connectionParameters) {
|
||||
|
||||
// Calculate optimal width/height for display
|
||||
var pixel_density = $window.devicePixelRatio || 1;
|
||||
@@ -173,21 +174,23 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
|
||||
// Build base connect string
|
||||
var connectString =
|
||||
"id=" + encodeURIComponent(id)
|
||||
+ "&authToken=" + encodeURIComponent(authenticationService.getCurrentToken())
|
||||
+ "&width=" + Math.floor(optimal_width)
|
||||
+ "&height=" + Math.floor(optimal_height)
|
||||
+ "&dpi=" + Math.floor(optimal_dpi)
|
||||
"token=" + encodeURIComponent(authenticationService.getCurrentToken())
|
||||
+ "&GUAC_DATA_SOURCE=" + encodeURIComponent(identifier.dataSource)
|
||||
+ "&GUAC_ID=" + encodeURIComponent(identifier.id)
|
||||
+ "&GUAC_TYPE=" + encodeURIComponent(identifier.type)
|
||||
+ "&GUAC_WIDTH=" + Math.floor(optimal_width)
|
||||
+ "&GUAC_HEIGHT=" + Math.floor(optimal_height)
|
||||
+ "&GUAC_DPI=" + Math.floor(optimal_dpi)
|
||||
+ (connectionParameters ? '&' + connectionParameters : '');
|
||||
|
||||
// Add audio mimetypes to connect_string
|
||||
guacAudio.supported.forEach(function(mimetype) {
|
||||
connectString += "&audio=" + encodeURIComponent(mimetype);
|
||||
connectString += "&GUAC_AUDIO=" + encodeURIComponent(mimetype);
|
||||
});
|
||||
|
||||
// Add video mimetypes to connect_string
|
||||
guacVideo.supported.forEach(function(mimetype) {
|
||||
connectString += "&video=" + encodeURIComponent(mimetype);
|
||||
connectString += "&GUAC_VIDEO=" + encodeURIComponent(mimetype);
|
||||
});
|
||||
|
||||
return connectString;
|
||||
@@ -238,7 +241,9 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
* or group.
|
||||
*
|
||||
* @param {String} id
|
||||
* The ID of the connection or group to connect to.
|
||||
* The ID of the connection or group to connect to. This String must be
|
||||
* a valid ClientIdentifier string, as would be generated by
|
||||
* ClientIdentifier.toString().
|
||||
*
|
||||
* @param {String} [connectionParameters]
|
||||
* Any additional HTTP parameters to pass while connecting.
|
||||
@@ -402,23 +407,23 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
||||
// Manage the client display
|
||||
managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay());
|
||||
|
||||
// Connect the Guacamole client
|
||||
client.connect(getConnectString(id, connectionParameters));
|
||||
// Parse connection details from ID
|
||||
var clientIdentifier = ClientIdentifier.fromString(id);
|
||||
|
||||
// Determine type of connection
|
||||
var typePrefix = id.substring(0, 2);
|
||||
// Connect the Guacamole client
|
||||
client.connect(getConnectString(clientIdentifier, connectionParameters));
|
||||
|
||||
// If using a connection, pull connection name
|
||||
if (typePrefix === 'c/') {
|
||||
connectionService.getConnection(id.substring(2))
|
||||
if (clientIdentifier.type === ClientIdentifier.Types.CONNECTION) {
|
||||
connectionService.getConnection(clientIdentifier.dataSource, clientIdentifier.id)
|
||||
.success(function connectionRetrieved(connection) {
|
||||
managedClient.name = connection.name;
|
||||
});
|
||||
}
|
||||
|
||||
// If using a connection group, pull connection name
|
||||
else if (typePrefix === 'g/') {
|
||||
connectionGroupService.getConnectionGroup(id.substring(2))
|
||||
else if (clientIdentifier.type === ClientIdentifier.Types.CONNECTION_GROUP) {
|
||||
connectionGroupService.getConnectionGroup(clientIdentifier.dataSource, clientIdentifier.id)
|
||||
.success(function connectionGroupRetrieved(group) {
|
||||
managedClient.name = group.name;
|
||||
});
|
||||
|
@@ -697,15 +697,15 @@ angular.module('form').controller('timeZoneFieldController', ['$scope', '$inject
|
||||
*/
|
||||
$scope.region = '';
|
||||
|
||||
// Restore time zone selection when region changes
|
||||
$scope.$watch('region', function restoreSelection(region) {
|
||||
$scope.model = selectedTimeZone[region] || null;
|
||||
});
|
||||
|
||||
// Ensure corresponding region is selected
|
||||
$scope.$watch('model', function setModel(model) {
|
||||
$scope.region = timeZoneRegions[model] || '';
|
||||
selectedTimeZone[$scope.region] = model;
|
||||
});
|
||||
|
||||
// Restore time zone selection when region changes
|
||||
$scope.$watch('region', function restoreSelection(region) {
|
||||
$scope.model = selectedTimeZone[region] || null;
|
||||
});
|
||||
|
||||
}]);
|
||||
|
@@ -32,11 +32,12 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
|
||||
scope: {
|
||||
|
||||
/**
|
||||
* The connection group to display.
|
||||
* The connection groups to display as a map of data source
|
||||
* identifier to corresponding root group.
|
||||
*
|
||||
* @type ConnectionGroup|Object
|
||||
* @type Object.<String, ConnectionGroup>
|
||||
*/
|
||||
connectionGroup : '=',
|
||||
connectionGroups : '=',
|
||||
|
||||
/**
|
||||
* Arbitrary object which shall be made available to the connection
|
||||
@@ -92,43 +93,38 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
|
||||
|
||||
// Required services
|
||||
var activeConnectionService = $injector.get('activeConnectionService');
|
||||
var dataSourceService = $injector.get('dataSourceService');
|
||||
|
||||
// Required types
|
||||
var GroupListItem = $injector.get('GroupListItem');
|
||||
|
||||
/**
|
||||
* The number of active connections associated with a given
|
||||
* connection identifier. If this information is unknown, or there
|
||||
* are no active connections for a given identifier, no number will
|
||||
* be stored.
|
||||
* Map of data source identifier to the number of active
|
||||
* connections associated with a given connection identifier.
|
||||
* If this information is unknown, or there are no active
|
||||
* connections for a given identifier, no number will be stored.
|
||||
*
|
||||
* @type Object.<String, Number>
|
||||
* @type Object.<String, Object.<String, Number>>
|
||||
*/
|
||||
var connectionCount = {};
|
||||
|
||||
// Count active connections by connection identifier
|
||||
activeConnectionService.getActiveConnections()
|
||||
.success(function activeConnectionsRetrieved(activeConnections) {
|
||||
|
||||
// Count each active connection by identifier
|
||||
angular.forEach(activeConnections, function addActiveConnection(activeConnection) {
|
||||
|
||||
// If counter already exists, increment
|
||||
var identifier = activeConnection.connectionIdentifier;
|
||||
if (connectionCount[identifier])
|
||||
connectionCount[identifier]++;
|
||||
|
||||
// Otherwise, initialize counter to 1
|
||||
else
|
||||
connectionCount[identifier] = 1;
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
/**
|
||||
* A list of all items which should appear at the root level. As
|
||||
* connections and connection groups from multiple data sources may
|
||||
* be included in a guacGroupList, there may be multiple root
|
||||
* items, even if the root connection group is shown.
|
||||
*
|
||||
* @type GroupListItem[]
|
||||
*/
|
||||
$scope.rootItems = [];
|
||||
|
||||
/**
|
||||
* Returns the number of active usages of a given connection.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The identifier of the data source containing the given
|
||||
* connection.
|
||||
*
|
||||
* @param {Connection} connection
|
||||
* The connection whose active connections should be counted.
|
||||
*
|
||||
@@ -136,8 +132,8 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
|
||||
* The number of currently-active usages of the given
|
||||
* connection.
|
||||
*/
|
||||
var countActiveConnections = function countActiveConnections(connection) {
|
||||
return connectionCount[connection.identifier];
|
||||
var countActiveConnections = function countActiveConnections(dataSource, connection) {
|
||||
return connectionCount[dataSource][connection.identifier];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -173,29 +169,66 @@ angular.module('groupList').directive('guacGroupList', [function guacGroupList()
|
||||
};
|
||||
|
||||
// Set contents whenever the connection group is assigned or changed
|
||||
$scope.$watch("connectionGroup", function setContents(connectionGroup) {
|
||||
$scope.$watch('connectionGroups', function setContents(connectionGroups) {
|
||||
|
||||
if (connectionGroup) {
|
||||
// Reset stored data
|
||||
var dataSources = [];
|
||||
$scope.rootItems = [];
|
||||
connectionCount = {};
|
||||
|
||||
// Create item hierarchy, including connections only if they will be visible
|
||||
var rootItem = GroupListItem.fromConnectionGroup(connectionGroup,
|
||||
!!$scope.connectionTemplate, countActiveConnections);
|
||||
// If connection groups are given, add them to the interface
|
||||
if (connectionGroups) {
|
||||
|
||||
// If root group is to be shown, wrap that group as the child of a fake root group
|
||||
if ($scope.showRootGroup)
|
||||
$scope.rootItem = new GroupListItem({
|
||||
isConnectionGroup : true,
|
||||
isBalancing : false,
|
||||
children : [ rootItem ]
|
||||
// Add each provided connection group
|
||||
angular.forEach(connectionGroups, function addConnectionGroup(connectionGroup, dataSource) {
|
||||
|
||||
// Prepare data source for active connection counting
|
||||
dataSources.push(dataSource);
|
||||
connectionCount[dataSource] = {};
|
||||
|
||||
// Create root item for current connection group
|
||||
var rootItem = GroupListItem.fromConnectionGroup(dataSource, connectionGroup,
|
||||
!!$scope.connectionTemplate, countActiveConnections);
|
||||
|
||||
// If root group is to be shown, add it as a root item
|
||||
if ($scope.showRootGroup)
|
||||
$scope.rootItems.push(rootItem);
|
||||
|
||||
// Otherwise, add its children as root items
|
||||
else {
|
||||
angular.forEach(rootItem.children, function addRootItem(child) {
|
||||
$scope.rootItems.push(child);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Count active connections by connection identifier
|
||||
dataSourceService.apply(
|
||||
activeConnectionService.getActiveConnections,
|
||||
dataSources
|
||||
)
|
||||
.then(function activeConnectionsRetrieved(activeConnectionMap) {
|
||||
|
||||
// Within each data source, count each active connection by identifier
|
||||
angular.forEach(activeConnectionMap, function addActiveConnections(activeConnections, dataSource) {
|
||||
angular.forEach(activeConnections, function addActiveConnection(activeConnection) {
|
||||
|
||||
// If counter already exists, increment
|
||||
var identifier = activeConnection.connectionIdentifier;
|
||||
if (connectionCount[dataSource][identifier])
|
||||
connectionCount[dataSource][identifier]++;
|
||||
|
||||
// Otherwise, initialize counter to 1
|
||||
else
|
||||
connectionCount[dataSource][identifier] = 1;
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// If not wrapped, only the descendants of the root will be shown
|
||||
else
|
||||
$scope.rootItem = rootItem;
|
||||
});
|
||||
|
||||
}
|
||||
else
|
||||
$scope.rootItem = null;
|
||||
|
||||
});
|
||||
|
||||
|
@@ -57,7 +57,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Pager for connections / groups -->
|
||||
<guac-pager page="childrenPage" items="rootItem.children | orderBy : 'name'"
|
||||
<guac-pager page="childrenPage" items="rootItems | orderBy : 'name'"
|
||||
page-size="pageSize"></guac-pager>
|
||||
|
||||
</div>
|
||||
|
@@ -39,6 +39,14 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* The identifier of the data source associated with the connection or
|
||||
* connection group this item represents.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.dataSource = template.dataSource;
|
||||
|
||||
/**
|
||||
* The unique identifier associated with the connection or connection
|
||||
* group this item represents.
|
||||
@@ -124,6 +132,10 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
|
||||
/**
|
||||
* Creates a new GroupListItem using the contents of the given connection.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The identifier of the data source containing the given connection
|
||||
* group.
|
||||
*
|
||||
* @param {ConnectionGroup} connection
|
||||
* The connection whose contents should be represented by the new
|
||||
* GroupListItem.
|
||||
@@ -131,12 +143,15 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
|
||||
* @param {Function} [countActiveConnections]
|
||||
* A getter which returns the current number of active connections for
|
||||
* the given connection. If omitted, the number of active connections
|
||||
* known at the time this function was called is used instead.
|
||||
* known at the time this function was called is used instead. This
|
||||
* function will be passed, in order, the data source identifier and
|
||||
* the connection in question.
|
||||
*
|
||||
* @returns {GroupListItem}
|
||||
* A new GroupListItem which represents the given connection.
|
||||
*/
|
||||
GroupListItem.fromConnection = function fromConnection(connection, countActiveConnections) {
|
||||
GroupListItem.fromConnection = function fromConnection(dataSource,
|
||||
connection, countActiveConnections) {
|
||||
|
||||
// Return item representing the given connection
|
||||
return new GroupListItem({
|
||||
@@ -145,6 +160,7 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
|
||||
name : connection.name,
|
||||
identifier : connection.identifier,
|
||||
protocol : connection.protocol,
|
||||
dataSource : dataSource,
|
||||
|
||||
// Type information
|
||||
isConnection : true,
|
||||
@@ -155,7 +171,7 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
|
||||
|
||||
// Use getter, if provided
|
||||
if (countActiveConnections)
|
||||
return countActiveConnections(connection);
|
||||
return countActiveConnections(dataSource, connection);
|
||||
|
||||
return connection.activeConnections;
|
||||
|
||||
@@ -172,6 +188,10 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
|
||||
* Creates a new GroupListItem using the contents and descendants of the
|
||||
* given connection group.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The identifier of the data source containing the given connection
|
||||
* group.
|
||||
*
|
||||
* @param {ConnectionGroup} connectionGroup
|
||||
* The connection group whose contents and descendants should be
|
||||
* represented by the new GroupListItem and its descendants.
|
||||
@@ -183,34 +203,41 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
|
||||
* @param {Function} [countActiveConnections]
|
||||
* A getter which returns the current number of active connections for
|
||||
* the given connection. If omitted, the number of active connections
|
||||
* known at the time this function was called is used instead.
|
||||
* known at the time this function was called is used instead. This
|
||||
* function will be passed, in order, the data source identifier and
|
||||
* the connection group in question.
|
||||
*
|
||||
* @param {Function} [countActiveConnectionGroups]
|
||||
* A getter which returns the current number of active connections for
|
||||
* the given connection group. If omitted, the number of active
|
||||
* connections known at the time this function was called is used
|
||||
* instead.
|
||||
* instead. This function will be passed, in order, the data source
|
||||
* identifier and the connection group in question.
|
||||
*
|
||||
* @returns {GroupListItem}
|
||||
* A new GroupListItem which represents the given connection group,
|
||||
* including all descendants.
|
||||
*/
|
||||
GroupListItem.fromConnectionGroup = function fromConnectionGroup(connectionGroup,
|
||||
includeConnections, countActiveConnections, countActiveConnectionGroups) {
|
||||
GroupListItem.fromConnectionGroup = function fromConnectionGroup(dataSource,
|
||||
connectionGroup, includeConnections, countActiveConnections,
|
||||
countActiveConnectionGroups) {
|
||||
|
||||
var children = [];
|
||||
|
||||
// Add any child connections
|
||||
if (connectionGroup.childConnections && includeConnections !== false) {
|
||||
connectionGroup.childConnections.forEach(function addChildConnection(child) {
|
||||
children.push(GroupListItem.fromConnection(child, countActiveConnections));
|
||||
children.push(GroupListItem.fromConnection(dataSource, child,
|
||||
countActiveConnections));
|
||||
});
|
||||
}
|
||||
|
||||
// Add any child groups
|
||||
if (connectionGroup.childConnectionGroups) {
|
||||
connectionGroup.childConnectionGroups.forEach(function addChildGroup(child) {
|
||||
children.push(GroupListItem.fromConnectionGroup(child, includeConnections, countActiveConnections, countActiveConnectionGroups));
|
||||
children.push(GroupListItem.fromConnectionGroup(dataSource,
|
||||
child, includeConnections, countActiveConnections,
|
||||
countActiveConnectionGroups));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,6 +247,7 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
|
||||
// Identifying information
|
||||
name : connectionGroup.name,
|
||||
identifier : connectionGroup.identifier,
|
||||
dataSource : dataSource,
|
||||
|
||||
// Type information
|
||||
isConnection : false,
|
||||
@@ -234,7 +262,7 @@ angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', functio
|
||||
|
||||
// Use getter, if provided
|
||||
if (countActiveConnectionGroups)
|
||||
return countActiveConnectionGroups(connectionGroup);
|
||||
return countActiveConnectionGroups(dataSource, connectionGroup);
|
||||
|
||||
return connectionGroup.activeConnections;
|
||||
|
||||
|
@@ -27,19 +27,22 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
|
||||
function homeController($scope, $injector) {
|
||||
|
||||
// Get required types
|
||||
var ConnectionGroup = $injector.get("ConnectionGroup");
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var ClientIdentifier = $injector.get('ClientIdentifier');
|
||||
|
||||
// Get required services
|
||||
var authenticationService = $injector.get("authenticationService");
|
||||
var connectionGroupService = $injector.get("connectionGroupService");
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var dataSourceService = $injector.get('dataSourceService');
|
||||
|
||||
/**
|
||||
* The root connection group, or null if the connection group hierarchy has
|
||||
* not yet been loaded.
|
||||
* Map of data source identifier to the root connection group of that data
|
||||
* source, or null if the connection group hierarchy has not yet been
|
||||
* loaded.
|
||||
*
|
||||
* @type ConnectionGroup
|
||||
* @type Object.<String, ConnectionGroup>
|
||||
*/
|
||||
$scope.rootConnectionGroup = null;
|
||||
$scope.rootConnectionGroups = null;
|
||||
|
||||
/**
|
||||
* Returns whether critical data has completed being loaded.
|
||||
@@ -54,10 +57,59 @@ angular.module('home').controller('homeController', ['$scope', '$injector',
|
||||
|
||||
};
|
||||
|
||||
// Retrieve root group and all descendants
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER)
|
||||
.success(function rootGroupRetrieved(rootConnectionGroup) {
|
||||
$scope.rootConnectionGroup = rootConnectionGroup;
|
||||
/**
|
||||
* Object passed to the guacGroupList directive, providing context-specific
|
||||
* functions or data.
|
||||
*/
|
||||
$scope.context = {
|
||||
|
||||
/**
|
||||
* Returns the unique string identifier which must be used when
|
||||
* connecting to a connection or connection group represented by the
|
||||
* given GroupListItem.
|
||||
*
|
||||
* @param {GroupListItem} item
|
||||
* The GroupListItem to determine the client identifier of.
|
||||
*
|
||||
* @returns {String}
|
||||
* The client identifier associated with the connection or
|
||||
* connection group represented by the given GroupListItem, or null
|
||||
* if the GroupListItem cannot have an associated client
|
||||
* identifier.
|
||||
*/
|
||||
getClientIdentifier : function getClientIdentifier(item) {
|
||||
|
||||
// If the item is a connection, generate a connection identifier
|
||||
if (item.isConnection)
|
||||
return ClientIdentifier.toString({
|
||||
dataSource : item.dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION,
|
||||
id : item.identifier
|
||||
});
|
||||
|
||||
// If the item is a connection, generate a connection group identifier
|
||||
if (item.isConnectionGroup)
|
||||
return ClientIdentifier.toString({
|
||||
dataSource : item.dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION_GROUP,
|
||||
id : item.identifier
|
||||
});
|
||||
|
||||
// Otherwise, no such identifier can exist
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Retrieve root groups and all descendants
|
||||
dataSourceService.apply(
|
||||
connectionGroupService.getConnectionGroupTree,
|
||||
authenticationService.getAvailableDataSources(),
|
||||
ConnectionGroup.ROOT_IDENTIFIER
|
||||
)
|
||||
.then(function rootGroupsRetrieved(rootConnectionGroups) {
|
||||
$scope.rootConnectionGroups = rootConnectionGroups;
|
||||
});
|
||||
|
||||
}]);
|
||||
|
@@ -31,13 +31,15 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
|
||||
scope: {
|
||||
|
||||
/**
|
||||
* The root connection group, and all visible descendants.
|
||||
* Recent connections will only be shown if they exist within this
|
||||
* hierarchy, regardless of their existence within the history.
|
||||
* The root connection groups to display, and all visible
|
||||
* descendants, as a map of data source identifier to the root
|
||||
* connection group within that data source. Recent connections
|
||||
* will only be shown if they exist within this hierarchy,
|
||||
* regardless of their existence within the history.
|
||||
*
|
||||
* @type ConnectionGroup
|
||||
* @type Object.<String, ConnectionGroup>
|
||||
*/
|
||||
rootGroup : '='
|
||||
rootGroups : '='
|
||||
|
||||
},
|
||||
|
||||
@@ -46,6 +48,7 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
|
||||
|
||||
// Required types
|
||||
var ActiveConnection = $injector.get('ActiveConnection');
|
||||
var ClientIdentifier = $injector.get('ClientIdentifier');
|
||||
var RecentConnection = $injector.get('RecentConnection');
|
||||
|
||||
// Required services
|
||||
@@ -92,13 +95,21 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
|
||||
* Adds the given connection to the internal set of visible
|
||||
* objects.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The identifier of the data source associated with the
|
||||
* given connection group.
|
||||
*
|
||||
* @param {Connection} connection
|
||||
* The connection to add to the internal set of visible objects.
|
||||
*/
|
||||
var addVisibleConnection = function addVisibleConnection(connection) {
|
||||
var addVisibleConnection = function addVisibleConnection(dataSource, connection) {
|
||||
|
||||
// Add given connection to set of visible objects
|
||||
visibleObjects['c/' + connection.identifier] = connection;
|
||||
visibleObjects[ClientIdentifier.toString({
|
||||
dataSource : dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION,
|
||||
id : connection.identifier
|
||||
})] = connection;
|
||||
|
||||
};
|
||||
|
||||
@@ -106,27 +117,39 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
|
||||
* Adds the given connection group to the internal set of visible
|
||||
* objects, along with any descendants.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The identifier of the data source associated with the
|
||||
* given connection group.
|
||||
*
|
||||
* @param {ConnectionGroup} connectionGroup
|
||||
* The connection group to add to the internal set of visible
|
||||
* objects, along with any descendants.
|
||||
*/
|
||||
var addVisibleConnectionGroup = function addVisibleConnectionGroup(connectionGroup) {
|
||||
var addVisibleConnectionGroup = function addVisibleConnectionGroup(dataSource, connectionGroup) {
|
||||
|
||||
// Add given connection group to set of visible objects
|
||||
visibleObjects['g/' + connectionGroup.identifier] = connectionGroup;
|
||||
visibleObjects[ClientIdentifier.toString({
|
||||
dataSource : dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION_GROUP,
|
||||
id : connectionGroup.identifier
|
||||
})] = connectionGroup;
|
||||
|
||||
// Add all child connections
|
||||
if (connectionGroup.childConnections)
|
||||
connectionGroup.childConnections.forEach(addVisibleConnection);
|
||||
connectionGroup.childConnections.forEach(function addChildConnection(child) {
|
||||
addVisibleConnection(dataSource, child);
|
||||
});
|
||||
|
||||
// Add all child connection groups
|
||||
if (connectionGroup.childConnectionGroups)
|
||||
connectionGroup.childConnectionGroups.forEach(addVisibleConnectionGroup);
|
||||
connectionGroup.childConnectionGroups.forEach(function addChildConnectionGroup(child) {
|
||||
addVisibleConnectionGroup(dataSource, child);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// Update visible objects when root group is set
|
||||
$scope.$watch("rootGroup", function setRootGroup(rootGroup) {
|
||||
// Update visible objects when root groups are set
|
||||
$scope.$watch("rootGroups", function setRootGroups(rootGroups) {
|
||||
|
||||
// Clear connection arrays
|
||||
$scope.activeConnections = [];
|
||||
@@ -134,8 +157,11 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo
|
||||
|
||||
// Produce collection of visible objects
|
||||
visibleObjects = {};
|
||||
if (rootGroup)
|
||||
addVisibleConnectionGroup(rootGroup);
|
||||
if (rootGroups) {
|
||||
angular.forEach(rootGroups, function addConnectionGroup(rootGroup, dataSource) {
|
||||
addVisibleConnectionGroup(dataSource, rootGroup);
|
||||
});
|
||||
}
|
||||
|
||||
var managedClients = guacClientManager.getManagedClients();
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<a ng-href="#/client/c/{{item.identifier}}">
|
||||
<a ng-href="#/client/{{context.getClientIdentifier(item)}}">
|
||||
<!--
|
||||
Copyright (C) 2014 Glyptodon LLC
|
||||
|
||||
|
@@ -21,6 +21,6 @@
|
||||
THE SOFTWARE.
|
||||
-->
|
||||
|
||||
<a ng-show="item.isBalancing" ng-href="#/client/g/{{item.identifier}}">{{item.name}}</a>
|
||||
<a ng-show="item.isBalancing" ng-href="#/client/{{context.getClientIdentifier(item)}}">{{item.name}}</a>
|
||||
<span ng-show="!item.isBalancing">{{item.name}}</span>
|
||||
</span>
|
||||
|
@@ -30,14 +30,15 @@
|
||||
<guac-user-menu></guac-user-menu>
|
||||
</div>
|
||||
<div class="recent-connections">
|
||||
<guac-recent-connections root-group="rootConnectionGroup"></guac-recent-connections>
|
||||
<guac-recent-connections root-groups="rootConnectionGroups"></guac-recent-connections>
|
||||
</div>
|
||||
|
||||
<!-- All connections for this user -->
|
||||
<h2 class="header">{{'HOME.SECTION_HEADER_ALL_CONNECTIONS' | translate}}</h2>
|
||||
<div class="all-connections">
|
||||
<guac-group-list
|
||||
connection-group="rootConnectionGroup"
|
||||
context="context"
|
||||
connection-groups="rootConnectionGroups"
|
||||
connection-template="'app/home/templates/connection.html'"
|
||||
connection-group-template="'app/home/templates/connectionGroup.html'"
|
||||
page-size="20"></guac-group-list>
|
||||
|
@@ -123,7 +123,7 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
|
||||
})
|
||||
|
||||
// Management screen
|
||||
.when('/settings/:tab', {
|
||||
.when('/settings/:dataSource?/:tab', {
|
||||
title : 'APP.NAME',
|
||||
bodyClassName : 'settings',
|
||||
templateUrl : 'app/settings/templates/settings.html',
|
||||
@@ -132,7 +132,7 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
|
||||
})
|
||||
|
||||
// Connection editor
|
||||
.when('/manage/connections/:id?', {
|
||||
.when('/manage/:dataSource/connections/:id?', {
|
||||
title : 'APP.NAME',
|
||||
bodyClassName : 'manage',
|
||||
templateUrl : 'app/manage/templates/manageConnection.html',
|
||||
@@ -141,7 +141,7 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
|
||||
})
|
||||
|
||||
// Connection group editor
|
||||
.when('/manage/connectionGroups/:id?', {
|
||||
.when('/manage/:dataSource/connectionGroups/:id?', {
|
||||
title : 'APP.NAME',
|
||||
bodyClassName : 'manage',
|
||||
templateUrl : 'app/manage/templates/manageConnectionGroup.html',
|
||||
@@ -150,7 +150,7 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
|
||||
})
|
||||
|
||||
// User editor
|
||||
.when('/manage/users/:id', {
|
||||
.when('/manage/:dataSource/users/:id', {
|
||||
title : 'APP.NAME',
|
||||
bodyClassName : 'manage',
|
||||
templateUrl : 'app/manage/templates/manageUser.html',
|
||||
@@ -159,7 +159,7 @@ angular.module('index').config(['$routeProvider', '$locationProvider',
|
||||
})
|
||||
|
||||
// Client view
|
||||
.when('/client/:type/:id/:params?', {
|
||||
.when('/client/:id/:params?', {
|
||||
bodyClassName : 'client',
|
||||
templateUrl : 'app/client/templates/client.html',
|
||||
controller : 'clientController',
|
||||
|
@@ -56,6 +56,14 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The unique identifier of the data source containing the connection being
|
||||
* edited.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var dataSource = $routeParams.dataSource;
|
||||
|
||||
/**
|
||||
* The identifier of the original connection from which this connection is
|
||||
* being cloned. Only valid if this is a new connection.
|
||||
@@ -178,20 +186,24 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
};
|
||||
|
||||
// Pull connection attribute schema
|
||||
schemaService.getConnectionAttributes().success(function attributesReceived(attributes) {
|
||||
schemaService.getConnectionAttributes(dataSource)
|
||||
.success(function attributesReceived(attributes) {
|
||||
$scope.attributes = attributes;
|
||||
});
|
||||
|
||||
// Pull connection group hierarchy
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER,
|
||||
[PermissionSet.ObjectPermissionType.ADMINISTER])
|
||||
connectionGroupService.getConnectionGroupTree(
|
||||
dataSource,
|
||||
ConnectionGroup.ROOT_IDENTIFIER,
|
||||
[PermissionSet.ObjectPermissionType.ADMINISTER]
|
||||
)
|
||||
.success(function connectionGroupReceived(rootGroup) {
|
||||
$scope.rootGroup = rootGroup;
|
||||
});
|
||||
|
||||
// Query the user's permissions for the current connection
|
||||
permissionService.getPermissions(authenticationService.getCurrentUsername())
|
||||
.success(function permissionsReceived(permissions) {
|
||||
permissionService.getPermissions(dataSource, authenticationService.getCurrentUsername())
|
||||
.success(function permissionsReceived(permissions) {
|
||||
|
||||
$scope.permissions = permissions;
|
||||
|
||||
@@ -220,7 +232,8 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
});
|
||||
|
||||
// Get protocol metadata
|
||||
schemaService.getProtocols().success(function protocolsReceived(protocols) {
|
||||
schemaService.getProtocols(dataSource)
|
||||
.success(function protocolsReceived(protocols) {
|
||||
$scope.protocols = protocols;
|
||||
});
|
||||
|
||||
@@ -233,12 +246,14 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
if (identifier) {
|
||||
|
||||
// Pull data from existing connection
|
||||
connectionService.getConnection(identifier).success(function connectionRetrieved(connection) {
|
||||
connectionService.getConnection(dataSource, identifier)
|
||||
.success(function connectionRetrieved(connection) {
|
||||
$scope.connection = connection;
|
||||
});
|
||||
|
||||
// Pull connection history
|
||||
connectionService.getConnectionHistory(identifier).success(function historyReceived(historyEntries) {
|
||||
connectionService.getConnectionHistory(dataSource, identifier)
|
||||
.success(function historyReceived(historyEntries) {
|
||||
|
||||
// Wrap all history entries for sake of display
|
||||
$scope.historyEntryWrappers = [];
|
||||
@@ -249,7 +264,8 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
});
|
||||
|
||||
// Pull connection parameters
|
||||
connectionService.getConnectionParameters(identifier).success(function parametersReceived(parameters) {
|
||||
connectionService.getConnectionParameters(dataSource, identifier)
|
||||
.success(function parametersReceived(parameters) {
|
||||
$scope.parameters = parameters;
|
||||
});
|
||||
}
|
||||
@@ -258,7 +274,8 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
else if (cloneSourceIdentifier) {
|
||||
|
||||
// Pull data from cloned connection
|
||||
connectionService.getConnection(cloneSourceIdentifier).success(function connectionRetrieved(connection) {
|
||||
connectionService.getConnection(dataSource, cloneSourceIdentifier)
|
||||
.success(function connectionRetrieved(connection) {
|
||||
$scope.connection = connection;
|
||||
|
||||
// Clear the identifier field because this connection is new
|
||||
@@ -269,7 +286,8 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
$scope.historyEntryWrappers = [];
|
||||
|
||||
// Pull connection parameters from cloned connection
|
||||
connectionService.getConnectionParameters(cloneSourceIdentifier).success(function parametersReceived(parameters) {
|
||||
connectionService.getConnectionParameters(dataSource, cloneSourceIdentifier)
|
||||
.success(function parametersReceived(parameters) {
|
||||
$scope.parameters = parameters;
|
||||
});
|
||||
}
|
||||
@@ -332,7 +350,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
* Cancels all pending edits, returning to the management page.
|
||||
*/
|
||||
$scope.cancel = function cancel() {
|
||||
$location.path('/settings/connections');
|
||||
$location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -340,7 +358,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
* which is prepopulated with the data from the connection currently being edited.
|
||||
*/
|
||||
$scope.cloneConnection = function cloneConnection() {
|
||||
$location.path('/manage/connections').search('clone', identifier);
|
||||
$location.path('/manage/' + encodeURIComponent(dataSource) + '/connections').search('clone', identifier);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -352,9 +370,9 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
$scope.connection.parameters = $scope.parameters;
|
||||
|
||||
// Save the connection
|
||||
connectionService.saveConnection($scope.connection)
|
||||
connectionService.saveConnection(dataSource, $scope.connection)
|
||||
.success(function savedConnection() {
|
||||
$location.path('/settings/connections');
|
||||
$location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
@@ -402,9 +420,9 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
|
||||
var deleteConnectionImmediately = function deleteConnectionImmediately() {
|
||||
|
||||
// Delete the connection
|
||||
connectionService.deleteConnection($scope.connection)
|
||||
connectionService.deleteConnection(dataSource, $scope.connection)
|
||||
.success(function deletedConnection() {
|
||||
$location.path('/settings/connections');
|
||||
$location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
|
@@ -51,6 +51,14 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The unique identifier of the data source containing the connection group
|
||||
* being edited.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var dataSource = $routeParams.dataSource;
|
||||
|
||||
/**
|
||||
* The identifier of the connection group being edited. If a new connection
|
||||
* group is being created, this will not be defined.
|
||||
@@ -123,13 +131,14 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
|
||||
};
|
||||
|
||||
// Pull connection group attribute schema
|
||||
schemaService.getConnectionGroupAttributes().success(function attributesReceived(attributes) {
|
||||
schemaService.getConnectionGroupAttributes(dataSource)
|
||||
.success(function attributesReceived(attributes) {
|
||||
$scope.attributes = attributes;
|
||||
});
|
||||
|
||||
// Query the user's permissions for the current connection group
|
||||
permissionService.getPermissions(authenticationService.getCurrentUsername())
|
||||
.success(function permissionsReceived(permissions) {
|
||||
permissionService.getPermissions(dataSource, authenticationService.getCurrentUsername())
|
||||
.success(function permissionsReceived(permissions) {
|
||||
|
||||
$scope.permissions = permissions;
|
||||
|
||||
@@ -150,14 +159,19 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
|
||||
|
||||
|
||||
// Pull connection group hierarchy
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, [PermissionSet.ObjectPermissionType.ADMINISTER])
|
||||
connectionGroupService.getConnectionGroupTree(
|
||||
dataSource,
|
||||
ConnectionGroup.ROOT_IDENTIFIER,
|
||||
[PermissionSet.ObjectPermissionType.ADMINISTER]
|
||||
)
|
||||
.success(function connectionGroupReceived(rootGroup) {
|
||||
$scope.rootGroup = rootGroup;
|
||||
});
|
||||
|
||||
// If we are editing an existing connection group, pull its data
|
||||
if (identifier) {
|
||||
connectionGroupService.getConnectionGroup(identifier).success(function connectionGroupReceived(connectionGroup) {
|
||||
connectionGroupService.getConnectionGroup(dataSource, identifier)
|
||||
.success(function connectionGroupReceived(connectionGroup) {
|
||||
$scope.connectionGroup = connectionGroup;
|
||||
});
|
||||
}
|
||||
@@ -187,7 +201,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
|
||||
* Cancels all pending edits, returning to the management page.
|
||||
*/
|
||||
$scope.cancel = function cancel() {
|
||||
$location.path('/settings/connections');
|
||||
$location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -197,9 +211,9 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
|
||||
$scope.saveConnectionGroup = function saveConnectionGroup() {
|
||||
|
||||
// Save the connection
|
||||
connectionGroupService.saveConnectionGroup($scope.connectionGroup)
|
||||
connectionGroupService.saveConnectionGroup(dataSource, $scope.connectionGroup)
|
||||
.success(function savedConnectionGroup() {
|
||||
$location.path('/settings/connections');
|
||||
$location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
@@ -247,9 +261,9 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope'
|
||||
var deleteConnectionGroupImmediately = function deleteConnectionGroupImmediately() {
|
||||
|
||||
// Delete the connection group
|
||||
connectionGroupService.deleteConnectionGroup($scope.connectionGroup)
|
||||
connectionGroupService.deleteConnectionGroup(dataSource, $scope.connectionGroup)
|
||||
.success(function deletedConnectionGroup() {
|
||||
$location.path('/settings/connections');
|
||||
$location.path('/settings/' + encodeURIComponent(dataSource) + '/connections');
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Glyptodon LLC
|
||||
* 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
|
||||
@@ -28,18 +28,23 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
|
||||
// Required types
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var PageDefinition = $injector.get('PageDefinition');
|
||||
var PermissionFlagSet = $injector.get('PermissionFlagSet');
|
||||
var PermissionSet = $injector.get('PermissionSet');
|
||||
var User = $injector.get('User');
|
||||
|
||||
// Required services
|
||||
var $location = $injector.get('$location');
|
||||
var $routeParams = $injector.get('$routeParams');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
var schemaService = $injector.get('schemaService');
|
||||
var userService = $injector.get('userService');
|
||||
var $location = $injector.get('$location');
|
||||
var $routeParams = $injector.get('$routeParams');
|
||||
var $q = $injector.get('$q');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var dataSourceService = $injector.get('dataSourceService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
var schemaService = $injector.get('schemaService');
|
||||
var translationStringService = $injector.get('translationStringService');
|
||||
var userService = $injector.get('userService');
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to showStatus which
|
||||
@@ -53,6 +58,29 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The identifiers of all data sources currently available to the
|
||||
* authenticated user.
|
||||
*
|
||||
* @type String[]
|
||||
*/
|
||||
var dataSources = authenticationService.getAvailableDataSources();
|
||||
|
||||
/**
|
||||
* The username of the current, authenticated user.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var currentUsername = authenticationService.getCurrentUsername();
|
||||
|
||||
/**
|
||||
* The unique identifier of the data source containing the user being
|
||||
* edited.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var dataSource = $routeParams.dataSource;
|
||||
|
||||
/**
|
||||
* The username of the user being edited.
|
||||
*
|
||||
@@ -60,6 +88,16 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
*/
|
||||
var username = $routeParams.id;
|
||||
|
||||
/**
|
||||
* Whether the user being modified actually exists. If the user does not
|
||||
* yet exist, a different REST service call must be made to create that
|
||||
* user rather than update an existing user. If the user has not yet been
|
||||
* loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
var userExists = null;
|
||||
|
||||
/**
|
||||
* The user being modified.
|
||||
*
|
||||
@@ -75,25 +113,13 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
$scope.permissionFlags = null;
|
||||
|
||||
/**
|
||||
* The root connection group of the connection group hierarchy.
|
||||
* A map of data source identifiers to the root connection groups within
|
||||
* thost data sources. As only one data source is applicable to any one
|
||||
* user being edited/created, this will only contain a single key.
|
||||
*
|
||||
* @type ConnectionGroup
|
||||
* @type Object.<String, ConnectionGroup>
|
||||
*/
|
||||
$scope.rootGroup = null;
|
||||
|
||||
/**
|
||||
* Whether the authenticated user has UPDATE permission for the user being edited.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.hasUpdatePermission = null;
|
||||
|
||||
/**
|
||||
* Whether the authenticated user has DELETE permission for the user being edited.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.hasDeletePermission = null;
|
||||
$scope.rootGroups = null;
|
||||
|
||||
/**
|
||||
* All permissions associated with the current user, or null if the user's
|
||||
@@ -112,6 +138,14 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
*/
|
||||
$scope.attributes = null;
|
||||
|
||||
/**
|
||||
* The pages associated with each user account having the given username.
|
||||
* Each user account will be associated with a particular data source.
|
||||
*
|
||||
* @type PageDefinition[]
|
||||
*/
|
||||
$scope.accountPages = [];
|
||||
|
||||
/**
|
||||
* Returns whether critical data has completed being loaded.
|
||||
*
|
||||
@@ -123,54 +157,227 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
|
||||
return $scope.user !== null
|
||||
&& $scope.permissionFlags !== null
|
||||
&& $scope.rootGroup !== null
|
||||
&& $scope.rootGroups !== null
|
||||
&& $scope.permissions !== null
|
||||
&& $scope.attributes !== null
|
||||
&& $scope.canSaveUser !== null
|
||||
&& $scope.canDeleteUser !== null;
|
||||
&& $scope.attributes !== null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the current user can change attributes associated with
|
||||
* the user being edited.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the current user can change attributes associated with the
|
||||
* user being edited, false otherwise.
|
||||
*/
|
||||
$scope.canChangeAttributes = function canChangeAttributes() {
|
||||
|
||||
// Do not check if permissions are not yet loaded
|
||||
if (!$scope.permissions)
|
||||
return false;
|
||||
|
||||
// Attributes can always be set if we are creating the user
|
||||
if (!userExists)
|
||||
return true;
|
||||
|
||||
// The administrator can always change attributes
|
||||
if (PermissionSet.hasSystemPermission($scope.permissions,
|
||||
PermissionSet.SystemPermissionType.ADMINISTER))
|
||||
return true;
|
||||
|
||||
// Otherwise, can change attributes if we have permission to update this user
|
||||
return PermissionSet.hasUserPermission($scope.permissions,
|
||||
PermissionSet.ObjectPermissionType.UPDATE, username);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the current user can change permissions of any kind
|
||||
* which are associated with the user being edited.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the current user can grant or revoke permissions of any kind
|
||||
* which are associated with the user being edited, false otherwise.
|
||||
*/
|
||||
$scope.canChangePermissions = function canChangePermissions() {
|
||||
|
||||
// Do not check if permissions are not yet loaded
|
||||
if (!$scope.permissions)
|
||||
return false;
|
||||
|
||||
// Permissions can always be set if we are creating the user
|
||||
if (!userExists)
|
||||
return true;
|
||||
|
||||
// The administrator can always modify permissions
|
||||
if (PermissionSet.hasSystemPermission($scope.permissions,
|
||||
PermissionSet.SystemPermissionType.ADMINISTER))
|
||||
return true;
|
||||
|
||||
// Otherwise, can only modify permissions if we have explicit
|
||||
// ADMINISTER permission
|
||||
return PermissionSet.hasUserPermission($scope.permissions,
|
||||
PermissionSet.ObjectPermissionType.ADMINISTER, username);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the current user can change the system permissions
|
||||
* granted to the user being edited.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the current user can grant or revoke system permissions to
|
||||
* the user being edited, false otherwise.
|
||||
*/
|
||||
$scope.canChangeSystemPermissions = function canChangeSystemPermissions() {
|
||||
|
||||
// Do not check if permissions are not yet loaded
|
||||
if (!$scope.permissions)
|
||||
return false;
|
||||
|
||||
// Only the administrator can modify system permissions
|
||||
return PermissionSet.hasSystemPermission($scope.permissions,
|
||||
PermissionSet.SystemPermissionType.ADMINISTER);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the current user can save the user being edited. Saving
|
||||
* will create or update that user depending on whether the user already
|
||||
* exists.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the current user can save changes to the user being edited,
|
||||
* false otherwise.
|
||||
*/
|
||||
$scope.canSaveUser = function canSaveUser() {
|
||||
|
||||
// Do not check if permissions are not yet loaded
|
||||
if (!$scope.permissions)
|
||||
return false;
|
||||
|
||||
// The administrator can always save users
|
||||
if (PermissionSet.hasSystemPermission($scope.permissions,
|
||||
PermissionSet.SystemPermissionType.ADMINISTER))
|
||||
return true;
|
||||
|
||||
// If user does not exist, can only save if we have permission to create users
|
||||
if (!userExists)
|
||||
return PermissionSet.hasSystemPermission($scope.permissions,
|
||||
PermissionSet.SystemPermissionType.CREATE_USER);
|
||||
|
||||
// Otherwise, can only save if we have permission to update this user
|
||||
return PermissionSet.hasUserPermission($scope.permissions,
|
||||
PermissionSet.ObjectPermissionType.UPDATE, username);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the current user can delete the user being edited.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the current user can delete the user being edited, false
|
||||
* otherwise.
|
||||
*/
|
||||
$scope.canDeleteUser = function canDeleteUser() {
|
||||
|
||||
// Do not check if permissions are not yet loaded
|
||||
if (!$scope.permissions)
|
||||
return false;
|
||||
|
||||
// Can't delete what doesn't exist
|
||||
if (!userExists)
|
||||
return false;
|
||||
|
||||
// The administrator can always delete users
|
||||
if (PermissionSet.hasSystemPermission($scope.permissions,
|
||||
PermissionSet.SystemPermissionType.ADMINISTER))
|
||||
return true;
|
||||
|
||||
// Otherwise, require explicit DELETE permission on the user
|
||||
return PermissionSet.hasUserPermission($scope.permissions,
|
||||
PermissionSet.ObjectPermissionType.DELETE, username);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the user being edited is read-only, and thus cannot be
|
||||
* modified by the current user.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the user being edited is actually read-only and cannot be
|
||||
* edited at all, false otherwise.
|
||||
*/
|
||||
$scope.isReadOnly = function isReadOnly() {
|
||||
return !$scope.canSaveUser();
|
||||
};
|
||||
|
||||
// Pull user attribute schema
|
||||
schemaService.getUserAttributes().success(function attributesReceived(attributes) {
|
||||
schemaService.getUserAttributes(dataSource).success(function attributesReceived(attributes) {
|
||||
$scope.attributes = attributes;
|
||||
});
|
||||
|
||||
// Pull user data
|
||||
userService.getUser(username).success(function userReceived(user) {
|
||||
$scope.user = user;
|
||||
dataSourceService.apply(userService.getUser, dataSources, username)
|
||||
.then(function usersReceived(users) {
|
||||
|
||||
// Get user for currently-selected data source
|
||||
$scope.user = users[dataSource];
|
||||
|
||||
// Create skeleton user if user does not exist
|
||||
if (!$scope.user) {
|
||||
userExists = false;
|
||||
$scope.user = new User({
|
||||
'username' : username
|
||||
});
|
||||
}
|
||||
else
|
||||
userExists = true;
|
||||
|
||||
// Generate pages for each applicable data source
|
||||
$scope.accountPages = [];
|
||||
angular.forEach(dataSources, function addAccountPage(dataSource) {
|
||||
|
||||
// Determine whether data source contains this user
|
||||
var linked = dataSource in users;
|
||||
|
||||
// Add page entry
|
||||
$scope.accountPages.push(new PageDefinition({
|
||||
name : translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME',
|
||||
url : '/manage/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username),
|
||||
className : linked ? 'linked' : 'unlinked'
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Pull user permissions
|
||||
permissionService.getPermissions(username).success(function gotPermissions(permissions) {
|
||||
permissionService.getPermissions(dataSource, username).success(function gotPermissions(permissions) {
|
||||
$scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions);
|
||||
})
|
||||
|
||||
// If permissions cannot be retrieved, use empty permissions
|
||||
.error(function permissionRetrievalFailed() {
|
||||
$scope.permissionFlags = new PermissionFlagSet();
|
||||
});
|
||||
|
||||
// Retrieve all connections for which we have ADMINISTER permission
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, [PermissionSet.ObjectPermissionType.ADMINISTER])
|
||||
.success(function connectionGroupReceived(rootGroup) {
|
||||
$scope.rootGroup = rootGroup;
|
||||
dataSourceService.apply(
|
||||
connectionGroupService.getConnectionGroupTree,
|
||||
[dataSource],
|
||||
ConnectionGroup.ROOT_IDENTIFIER,
|
||||
[PermissionSet.ObjectPermissionType.ADMINISTER]
|
||||
)
|
||||
.then(function connectionGroupReceived(rootGroups) {
|
||||
$scope.rootGroups = rootGroups;
|
||||
});
|
||||
|
||||
// Query the user's permissions for the current connection
|
||||
permissionService.getPermissions(authenticationService.getCurrentUsername())
|
||||
.success(function permissionsReceived(permissions) {
|
||||
|
||||
// Query the user's permissions for the current user
|
||||
permissionService.getPermissions(dataSource, currentUsername)
|
||||
.success(function permissionsReceived(permissions) {
|
||||
$scope.permissions = permissions;
|
||||
|
||||
// Check if the user is new or if the user has UPDATE permission
|
||||
$scope.canSaveUser =
|
||||
!username
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, username);
|
||||
|
||||
// Check if user is not new and the user has DELETE permission
|
||||
$scope.canDeleteUser =
|
||||
!!username && (
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE, username)
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -507,12 +714,17 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the user
|
||||
userService.saveUser($scope.user)
|
||||
.success(function savedUser() {
|
||||
// Save or create the user, depending on whether the user exists
|
||||
var saveUserPromise;
|
||||
if (userExists)
|
||||
saveUserPromise = userService.saveUser(dataSource, $scope.user);
|
||||
else
|
||||
saveUserPromise = userService.createUser(dataSource, $scope.user);
|
||||
|
||||
saveUserPromise.success(function savedUser() {
|
||||
|
||||
// Upon success, save any changed permissions
|
||||
permissionService.patchPermissions($scope.user.username, permissionsAdded, permissionsRemoved)
|
||||
permissionService.patchPermissions(dataSource, $scope.user.username, permissionsAdded, permissionsRemoved)
|
||||
.success(function patchedUserPermissions() {
|
||||
$location.path('/settings/users');
|
||||
})
|
||||
@@ -574,7 +786,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
|
||||
var deleteUserImmediately = function deleteUserImmediately() {
|
||||
|
||||
// Delete the user
|
||||
userService.deleteUser($scope.user)
|
||||
userService.deleteUser(dataSource, $scope.user)
|
||||
.success(function deletedUser() {
|
||||
$location.path('/settings/users');
|
||||
})
|
||||
|
65
guacamole/src/main/webapp/app/manage/styles/manage-user.css
Normal file
65
guacamole/src/main/webapp/app/manage/styles/manage-user.css
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.manage-user .username.header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.manage-user .username.header h2 {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href],
|
||||
.manage-user .page-tabs .page-list li.linked a[href] {
|
||||
padding-right: 2.5em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href]:before,
|
||||
.manage-user .page-tabs .page-list li.linked a[href]:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
width: 2.5em;
|
||||
background-size: 1.25em;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href]:before {
|
||||
background-image: url('images/plus.png');
|
||||
}
|
||||
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href]:hover,
|
||||
.manage-user .page-tabs .page-list li.unlinked a[href].current {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.manage-user .page-tabs .page-list li.linked a[href]:before {
|
||||
background-image: url('images/checkmark.png');
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2014 Glyptodon LLC.
|
||||
Copyright 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
|
||||
@@ -20,70 +20,84 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-->
|
||||
|
||||
<div class="view" ng-class="{loading: !isLoaded()}">
|
||||
<div class="manage-user view" ng-class="{loading: !isLoaded()}">
|
||||
|
||||
<!-- Main property editor -->
|
||||
<div class="header">
|
||||
<h2>{{'MANAGE_USER.SECTION_HEADER_EDIT_USER' | translate}}</h2>
|
||||
<!-- User header and data source tabs -->
|
||||
<div class="username header">
|
||||
<h2>{{user.username}}</h2>
|
||||
<guac-user-menu></guac-user-menu>
|
||||
</div>
|
||||
<div class="section">
|
||||
<table class="properties">
|
||||
<tr>
|
||||
<th>{{'MANAGE_USER.FIELD_HEADER_USERNAME' | translate}}</th>
|
||||
|
||||
<td>{{user.username}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{'MANAGE_USER.FIELD_HEADER_PASSWORD' | translate}}</th>
|
||||
|
||||
<td><input ng-model="user.password" type="password" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{'MANAGE_USER.FIELD_HEADER_PASSWORD_AGAIN' | translate}}</th>
|
||||
|
||||
<td><input ng-model="passwordMatch" type="password" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="page-tabs">
|
||||
<guac-page-list pages="accountPages"></guac-page-list>
|
||||
</div>
|
||||
|
||||
<!-- User attributes section -->
|
||||
<div class="attributes">
|
||||
<guac-form namespace="'USER_ATTRIBUTES'" content="attributes" model="user.attributes"></guac-form>
|
||||
<!-- Warn if user is read-only -->
|
||||
<div class="section" ng-show="isReadOnly()">
|
||||
<p class="notice read-only">{{'MANAGE_USER.INFO_READ_ONLY' | translate}}</p>
|
||||
</div>
|
||||
|
||||
<!-- System permissions section -->
|
||||
<h2 class="header">{{'MANAGE_USER.SECTION_HEADER_PERMISSIONS' | translate}}</h2>
|
||||
<div class="section">
|
||||
<table class="properties">
|
||||
<tr ng-repeat="systemPermissionType in systemPermissionTypes">
|
||||
<th>{{systemPermissionType.label | translate}}</th>
|
||||
<td><input type="checkbox" ng-model="permissionFlags.systemPermissions[systemPermissionType.value]"
|
||||
ng-change="systemPermissionChanged(systemPermissionType.value)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{'MANAGE_USER.FIELD_HEADER_CHANGE_OWN_PASSWORD' | translate}}</th>
|
||||
<td><input type="checkbox" ng-model="permissionFlags.userPermissions.UPDATE[user.username]"
|
||||
ng-change="userPermissionChanged('UPDATE', user.username)"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Sections applicable to non-read-only users -->
|
||||
<div ng-show="!isReadOnly()">
|
||||
|
||||
<!-- User password section -->
|
||||
<div class="section">
|
||||
<table class="properties">
|
||||
<tr>
|
||||
<th>{{'MANAGE_USER.FIELD_HEADER_PASSWORD' | translate}}</th>
|
||||
<td><input ng-model="user.password" type="password" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{'MANAGE_USER.FIELD_HEADER_PASSWORD_AGAIN' | translate}}</th>
|
||||
<td><input ng-model="passwordMatch" type="password" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- User attributes section -->
|
||||
<div class="attributes" ng-show="canChangeAttributes()">
|
||||
<guac-form namespace="'USER_ATTRIBUTES'" content="attributes" model="user.attributes"></guac-form>
|
||||
</div>
|
||||
|
||||
<!-- System permissions section -->
|
||||
<div class="system-permissions" ng-show="canChangePermissions()">
|
||||
<h2 class="header">{{'MANAGE_USER.SECTION_HEADER_PERMISSIONS' | translate}}</h2>
|
||||
<div class="section">
|
||||
<table class="properties">
|
||||
<tr ng-repeat="systemPermissionType in systemPermissionTypes"
|
||||
ng-show="canChangeSystemPermissions()">
|
||||
<th>{{systemPermissionType.label | translate}}</th>
|
||||
<td><input type="checkbox" ng-model="permissionFlags.systemPermissions[systemPermissionType.value]"
|
||||
ng-change="systemPermissionChanged(systemPermissionType.value)"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{'MANAGE_USER.FIELD_HEADER_CHANGE_OWN_PASSWORD' | translate}}</th>
|
||||
<td><input type="checkbox" ng-model="permissionFlags.userPermissions.UPDATE[user.username]"
|
||||
ng-change="userPermissionChanged('UPDATE', user.username)"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connection permissions section -->
|
||||
<div class="connection-permissions" ng-show="canChangePermissions()">
|
||||
<h2 class="header">{{'MANAGE_USER.SECTION_HEADER_CONNECTIONS' | translate}}</h2>
|
||||
<div class="section">
|
||||
<guac-group-list
|
||||
context="groupListContext"
|
||||
connection-groups="rootGroups"
|
||||
connection-template="'app/manage/templates/connectionPermission.html'"
|
||||
connection-group-template="'app/manage/templates/connectionGroupPermission.html'"
|
||||
page-size="20"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="header">{{'MANAGE_USER.SECTION_HEADER_CONNECTIONS' | translate}}</h2>
|
||||
<div class="section" ng-class="{loading: !rootGroup}">
|
||||
<guac-group-list
|
||||
context="groupListContext"
|
||||
connection-group="rootGroup"
|
||||
connection-template="'app/manage/templates/connectionPermission.html'"
|
||||
connection-group-template="'app/manage/templates/connectionGroupPermission.html'"
|
||||
page-size="20"/>
|
||||
</div>
|
||||
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons">
|
||||
<button ng-show="canSaveUser" ng-click="saveUser()">{{'MANAGE_USER.ACTION_SAVE' | translate}}</button>
|
||||
<button ng-show="canSaveUser()" ng-click="saveUser()">{{'MANAGE_USER.ACTION_SAVE' | translate}}</button>
|
||||
<button ng-click="cancel()">{{'MANAGE_USER.ACTION_CANCEL' | translate}}</button>
|
||||
<button ng-show="canDeleteUser" ng-click="deleteUser()" class="danger">{{'MANAGE_USER.ACTION_DELETE' | translate}}</button>
|
||||
<button ng-show="canDeleteUser()" ng-click="deleteUser()" class="danger">{{'MANAGE_USER.ACTION_DELETE' | translate}}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
56
guacamole/src/main/webapp/app/manage/types/ManageableUser.js
Normal file
56
guacamole/src/main/webapp/app/manage/types/ManageableUser.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A service for defining the ManageableUser class.
|
||||
*/
|
||||
angular.module('manage').factory('ManageableUser', [function defineManageableUser() {
|
||||
|
||||
/**
|
||||
* A pairing of an @link{User} with the identifier of its corresponding
|
||||
* data source.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object|ManageableUser} template
|
||||
*/
|
||||
var ManageableUser = function ManageableUser(template) {
|
||||
|
||||
/**
|
||||
* The unique identifier of the data source containing this user.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.dataSource = template.dataSource;
|
||||
|
||||
/**
|
||||
* The @link{User} object represented by this ManageableUser and
|
||||
* contained within the associated data source.
|
||||
*
|
||||
* @type User
|
||||
*/
|
||||
this.user = template.user;
|
||||
|
||||
};
|
||||
|
||||
return ManageableUser;
|
||||
|
||||
}]);
|
@@ -33,7 +33,7 @@ angular.module('navigation').directive('guacPageList', [function guacPageList()
|
||||
/**
|
||||
* The array of pages to display.
|
||||
*
|
||||
* @type Page[]
|
||||
* @type PageDefinition[]
|
||||
*/
|
||||
pages : '='
|
||||
|
||||
@@ -42,13 +42,119 @@ angular.module('navigation').directive('guacPageList', [function guacPageList()
|
||||
templateUrl: 'app/navigation/templates/guacPageList.html',
|
||||
controller: ['$scope', '$injector', function guacPageListController($scope, $injector) {
|
||||
|
||||
// Get required services
|
||||
// Required types
|
||||
var PageDefinition = $injector.get('PageDefinition');
|
||||
|
||||
// Required services
|
||||
var $location = $injector.get('$location');
|
||||
|
||||
/**
|
||||
* The URL of the currently-displayed page.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var currentURL = $location.url();
|
||||
|
||||
/**
|
||||
* The names associated with the current page, if the current page
|
||||
* is known. The value of this property corresponds to the value of
|
||||
* PageDefinition.name. Though PageDefinition.name may be a String,
|
||||
* this will always be an Array.
|
||||
*
|
||||
* @type String[]
|
||||
*/
|
||||
var currentPageName = [];
|
||||
|
||||
/**
|
||||
* Array of each level of the page list, where a level is defined
|
||||
* by a mapping of names (translation strings) to the
|
||||
* PageDefinitions corresponding to those names.
|
||||
*
|
||||
* @type Object.<String, PageDefinition>[]
|
||||
*/
|
||||
$scope.levels = [];
|
||||
|
||||
/**
|
||||
* Returns the names associated with the given page, in
|
||||
* hierarchical order. If the page is only associated with a single
|
||||
* name, and that name is not stored as an array, it will be still
|
||||
* be returned as an array containing a single item.
|
||||
*
|
||||
* @param {PageDefinition} page
|
||||
* The page to return the names of.
|
||||
*
|
||||
* @return {String[]}
|
||||
* An array of all names associated with the given page, in
|
||||
* hierarchical order.
|
||||
*/
|
||||
var getPageNames = function getPageNames(page) {
|
||||
|
||||
// If already an array, simply return the name
|
||||
if (angular.isArray(page.name))
|
||||
return page.name;
|
||||
|
||||
// Otherwise, transform into array
|
||||
return [page.name];
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the given PageDefinition to the overall set of pages
|
||||
* displayed by this guacPageList, automatically updating the
|
||||
* available levels ($scope.levels) and the contents of those
|
||||
* levels.
|
||||
*
|
||||
* @param {PageDefinition} page
|
||||
* The PageDefinition to add.
|
||||
*
|
||||
* @param {Number} weight
|
||||
* The sorting weight to use for the page if it does not
|
||||
* already have an associated weight.
|
||||
*/
|
||||
var addPage = function addPage(page, weight) {
|
||||
|
||||
// Pull all names for page
|
||||
var names = getPageNames(page);
|
||||
|
||||
// Copy the hierarchy of this page into the displayed levels
|
||||
// as far as is relevant for the currently-displayed page
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
|
||||
// Create current level, if it doesn't yet exist
|
||||
var pages = $scope.levels[i];
|
||||
if (!pages)
|
||||
pages = $scope.levels[i] = {};
|
||||
|
||||
// Get the name at the current level
|
||||
var name = names[i];
|
||||
|
||||
// Determine whether this page definition is part of the
|
||||
// hierarchy containing the current page
|
||||
var isCurrentPage = (currentPageName[i] === name);
|
||||
|
||||
// Store new page if it doesn't yet exist at this level
|
||||
if (!pages[name]) {
|
||||
pages[name] = new PageDefinition({
|
||||
name : name,
|
||||
url : isCurrentPage ? currentURL : page.url,
|
||||
className : page.className,
|
||||
weight : page.weight || weight
|
||||
});
|
||||
}
|
||||
|
||||
// If the name at this level no longer matches the
|
||||
// hierarchy of the current page, do not go any deeper
|
||||
if (currentPageName[i] !== name)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to the given page.
|
||||
*
|
||||
* @param {Page} page
|
||||
* @param {PageDefinition} page
|
||||
* The page to navigate to.
|
||||
*/
|
||||
$scope.navigateToPage = function navigateToPage(page) {
|
||||
@@ -58,16 +164,85 @@ angular.module('navigation').directive('guacPageList', [function guacPageList()
|
||||
/**
|
||||
* Tests whether the given page is the page currently being viewed.
|
||||
*
|
||||
* @param {Page} page
|
||||
* @param {PageDefinition} page
|
||||
* The page to test.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the given page is the current page, false otherwise.
|
||||
*/
|
||||
$scope.isCurrentPage = function isCurrentPage(page) {
|
||||
return $location.url() === page.url;
|
||||
return currentURL === page.url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given an arbitrary map of PageDefinitions, returns an array of
|
||||
* those PageDefinitions, sorted by weight.
|
||||
*
|
||||
* @param {Object.<*, PageDefinition>} level
|
||||
* A map of PageDefinitions with arbitrary keys. The value of
|
||||
* each key is ignored.
|
||||
*
|
||||
* @returns {PageDefinition[]}
|
||||
* An array of all PageDefinitions in the given map, sorted by
|
||||
* weight.
|
||||
*/
|
||||
$scope.getPages = function getPages(level) {
|
||||
|
||||
var pages = [];
|
||||
|
||||
// Convert contents of level to a flat array of pages
|
||||
angular.forEach(level, function addPageFromLevel(page) {
|
||||
pages.push(page);
|
||||
});
|
||||
|
||||
// Sort page array by weight
|
||||
pages.sort(function comparePages(a, b) {
|
||||
return a.weight - b.weight;
|
||||
});
|
||||
|
||||
return pages;
|
||||
|
||||
};
|
||||
|
||||
// Update page levels whenever pages changes
|
||||
$scope.$watch('pages', function setPages(pages) {
|
||||
|
||||
// Determine current page name
|
||||
currentPageName = [];
|
||||
angular.forEach(pages, function findCurrentPageName(page) {
|
||||
|
||||
// If page is current page, store its names
|
||||
if ($scope.isCurrentPage(page))
|
||||
currentPageName = getPageNames(page);
|
||||
|
||||
});
|
||||
|
||||
// Reset contents of levels
|
||||
$scope.levels = [];
|
||||
|
||||
// Add all page definitions
|
||||
angular.forEach(pages, addPage);
|
||||
|
||||
// Filter to only relevant levels
|
||||
$scope.levels = $scope.levels.filter(function isRelevant(level) {
|
||||
|
||||
// Determine relevancy by counting the number of pages
|
||||
var pageCount = 0;
|
||||
for (var name in level) {
|
||||
|
||||
// Level is relevant if it has two or more pages
|
||||
if (++pageCount === 2)
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// Otherwise, the level is not relevant
|
||||
return false;
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}] // end controller
|
||||
|
||||
};
|
||||
|
@@ -23,4 +23,8 @@
|
||||
/**
|
||||
* Module for generating and implementing user navigation options.
|
||||
*/
|
||||
angular.module('navigation', ['notification', 'rest']);
|
||||
angular.module('navigation', [
|
||||
'auth',
|
||||
'notification',
|
||||
'rest'
|
||||
]);
|
||||
|
@@ -27,91 +27,104 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
function userPageService($injector) {
|
||||
|
||||
// Get required types
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var PermissionSet = $injector.get('PermissionSet');
|
||||
var ClientIdentifier = $injector.get('ClientIdentifier');
|
||||
var ConnectionGroup = $injector.get('ConnectionGroup');
|
||||
var PageDefinition = $injector.get('PageDefinition');
|
||||
var PermissionSet = $injector.get('PermissionSet');
|
||||
|
||||
// Get required services
|
||||
var $q = $injector.get('$q');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get("connectionGroupService");
|
||||
var permissionService = $injector.get("permissionService");
|
||||
var $q = $injector.get('$q');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var dataSourceService = $injector.get('dataSourceService');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
var translationStringService = $injector.get('translationStringService');
|
||||
|
||||
var service = {};
|
||||
|
||||
/**
|
||||
* Construct a new Page object with the given name and url.
|
||||
* @constructor
|
||||
*
|
||||
* @param {String} name
|
||||
* The i18n key for the name of the page.
|
||||
*
|
||||
* @param {String} url
|
||||
* The url to the page.
|
||||
*
|
||||
* @returns {PageDefinition}
|
||||
* The newly created PageDefinition object.
|
||||
*/
|
||||
var Page = function Page(name, url) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
};
|
||||
|
||||
/**
|
||||
* The home page to assign to a user if they can navigate to more than one
|
||||
* page.
|
||||
*
|
||||
* @type Page
|
||||
* @type PageDefinition
|
||||
*/
|
||||
var SYSTEM_HOME_PAGE = new Page(
|
||||
'USER_MENU.ACTION_NAVIGATE_HOME',
|
||||
'/'
|
||||
);
|
||||
var SYSTEM_HOME_PAGE = new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_NAVIGATE_HOME',
|
||||
url : '/'
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns an appropriate home page for the current user.
|
||||
*
|
||||
* @param {ConnectionGroup} rootGroup
|
||||
* The root of the connection group tree for the current user.
|
||||
* @param {Object.<String, ConnectionGroup>} rootGroups
|
||||
* A map of all root connection groups visible to the current user,
|
||||
* where each key is the identifier of the corresponding data source.
|
||||
*
|
||||
* @returns {Page}
|
||||
* @returns {PageDefinition}
|
||||
* The user's home page.
|
||||
*/
|
||||
var generateHomePage = function generateHomePage(rootGroup) {
|
||||
var generateHomePage = function generateHomePage(rootGroups) {
|
||||
|
||||
// Get children
|
||||
var connections = rootGroup.childConnections || [];
|
||||
var connectionGroups = rootGroup.childConnectionGroups || [];
|
||||
var homePage = null;
|
||||
|
||||
// Use main connection list screen as home if multiple connections
|
||||
// are available
|
||||
if (connections.length + connectionGroups.length === 1) {
|
||||
// Determine whether a connection or balancing group should serve as
|
||||
// the home page
|
||||
for (var dataSource in rootGroups) {
|
||||
|
||||
var connection = connections[0];
|
||||
var connectionGroup = connectionGroups[0];
|
||||
// Get corresponding root group
|
||||
var rootGroup = rootGroups[dataSource];
|
||||
|
||||
// Get children
|
||||
var connections = rootGroup.childConnections || [];
|
||||
var connectionGroups = rootGroup.childConnectionGroups || [];
|
||||
|
||||
// If exactly one connection or balancing group is available, use
|
||||
// that as the home page
|
||||
if (homePage === null && connections.length + connectionGroups.length === 1) {
|
||||
|
||||
var connection = connections[0];
|
||||
var connectionGroup = connectionGroups[0];
|
||||
|
||||
// Only one connection present, use as home page
|
||||
if (connection) {
|
||||
homePage = new PageDefinition({
|
||||
name : connection.name,
|
||||
url : '/client/' + ClientIdentifier.toString({
|
||||
dataSource : dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION,
|
||||
id : connection.identifier
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Only one balancing group present, use as home page
|
||||
if (connectionGroup
|
||||
&& connectionGroup.type === ConnectionGroup.Type.BALANCING
|
||||
&& _.isEmpty(connectionGroup.childConnections)
|
||||
&& _.isEmpty(connectionGroup.childConnectionGroups)) {
|
||||
homePage = new PageDefinition({
|
||||
name : connectionGroup.name,
|
||||
url : '/client/' + ClientIdentifier.toString({
|
||||
dataSource : dataSource,
|
||||
type : ClientIdentifier.Types.CONNECTION_GROUP,
|
||||
id : connectionGroup.identifier
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Only one connection present, use as home page
|
||||
if (connection) {
|
||||
return new Page(
|
||||
connection.name,
|
||||
'/client/c/' + connection.identifier
|
||||
);
|
||||
}
|
||||
|
||||
// Only one connection present, use as home page
|
||||
if (connectionGroup
|
||||
&& connectionGroup.type === ConnectionGroup.Type.BALANCING
|
||||
&& _.isEmpty(connectionGroup.childConnections)
|
||||
&& _.isEmpty(connectionGroup.childConnectionGroups)) {
|
||||
return new Page(
|
||||
connectionGroup.name,
|
||||
'/client/g/' + connectionGroup.identifier
|
||||
);
|
||||
// Otherwise, a connection or balancing group cannot serve as the
|
||||
// home page
|
||||
else {
|
||||
homePage = null;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
} // end for each data source
|
||||
|
||||
// Resolve promise with default home page
|
||||
return SYSTEM_HOME_PAGE;
|
||||
// Use default home page if no other is available
|
||||
return homePage || SYSTEM_HOME_PAGE;
|
||||
|
||||
};
|
||||
|
||||
@@ -126,10 +139,14 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
// Resolve promise using home page derived from root connection group
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER)
|
||||
.success(function rootConnectionGroupRetrieved(rootGroup) {
|
||||
deferred.resolve(generateHomePage(rootGroup));
|
||||
// Resolve promise using home page derived from root connection groups
|
||||
dataSourceService.apply(
|
||||
connectionGroupService.getConnectionGroupTree,
|
||||
authenticationService.getAvailableDataSources(),
|
||||
ConnectionGroup.ROOT_IDENTIFIER
|
||||
)
|
||||
.then(function rootConnectionGroupsRetrieved(rootGroups) {
|
||||
deferred.resolve(generateHomePage(rootGroups));
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
@@ -140,94 +157,115 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
* Returns all settings pages that the current user can visit. This can
|
||||
* include any of the various manage pages.
|
||||
*
|
||||
* @param {PermissionSet} permissions
|
||||
* The permissions for the current user.
|
||||
* @param {Object.<String, PermissionSet>} permissionSets
|
||||
* A map of all permissions granted to the current user, where each
|
||||
* key is the identifier of the corresponding data source.
|
||||
*
|
||||
* @returns {Page[]}
|
||||
* An array of all settings pages that the current user can visit.
|
||||
*/
|
||||
var generateSettingsPages = function generateSettingsPages(permissions) {
|
||||
var generateSettingsPages = function generateSettingsPages(permissionSets) {
|
||||
|
||||
var pages = [];
|
||||
|
||||
permissions = angular.copy(permissions);
|
||||
var canManageUsers = [];
|
||||
var canManageConnections = [];
|
||||
var canManageSessions = [];
|
||||
|
||||
// Ignore permission to update root group
|
||||
PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER);
|
||||
// Inspect the contents of each provided permission set
|
||||
angular.forEach(permissionSets, function inspectPermissions(permissions, dataSource) {
|
||||
|
||||
// Ignore permission to update self
|
||||
PermissionSet.removeUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, authenticationService.getCurrentUsername());
|
||||
permissions = angular.copy(permissions);
|
||||
|
||||
// Determine whether the current user needs access to the user management UI
|
||||
var canManageUsers =
|
||||
// Ignore permission to update root group
|
||||
PermissionSet.removeConnectionGroupPermission(permissions,
|
||||
PermissionSet.ObjectPermissionType.UPDATE,
|
||||
ConnectionGroup.ROOT_IDENTIFIER);
|
||||
|
||||
// System permissions
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER)
|
||||
// Ignore permission to update self
|
||||
PermissionSet.removeUserPermission(permissions,
|
||||
PermissionSet.ObjectPermissionType.UPDATE,
|
||||
authenticationService.getCurrentUsername());
|
||||
|
||||
// Permission to update users
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
// Determine whether the current user needs access to the user management UI
|
||||
if (
|
||||
// System permissions
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER)
|
||||
|
||||
// Permission to delete users
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
|
||||
// Permission to update users
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|
||||
// Permission to administer users
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER);
|
||||
// Permission to delete users
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
|
||||
|
||||
// Determine whether the current user needs access to the connection management UI
|
||||
var canManageConnections =
|
||||
// Permission to administer users
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
|
||||
)
|
||||
canManageUsers.push(dataSource);
|
||||
|
||||
// System permissions
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP)
|
||||
// Determine whether the current user needs access to the connection management UI
|
||||
if (
|
||||
// System permissions
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP)
|
||||
|
||||
// Permission to update connections or connection groups
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
// Permission to update connections or connection groups
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|
||||
// Permission to delete connections or connection groups
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
|
||||
// Permission to delete connections or connection groups
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
|
||||
|
||||
// Permission to administer connections or connection groups
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER);
|
||||
// Permission to administer connections or connection groups
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
|
||||
)
|
||||
canManageConnections.push(dataSource);
|
||||
|
||||
var canManageSessions =
|
||||
// Determine whether the current user needs access to the session management UI
|
||||
if (
|
||||
// A user must be a system administrator to manage sessions
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
)
|
||||
canManageSessions.push(dataSource);
|
||||
|
||||
// A user must be a system administrator to manage sessions
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER);
|
||||
});
|
||||
|
||||
// If user can manage sessions, add link to sessions management page
|
||||
if (canManageSessions) {
|
||||
pages.push(new Page(
|
||||
'USER_MENU.ACTION_MANAGE_SESSIONS',
|
||||
'/settings/sessions'
|
||||
));
|
||||
if (canManageSessions.length) {
|
||||
pages.push(new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_MANAGE_SESSIONS',
|
||||
url : '/settings/sessions'
|
||||
}));
|
||||
}
|
||||
|
||||
// If user can manage users, add link to user management page
|
||||
if (canManageUsers) {
|
||||
pages.push(new Page(
|
||||
'USER_MENU.ACTION_MANAGE_USERS',
|
||||
'/settings/users'
|
||||
));
|
||||
if (canManageUsers.length) {
|
||||
pages.push(new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_MANAGE_USERS',
|
||||
url : '/settings/users'
|
||||
}));
|
||||
}
|
||||
|
||||
// If user can manage connections, add link to connections management page
|
||||
if (canManageConnections) {
|
||||
pages.push(new Page(
|
||||
'USER_MENU.ACTION_MANAGE_CONNECTIONS',
|
||||
'/settings/connections'
|
||||
));
|
||||
}
|
||||
// If user can manage connections, add links for connection management pages
|
||||
angular.forEach(canManageConnections, function addConnectionManagementLink(dataSource) {
|
||||
pages.push(new PageDefinition({
|
||||
name : [
|
||||
'USER_MENU.ACTION_MANAGE_CONNECTIONS',
|
||||
translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME'
|
||||
],
|
||||
url : '/settings/' + encodeURIComponent(dataSource) + '/connections'
|
||||
}));
|
||||
});
|
||||
|
||||
// Add link to user preferences (always accessible)
|
||||
pages.push(new Page(
|
||||
'USER_MENU.ACTION_MANAGE_PREFERENCES',
|
||||
'/settings/preferences'
|
||||
));
|
||||
pages.push(new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_MANAGE_PREFERENCES',
|
||||
url : '/settings/preferences'
|
||||
}));
|
||||
|
||||
return pages;
|
||||
};
|
||||
@@ -245,10 +283,15 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
// Retrieve current permissions, resolving main pages if possible
|
||||
// Retrieve current permissions
|
||||
dataSourceService.apply(
|
||||
permissionService.getPermissions,
|
||||
authenticationService.getAvailableDataSources(),
|
||||
authenticationService.getCurrentUsername()
|
||||
)
|
||||
|
||||
// Resolve promise using settings pages derived from permissions
|
||||
permissionService.getPermissions(authenticationService.getCurrentUsername())
|
||||
.success(function permissionsRetrieved(permissions) {
|
||||
.then(function permissionsRetrieved(permissions) {
|
||||
deferred.resolve(generateSettingsPages(permissions));
|
||||
});
|
||||
|
||||
@@ -261,21 +304,23 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
* include the home page, manage pages, etc. In the case that there are no
|
||||
* applicable pages of this sort, it may return a client page.
|
||||
*
|
||||
* @param {ConnectionGroup} rootGroup
|
||||
* The root of the connection group tree for the current user.
|
||||
* @param {Object.<String, ConnectionGroup>} rootGroups
|
||||
* A map of all root connection groups visible to the current user,
|
||||
* where each key is the identifier of the corresponding data source.
|
||||
*
|
||||
* @param {PermissionSet} permissions
|
||||
* The permissions for the current user.
|
||||
* @param {Object.<String, PermissionSet>} permissions
|
||||
* A map of all permissions granted to the current user, where each
|
||||
* key is the identifier of the corresponding data source.
|
||||
*
|
||||
* @returns {Page[]}
|
||||
* An array of all main pages that the current user can visit.
|
||||
*/
|
||||
var generateMainPages = function generateMainPages(rootGroup, permissions) {
|
||||
var generateMainPages = function generateMainPages(rootGroups, permissions) {
|
||||
|
||||
var pages = [];
|
||||
|
||||
// Get home page and settings pages
|
||||
var homePage = generateHomePage(rootGroup);
|
||||
var homePage = generateHomePage(rootGroups);
|
||||
var settingsPages = generateSettingsPages(permissions);
|
||||
|
||||
// Only include the home page in the list of main pages if the user
|
||||
@@ -285,10 +330,10 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
|
||||
// Add generic link to the first-available settings page
|
||||
if (settingsPages.length) {
|
||||
pages.push(new Page(
|
||||
'USER_MENU.ACTION_MANAGE_SETTINGS',
|
||||
settingsPages[0].url
|
||||
));
|
||||
pages.push(new PageDefinition({
|
||||
name : 'USER_MENU.ACTION_MANAGE_SETTINGS',
|
||||
url : settingsPages[0].url
|
||||
}));
|
||||
}
|
||||
|
||||
return pages;
|
||||
@@ -308,7 +353,7 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
var rootGroup = null;
|
||||
var rootGroups = null;
|
||||
var permissions = null;
|
||||
|
||||
/**
|
||||
@@ -316,20 +361,30 @@ angular.module('navigation').factory('userPageService', ['$injector',
|
||||
* insufficient data is available, this function does nothing.
|
||||
*/
|
||||
var resolveMainPages = function resolveMainPages() {
|
||||
if (rootGroup && permissions)
|
||||
deferred.resolve(generateMainPages(rootGroup, permissions));
|
||||
if (rootGroups && permissions)
|
||||
deferred.resolve(generateMainPages(rootGroups, permissions));
|
||||
};
|
||||
|
||||
// Retrieve root group, resolving main pages if possible
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER)
|
||||
.success(function rootConnectionGroupRetrieved(retrievedRootGroup) {
|
||||
rootGroup = retrievedRootGroup;
|
||||
dataSourceService.apply(
|
||||
connectionGroupService.getConnectionGroupTree,
|
||||
authenticationService.getAvailableDataSources(),
|
||||
ConnectionGroup.ROOT_IDENTIFIER
|
||||
)
|
||||
.then(function rootConnectionGroupsRetrieved(retrievedRootGroups) {
|
||||
rootGroups = retrievedRootGroups;
|
||||
resolveMainPages();
|
||||
});
|
||||
|
||||
// Retrieve current permissions, resolving main pages if possible
|
||||
permissionService.getPermissions(authenticationService.getCurrentUsername())
|
||||
.success(function permissionsRetrieved(retrievedPermissions) {
|
||||
// Retrieve current permissions
|
||||
dataSourceService.apply(
|
||||
permissionService.getPermissions,
|
||||
authenticationService.getAvailableDataSources(),
|
||||
authenticationService.getCurrentUsername()
|
||||
)
|
||||
|
||||
// Resolving main pages if possible
|
||||
.then(function permissionsRetrieved(retrievedPermissions) {
|
||||
permissions = retrievedPermissions;
|
||||
resolveMainPages();
|
||||
});
|
||||
|
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.page-tabs .page-list ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: rgba(0, 0, 0, 0.0125);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.page-tabs .page-list ul + ul {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.page-tabs .page-list li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.page-tabs .page-list li a[href] {
|
||||
display: block;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
padding: 0.75em 1em;
|
||||
}
|
||||
|
||||
.page-tabs .page-list li a[href]:visited {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.page-tabs .page-list li a[href]:hover {
|
||||
background-color: #CDA;
|
||||
}
|
||||
|
||||
.page-tabs .page-list li a[href].current,
|
||||
.page-tabs .page-list li a[href].current:hover {
|
||||
background: rgba(0,0,0,0.3);
|
||||
cursor: default;
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
<ul class="page-list">
|
||||
<div class="page-list" ng-show="levels.length">
|
||||
<!--
|
||||
Copyright (C) 2015 Glyptodon LLC
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
-->
|
||||
|
||||
<!-- Navigation links -->
|
||||
<li ng-repeat="page in pages">
|
||||
<a class="home" ng-click="navigateToPage(page)"
|
||||
ng-class="{current: isCurrentPage(page)}" href="#{{page.url}}">
|
||||
{{page.name | translate}}
|
||||
</a>
|
||||
</li>
|
||||
<ul class="page-list-level" ng-repeat="level in levels track by $index">
|
||||
<li ng-repeat="page in getPages(level)" class="{{page.className}}">
|
||||
<a class="home" ng-click="navigateToPage(page)"
|
||||
ng-class="{current: isCurrentPage(page)}" href="#{{page.url}}">
|
||||
{{page.name | translate}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the ClientIdentifier class definition.
|
||||
*/
|
||||
angular.module('client').factory('ClientIdentifier', ['$injector',
|
||||
function defineClientIdentifier($injector) {
|
||||
|
||||
// Required services
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var $window = $injector.get('$window');
|
||||
|
||||
/**
|
||||
* Object which uniquely identifies a particular connection or connection
|
||||
* group within Guacamole. This object can be converted to/from a string to
|
||||
* generate a guaranteed-unique, deterministic identifier for client URLs.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ClientIdentifier|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ClientIdentifier.
|
||||
*/
|
||||
var ClientIdentifier = function ClientIdentifier(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* The identifier of the data source associated with the object to
|
||||
* which the client will connect. This identifier will be the
|
||||
* identifier of an AuthenticationProvider within the Guacamole web
|
||||
* application.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.dataSource = template.dataSource;
|
||||
|
||||
/**
|
||||
* The type of object to which the client will connect. Possible values
|
||||
* are defined within ClientIdentifier.Types.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.type = template.type;
|
||||
|
||||
/**
|
||||
* The unique identifier of the object to which the client will
|
||||
* connect.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.id = template.id;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* All possible ClientIdentifier types.
|
||||
*
|
||||
* @type Object.<String, String>
|
||||
*/
|
||||
ClientIdentifier.Types = {
|
||||
|
||||
/**
|
||||
* The type string for a Guacamole connection.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
CONNECTION : 'c',
|
||||
|
||||
/**
|
||||
* The type string for a Guacamole connection group.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
CONNECTION_GROUP : 'g'
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the given ClientIdentifier or ClientIdentifier-like object to
|
||||
* a String representation. Any object having the same properties as
|
||||
* ClientIdentifier may be used, but only those properties will be taken
|
||||
* into account when producing the resulting String.
|
||||
*
|
||||
* @param {ClientIdentifier|Object} id
|
||||
* The ClientIdentifier or ClientIdentifier-like object to convert to
|
||||
* a String representation.
|
||||
*
|
||||
* @returns {String}
|
||||
* A deterministic String representation of the given ClientIdentifier
|
||||
* or ClientIdentifier-like object.
|
||||
*/
|
||||
ClientIdentifier.toString = function toString(id) {
|
||||
return $window.btoa([
|
||||
id.id,
|
||||
id.type,
|
||||
id.dataSource
|
||||
].join('\0'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the given String into the corresponding ClientIdentifier. If
|
||||
* the provided String is not a valid identifier, it will be interpreted
|
||||
* as the identifier of a connection within the data source that
|
||||
* authenticated the current user.
|
||||
*
|
||||
* @param {String} str
|
||||
* The String to convert to a ClientIdentifier.
|
||||
*
|
||||
* @returns {ClientIdentifier}
|
||||
* The ClientIdentifier represented by the given String.
|
||||
*/
|
||||
ClientIdentifier.fromString = function fromString(str) {
|
||||
|
||||
try {
|
||||
var values = $window.atob(str).split('\0');
|
||||
return new ClientIdentifier({
|
||||
id : values[0],
|
||||
type : values[1],
|
||||
dataSource : values[2]
|
||||
});
|
||||
}
|
||||
|
||||
// If the provided string is invalid, transform into a reasonable guess
|
||||
catch (e) {
|
||||
return new ClientIdentifier({
|
||||
id : str,
|
||||
type : ClientIdentifier.Types.CONNECTION,
|
||||
dataSource : authenticationService.getDataSource() || 'default'
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return ClientIdentifier;
|
||||
|
||||
}]);
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the PageDefinition class definition.
|
||||
*/
|
||||
angular.module('navigation').factory('PageDefinition', [function definePageDefinition() {
|
||||
|
||||
/**
|
||||
* Creates a new PageDefinition object which pairs the URL of a page with
|
||||
* an arbitrary, human-readable name.
|
||||
*
|
||||
* @constructor
|
||||
* @param {PageDefinition|Object} template
|
||||
* The object whose properties should be copied within the new
|
||||
* PageDefinition.
|
||||
*/
|
||||
var PageDefinition = function PageDefinition(template) {
|
||||
|
||||
/**
|
||||
* The the name of the page, which should be a translation table key.
|
||||
* Alternatively, this may also be a list of names, where the final
|
||||
* name represents the page and earlier names represent categorization.
|
||||
* Those categorical names may be rendered hierarchically as a system
|
||||
* of menus, tabs, etc.
|
||||
*
|
||||
* @type String|String[]
|
||||
*/
|
||||
this.name = template.name;
|
||||
|
||||
/**
|
||||
* The URL of the page.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.url = template.url;
|
||||
|
||||
/**
|
||||
* The CSS class name to associate with this page, if any. This will be
|
||||
* an empty string by default.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.className = template.className || '';
|
||||
|
||||
/**
|
||||
* A numeric value denoting the relative sort order when compared to
|
||||
* other sibling PageDefinitions. If unspecified, sort order is
|
||||
* determined by the system using the PageDefinition.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.weight = template.weight;
|
||||
|
||||
};
|
||||
|
||||
return PageDefinition;
|
||||
|
||||
}]);
|
@@ -23,8 +23,13 @@
|
||||
/**
|
||||
* Service for operating on active connections via the REST API.
|
||||
*/
|
||||
angular.module('rest').factory('activeConnectionService', ['$http', 'authenticationService',
|
||||
function activeConnectionService($http, authenticationService) {
|
||||
angular.module('rest').factory('activeConnectionService', ['$injector',
|
||||
function activeConnectionService($injector) {
|
||||
|
||||
// Required services
|
||||
var $http = $injector.get('$http');
|
||||
var $q = $injector.get('$q');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
|
||||
var service = {};
|
||||
|
||||
@@ -39,13 +44,12 @@ angular.module('rest').factory('activeConnectionService', ['$http', 'authenticat
|
||||
* result. If null, no filtering will be performed. Valid values are
|
||||
* listed within PermissionSet.ObjectType.
|
||||
*
|
||||
|
||||
* @returns {Promise.<Object.<String, ActiveConnection>>}
|
||||
* A promise which will resolve with a map of @link{ActiveConnection}
|
||||
* objects, where each key is the identifier of the corresponding
|
||||
* active connection.
|
||||
*/
|
||||
service.getActiveConnections = function getActiveConnections(permissionTypes) {
|
||||
service.getActiveConnections = function getActiveConnections(dataSource, permissionTypes) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -59,12 +63,74 @@ angular.module('rest').factory('activeConnectionService', ['$http', 'authenticat
|
||||
// Retrieve tunnels
|
||||
return $http({
|
||||
method : 'GET',
|
||||
url : 'api/activeConnections',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/activeConnections',
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a promise which resolves with all active connections accessible
|
||||
* by the current user, as a map of @link{ActiveConnection} maps, as would
|
||||
* be returned by getActiveConnections(), grouped by the identifier of
|
||||
* their corresponding data source. All given data sources are queried. If
|
||||
* an error occurs while retrieving any ActiveConnection map, the promise
|
||||
* will be rejected.
|
||||
*
|
||||
* @param {String[]} dataSources
|
||||
* The unique identifier of the data sources containing the active
|
||||
* connections to be retrieved. These identifiers correspond to
|
||||
* AuthenticationProviders within the Guacamole web application.
|
||||
*
|
||||
* @param {String[]} [permissionTypes]
|
||||
* The set of permissions to filter with. A user must have one or more
|
||||
* of these permissions for an active connection to appear in the
|
||||
* result. If null, no filtering will be performed. Valid values are
|
||||
* listed within PermissionSet.ObjectType.
|
||||
*
|
||||
* @returns {Promise.<Object.<String, Object.<String, ActiveConnection>>>}
|
||||
* A promise which resolves with all active connections available to
|
||||
* the current user, as a map of ActiveConnection maps, as would be
|
||||
* returned by getActiveConnections(), grouped by the identifier of
|
||||
* their corresponding data source.
|
||||
*/
|
||||
service.getAllActiveConnections = function getAllActiveConnections(dataSources, permissionTypes) {
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
var activeConnectionRequests = [];
|
||||
var activeConnectionMaps = {};
|
||||
|
||||
// Retrieve all active connections from all data sources
|
||||
angular.forEach(dataSources, function retrieveActiveConnections(dataSource) {
|
||||
activeConnectionRequests.push(
|
||||
service.getActiveConnections(dataSource, permissionTypes)
|
||||
.success(function activeConnectionsRetrieved(activeConnections) {
|
||||
activeConnectionMaps[dataSource] = activeConnections;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Resolve when all requests are completed
|
||||
$q.all(activeConnectionRequests)
|
||||
.then(
|
||||
|
||||
// All requests completed successfully
|
||||
function allActiveConnectionsRetrieved() {
|
||||
deferred.resolve(userArrays);
|
||||
},
|
||||
|
||||
// At least one request failed
|
||||
function activeConnectionRetrievalFailed(e) {
|
||||
deferred.reject(e);
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
return deferred.promise;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a request to the REST API to delete the active connections having
|
||||
* the given identifiers, effectively disconnecting them, returning a
|
||||
@@ -77,7 +143,7 @@ angular.module('rest').factory('activeConnectionService', ['$http', 'authenticat
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* delete operation is successful.
|
||||
*/
|
||||
service.deleteActiveConnections = function deleteActiveConnections(identifiers) {
|
||||
service.deleteActiveConnections = function deleteActiveConnections(dataSource, identifiers) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -96,7 +162,7 @@ angular.module('rest').factory('activeConnectionService', ['$http', 'authenticat
|
||||
// Perform active connection deletion via PATCH
|
||||
return $http({
|
||||
method : 'PATCH',
|
||||
url : 'api/activeConnections',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/activeConnections',
|
||||
params : httpParameters,
|
||||
data : activeConnectionPatch
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Glyptodon LLC
|
||||
* 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
|
||||
@@ -28,6 +28,7 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
|
||||
|
||||
// Required services
|
||||
var $http = $injector.get('$http');
|
||||
var $q = $injector.get('$q');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var cacheService = $injector.get('cacheService');
|
||||
|
||||
@@ -57,7 +58,7 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
|
||||
* A promise which will resolve with a @link{ConnectionGroup} upon
|
||||
* success.
|
||||
*/
|
||||
service.getConnectionGroupTree = function getConnectionGroupTree(connectionGroupID, permissionTypes) {
|
||||
service.getConnectionGroupTree = function getConnectionGroupTree(dataSource, connectionGroupID, permissionTypes) {
|
||||
|
||||
// Use the root connection group ID if no ID is passed in
|
||||
connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER;
|
||||
@@ -75,7 +76,7 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.connections,
|
||||
method : 'GET',
|
||||
url : 'api/connectionGroups/' + encodeURIComponent(connectionGroupID) + '/tree',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroupID) + '/tree',
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -94,7 +95,7 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
|
||||
* A promise which will resolve with a @link{ConnectionGroup} upon
|
||||
* success.
|
||||
*/
|
||||
service.getConnectionGroup = function getConnectionGroup(connectionGroupID) {
|
||||
service.getConnectionGroup = function getConnectionGroup(dataSource, connectionGroupID) {
|
||||
|
||||
// Use the root connection group ID if no ID is passed in
|
||||
connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER;
|
||||
@@ -108,7 +109,7 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.connections,
|
||||
method : 'GET',
|
||||
url : 'api/connectionGroups/' + encodeURIComponent(connectionGroupID),
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroupID),
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -127,7 +128,7 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* save operation is successful.
|
||||
*/
|
||||
service.saveConnectionGroup = function saveConnectionGroup(connectionGroup) {
|
||||
service.saveConnectionGroup = function saveConnectionGroup(dataSource, connectionGroup) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -138,7 +139,7 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
|
||||
if (!connectionGroup.identifier) {
|
||||
return $http({
|
||||
method : 'POST',
|
||||
url : 'api/connectionGroups',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connectionGroups',
|
||||
params : httpParameters,
|
||||
data : connectionGroup
|
||||
})
|
||||
@@ -154,7 +155,7 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
|
||||
else {
|
||||
return $http({
|
||||
method : 'PUT',
|
||||
url : 'api/connectionGroups/' + encodeURIComponent(connectionGroup.identifier),
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroup.identifier),
|
||||
params : httpParameters,
|
||||
data : connectionGroup
|
||||
})
|
||||
@@ -177,7 +178,7 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* delete operation is successful.
|
||||
*/
|
||||
service.deleteConnectionGroup = function deleteConnectionGroup(connectionGroup) {
|
||||
service.deleteConnectionGroup = function deleteConnectionGroup(dataSource, connectionGroup) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -187,7 +188,7 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
|
||||
// Delete connection group
|
||||
return $http({
|
||||
method : 'DELETE',
|
||||
url : 'api/connectionGroups/' + encodeURIComponent(connectionGroup.identifier),
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroup.identifier),
|
||||
params : httpParameters
|
||||
})
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Glyptodon LLC
|
||||
* 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
|
||||
@@ -48,7 +48,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
* // Do something with the connection
|
||||
* });
|
||||
*/
|
||||
service.getConnection = function getConnection(id) {
|
||||
service.getConnection = function getConnection(dataSource, id) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -59,7 +59,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.connections,
|
||||
method : 'GET',
|
||||
url : 'api/connections/' + encodeURIComponent(id),
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id),
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
* A promise which will resolve with an array of
|
||||
* @link{ConnectionHistoryEntry} objects upon success.
|
||||
*/
|
||||
service.getConnectionHistory = function getConnectionHistory(id) {
|
||||
service.getConnectionHistory = function getConnectionHistory(dataSource, id) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -87,7 +87,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
// Retrieve connection history
|
||||
return $http({
|
||||
method : 'GET',
|
||||
url : 'api/connections/' + encodeURIComponent(id) + '/history',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) + '/history',
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -105,7 +105,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
* A promise which will resolve with an map of parameter name/value
|
||||
* pairs upon success.
|
||||
*/
|
||||
service.getConnectionParameters = function getConnectionParameters(id) {
|
||||
service.getConnectionParameters = function getConnectionParameters(dataSource, id) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -116,7 +116,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.connections,
|
||||
method : 'GET',
|
||||
url : 'api/connections/' + encodeURIComponent(id) + '/parameters',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) + '/parameters',
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -135,7 +135,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* save operation is successful.
|
||||
*/
|
||||
service.saveConnection = function saveConnection(connection) {
|
||||
service.saveConnection = function saveConnection(dataSource, connection) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -146,7 +146,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
if (!connection.identifier) {
|
||||
return $http({
|
||||
method : 'POST',
|
||||
url : 'api/connections',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connections',
|
||||
params : httpParameters,
|
||||
data : connection
|
||||
})
|
||||
@@ -162,7 +162,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
else {
|
||||
return $http({
|
||||
method : 'PUT',
|
||||
url : 'api/connections/' + encodeURIComponent(connection.identifier),
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(connection.identifier),
|
||||
params : httpParameters,
|
||||
data : connection
|
||||
})
|
||||
@@ -185,7 +185,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* delete operation is successful.
|
||||
*/
|
||||
service.deleteConnection = function deleteConnection(connection) {
|
||||
service.deleteConnection = function deleteConnection(dataSource, connection) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -195,7 +195,7 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
// Delete connection
|
||||
return $http({
|
||||
method : 'DELETE',
|
||||
url : 'api/connections/' + encodeURIComponent(connection.identifier),
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(connection.identifier),
|
||||
params : httpParameters
|
||||
})
|
||||
|
||||
|
125
guacamole/src/main/webapp/app/rest/services/dataSourceService.js
Normal file
125
guacamole/src/main/webapp/app/rest/services/dataSourceService.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Service which contains all REST API response caches.
|
||||
*/
|
||||
angular.module('rest').factory('dataSourceService', ['$injector',
|
||||
function dataSourceService($injector) {
|
||||
|
||||
// Required services
|
||||
var $q = $injector.get('$q');
|
||||
|
||||
// Service containing all caches
|
||||
var service = {};
|
||||
|
||||
/**
|
||||
* Invokes the given function once for each of the given data sources,
|
||||
* passing that data source as the first argument to each invocation,
|
||||
* followed by any additional arguments passed to apply(). The results of
|
||||
* each invocation are aggregated into a map by data source identifier,
|
||||
* and handled through a single promise which is resolved or rejected
|
||||
* depending on the success/failure of each resulting REST call. Any error
|
||||
* results in rejection of the entire apply() operation, except 404 ("NOT
|
||||
* FOUND") errors, which are ignored.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* The function to call for each of the given data sources. The data
|
||||
* source identifier will be given as the first argument, followed by
|
||||
* the rest of the arguments given to apply(), in order. The function
|
||||
* must return a Promise which is resolved or rejected depending on the
|
||||
* result of the REST call.
|
||||
*
|
||||
* @param {String[]} dataSources
|
||||
* The array or data source identifiers against which the given
|
||||
* function should be called.
|
||||
*
|
||||
* @param {...*} args
|
||||
* Any additional arguments to pass to the given function each time it
|
||||
* is called.
|
||||
*
|
||||
* @returns {Promise.<Object.<String, *>>}
|
||||
* A Promise which resolves with a map of data source identifier to
|
||||
* corresponding result. The result will be the exact object or value
|
||||
* provided as the resolution to the Promise returned by calls to the
|
||||
* given function.
|
||||
*/
|
||||
service.apply = function apply(fn, dataSources) {
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
var requests = [];
|
||||
var results = {};
|
||||
|
||||
// Build array of arguments to pass to the given function
|
||||
var args = [];
|
||||
for (var i = 2; i < arguments.length; i++)
|
||||
args.push(arguments[i]);
|
||||
|
||||
// Retrieve the root group from all data sources
|
||||
angular.forEach(dataSources, function invokeAgainstDataSource(dataSource) {
|
||||
|
||||
// Add promise to list of pending requests
|
||||
var deferredRequest = $q.defer();
|
||||
requests.push(deferredRequest.promise);
|
||||
|
||||
// Retrieve root group from data source
|
||||
fn.apply(this, [dataSource].concat(args))
|
||||
|
||||
// Store result on success
|
||||
.then(function immediateRequestSucceeded(response) {
|
||||
results[dataSource] = response.data;
|
||||
deferredRequest.resolve();
|
||||
},
|
||||
|
||||
// Fail on any errors (except "NOT FOUND")
|
||||
function immediateRequestFailed(response) {
|
||||
|
||||
// Ignore "NOT FOUND" errors
|
||||
if (response.status === 404)
|
||||
deferredRequest.resolve();
|
||||
|
||||
// Explicitly abort for all other errors
|
||||
else
|
||||
deferredRequest.reject(response);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Resolve if all requests succeed
|
||||
$q.all(requests).then(function requestsSucceeded() {
|
||||
deferred.resolve(results);
|
||||
},
|
||||
|
||||
// Reject if at least one request fails
|
||||
function requestFailed(response) {
|
||||
deferred.reject(response);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
}]);
|
@@ -28,6 +28,7 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
|
||||
// Required services
|
||||
var $http = $injector.get('$http');
|
||||
var $q = $injector.get('$q');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var cacheService = $injector.get('cacheService');
|
||||
|
||||
@@ -41,6 +42,11 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
* given user, returning a promise that provides an array of
|
||||
* @link{Permission} objects if successful.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the user whose
|
||||
* permissions should be retrieved. This identifier corresponds to an
|
||||
* AuthenticationProvider within the Guacamole web application.
|
||||
*
|
||||
* @param {String} userID
|
||||
* The ID of the user to retrieve the permissions for.
|
||||
*
|
||||
@@ -48,7 +54,7 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
* A promise which will resolve with a @link{PermissionSet} upon
|
||||
* success.
|
||||
*/
|
||||
service.getPermissions = function getPermissions(userID) {
|
||||
service.getPermissions = function getPermissions(dataSource, userID) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -59,7 +65,7 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.users,
|
||||
method : 'GET',
|
||||
url : 'api/users/' + encodeURIComponent(userID) + '/permissions',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(userID) + '/permissions',
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -70,6 +76,11 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
* returning a promise that can be used for processing the results of the
|
||||
* call.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the user whose
|
||||
* permissions should be modified. This identifier corresponds to an
|
||||
* AuthenticationProvider within the Guacamole web application.
|
||||
*
|
||||
* @param {String} userID
|
||||
* The ID of the user to modify the permissions of.
|
||||
*
|
||||
@@ -80,8 +91,8 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* add operation is successful.
|
||||
*/
|
||||
service.addPermissions = function addPermissions(userID, permissions) {
|
||||
return service.patchPermissions(userID, permissions, null);
|
||||
service.addPermissions = function addPermissions(dataSource, userID, permissions) {
|
||||
return service.patchPermissions(dataSource, userID, permissions, null);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -89,6 +100,11 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
* returning a promise that can be used for processing the results of the
|
||||
* call.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the user whose
|
||||
* permissions should be modified. This identifier corresponds to an
|
||||
* AuthenticationProvider within the Guacamole web application.
|
||||
*
|
||||
* @param {String} userID
|
||||
* The ID of the user to modify the permissions of.
|
||||
*
|
||||
@@ -99,8 +115,8 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* remove operation is successful.
|
||||
*/
|
||||
service.removePermissions = function removePermissions(userID, permissions) {
|
||||
return service.patchPermissions(userID, null, permissions);
|
||||
service.removePermissions = function removePermissions(dataSource, userID, permissions) {
|
||||
return service.patchPermissions(dataSource, userID, null, permissions);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -186,6 +202,11 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
* user, returning a promise that can be used for processing the results of
|
||||
* the call.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the user whose
|
||||
* permissions should be modified. This identifier corresponds to an
|
||||
* AuthenticationProvider within the Guacamole web application.
|
||||
*
|
||||
* @param {String} userID
|
||||
* The ID of the user to modify the permissions of.
|
||||
*
|
||||
@@ -199,7 +220,7 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* patch operation is successful.
|
||||
*/
|
||||
service.patchPermissions = function patchPermissions(userID, permissionsToAdd, permissionsToRemove) {
|
||||
service.patchPermissions = function patchPermissions(dataSource, userID, permissionsToAdd, permissionsToRemove) {
|
||||
|
||||
var permissionPatch = [];
|
||||
|
||||
@@ -217,7 +238,7 @@ angular.module('rest').factory('permissionService', ['$injector',
|
||||
// Patch user permissions
|
||||
return $http({
|
||||
method : 'PATCH',
|
||||
url : 'api/users/' + encodeURIComponent(userID) + '/permissions',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(userID) + '/permissions',
|
||||
params : httpParameters,
|
||||
data : permissionPatch
|
||||
})
|
||||
|
@@ -39,12 +39,18 @@ angular.module('rest').factory('schemaService', ['$injector',
|
||||
* @link{Form} objects if successful. Each element of the array describes
|
||||
* a logical grouping of possible attributes.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the users whose
|
||||
* available attributes are to be retrieved. This identifier
|
||||
* corresponds to an AuthenticationProvider within the Guacamole web
|
||||
* application.
|
||||
*
|
||||
* @returns {Promise.<Form[]>}
|
||||
* A promise which will resolve with an array of @link{Form}
|
||||
* objects, where each @link{Form} describes a logical grouping of
|
||||
* possible attributes.
|
||||
*/
|
||||
service.getUserAttributes = function getUserAttributes() {
|
||||
service.getUserAttributes = function getUserAttributes(dataSource) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -55,7 +61,7 @@ angular.module('rest').factory('schemaService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.schema,
|
||||
method : 'GET',
|
||||
url : 'api/schema/users/attributes',
|
||||
url : 'api/schema/' + encodeURIComponent(dataSource) + '/users/attributes',
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -67,12 +73,18 @@ angular.module('rest').factory('schemaService', ['$injector',
|
||||
* @link{Form} objects if successful. Each element of the array describes
|
||||
* a logical grouping of possible attributes.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the connections
|
||||
* whose available attributes are to be retrieved. This identifier
|
||||
* corresponds to an AuthenticationProvider within the Guacamole web
|
||||
* application.
|
||||
*
|
||||
* @returns {Promise.<Form[]>}
|
||||
* A promise which will resolve with an array of @link{Form}
|
||||
* objects, where each @link{Form} describes a logical grouping of
|
||||
* possible attributes.
|
||||
*/
|
||||
service.getConnectionAttributes = function getConnectionAttributes() {
|
||||
service.getConnectionAttributes = function getConnectionAttributes(dataSource) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -83,7 +95,7 @@ angular.module('rest').factory('schemaService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.schema,
|
||||
method : 'GET',
|
||||
url : 'api/schema/connections/attributes',
|
||||
url : 'api/schema/' + encodeURIComponent(dataSource) + '/connections/attributes',
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -95,12 +107,18 @@ angular.module('rest').factory('schemaService', ['$injector',
|
||||
* of @link{Form} objects if successful. Each element of the array
|
||||
* a logical grouping of possible attributes.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the connection
|
||||
* groups whose available attributes are to be retrieved. This
|
||||
* identifier corresponds to an AuthenticationProvider within the
|
||||
* Guacamole web application.
|
||||
*
|
||||
* @returns {Promise.<Form[]>}
|
||||
* A promise which will resolve with an array of @link{Form}
|
||||
* objects, where each @link{Form} describes a logical grouping of
|
||||
* possible attributes.
|
||||
*/
|
||||
service.getConnectionGroupAttributes = function getConnectionGroupAttributes() {
|
||||
service.getConnectionGroupAttributes = function getConnectionGroupAttributes(dataSource) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -111,7 +129,7 @@ angular.module('rest').factory('schemaService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.schema,
|
||||
method : 'GET',
|
||||
url : 'api/schema/connectionGroups/attributes',
|
||||
url : 'api/schema/' + encodeURIComponent(dataSource) + '/connectionGroups/attributes',
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -122,11 +140,16 @@ angular.module('rest').factory('schemaService', ['$injector',
|
||||
* a promise that provides a map of @link{Protocol} objects by protocol
|
||||
* name if successful.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source defining available
|
||||
* protocols. This identifier corresponds to an AuthenticationProvider
|
||||
* within the Guacamole web application.
|
||||
*
|
||||
* @returns {Promise.<Object.<String, Protocol>>}
|
||||
* A promise which will resolve with a map of @link{Protocol}
|
||||
* objects by protocol name upon success.
|
||||
*/
|
||||
service.getProtocols = function getProtocols() {
|
||||
service.getProtocols = function getProtocols(dataSource) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -137,7 +160,7 @@ angular.module('rest').factory('schemaService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.schema,
|
||||
method : 'GET',
|
||||
url : 'api/schema/protocols',
|
||||
url : 'api/schema/' + encodeURIComponent(dataSource) + '/protocols',
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Glyptodon LLC
|
||||
* 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
|
||||
@@ -28,6 +28,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
|
||||
// Required services
|
||||
var $http = $injector.get('$http');
|
||||
var $q = $injector.get('$q');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var cacheService = $injector.get('cacheService');
|
||||
|
||||
@@ -41,6 +42,11 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* returning a promise that provides an array of @link{User} objects if
|
||||
* successful.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the users to be
|
||||
* retrieved. This identifier corresponds to an AuthenticationProvider
|
||||
* within the Guacamole web application.
|
||||
*
|
||||
* @param {String[]} [permissionTypes]
|
||||
* The set of permissions to filter with. A user must have one or more
|
||||
* of these permissions for a user to appear in the result.
|
||||
@@ -51,7 +57,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* A promise which will resolve with an array of @link{User} objects
|
||||
* upon success.
|
||||
*/
|
||||
service.getUsers = function getUsers(permissionTypes) {
|
||||
service.getUsers = function getUsers(dataSource, permissionTypes) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -66,7 +72,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.users,
|
||||
method : 'GET',
|
||||
url : 'api/users',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/users',
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -77,13 +83,18 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* username, returning a promise that provides the corresponding
|
||||
* @link{User} if successful.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the user to be
|
||||
* retrieved. This identifier corresponds to an AuthenticationProvider
|
||||
* within the Guacamole web application.
|
||||
*
|
||||
* @param {String} username
|
||||
* The username of the user to retrieve.
|
||||
*
|
||||
* @returns {Promise.<User>}
|
||||
* A promise which will resolve with a @link{User} upon success.
|
||||
*/
|
||||
service.getUser = function getUser(username) {
|
||||
service.getUser = function getUser(dataSource, username) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -94,7 +105,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
return $http({
|
||||
cache : cacheService.users,
|
||||
method : 'GET',
|
||||
url : 'api/users/' + encodeURIComponent(username),
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username),
|
||||
params : httpParameters
|
||||
});
|
||||
|
||||
@@ -104,6 +115,11 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* Makes a request to the REST API to delete a user, returning a promise
|
||||
* that can be used for processing the results of the call.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the user to be
|
||||
* deleted. This identifier corresponds to an AuthenticationProvider
|
||||
* within the Guacamole web application.
|
||||
*
|
||||
* @param {User} user
|
||||
* The user to delete.
|
||||
*
|
||||
@@ -111,7 +127,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* delete operation is successful.
|
||||
*/
|
||||
service.deleteUser = function deleteUser(user) {
|
||||
service.deleteUser = function deleteUser(dataSource, user) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -121,7 +137,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
// Delete user
|
||||
return $http({
|
||||
method : 'DELETE',
|
||||
url : 'api/users/' + encodeURIComponent(user.username),
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(user.username),
|
||||
params : httpParameters
|
||||
})
|
||||
|
||||
@@ -137,6 +153,11 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* Makes a request to the REST API to create a user, returning a promise
|
||||
* that can be used for processing the results of the call.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source in which the user should be
|
||||
* created. This identifier corresponds to an AuthenticationProvider
|
||||
* within the Guacamole web application.
|
||||
*
|
||||
* @param {User} user
|
||||
* The user to create.
|
||||
*
|
||||
@@ -144,7 +165,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* create operation is successful.
|
||||
*/
|
||||
service.createUser = function createUser(user) {
|
||||
service.createUser = function createUser(dataSource, user) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -154,7 +175,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
// Create user
|
||||
return $http({
|
||||
method : 'POST',
|
||||
url : 'api/users',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/users',
|
||||
params : httpParameters,
|
||||
data : user
|
||||
})
|
||||
@@ -170,6 +191,11 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* Makes a request to the REST API to save a user, returning a promise that
|
||||
* can be used for processing the results of the call.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the user to be
|
||||
* updated. This identifier corresponds to an AuthenticationProvider
|
||||
* within the Guacamole web application.
|
||||
*
|
||||
* @param {User} user
|
||||
* The user to update.
|
||||
*
|
||||
@@ -177,7 +203,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* save operation is successful.
|
||||
*/
|
||||
service.saveUser = function saveUser(user) {
|
||||
service.saveUser = function saveUser(dataSource, user) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
@@ -187,7 +213,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
// Update user
|
||||
return $http({
|
||||
method : 'PUT',
|
||||
url : 'api/users/' + encodeURIComponent(user.username),
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(user.username),
|
||||
params : httpParameters,
|
||||
data : user
|
||||
})
|
||||
@@ -203,6 +229,11 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* Makes a request to the REST API to update the password for a user,
|
||||
* returning a promise that can be used for processing the results of the call.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The unique identifier of the data source containing the user to be
|
||||
* updated. This identifier corresponds to an AuthenticationProvider
|
||||
* within the Guacamole web application.
|
||||
*
|
||||
* @param {String} username
|
||||
* The username of the user to update.
|
||||
*
|
||||
@@ -216,7 +247,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* password update operation is successful.
|
||||
*/
|
||||
service.updateUserPassword = function updateUserPassword(username,
|
||||
service.updateUserPassword = function updateUserPassword(dataSource, username,
|
||||
oldPassword, newPassword) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
@@ -227,7 +258,7 @@ angular.module('rest').factory('userService', ['$injector',
|
||||
// Update user password
|
||||
return $http({
|
||||
method : 'PUT',
|
||||
url : 'api/users/' + encodeURIComponent(username) + '/password',
|
||||
url : 'api/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username) + '/password',
|
||||
params : httpParameters,
|
||||
data : new UserPasswordUpdate({
|
||||
oldPassword : oldPassword,
|
||||
|
@@ -46,17 +46,6 @@ angular.module('manage').controller('settingsController', ['$scope', '$injector'
|
||||
*/
|
||||
$scope.activeTab = $routeParams.tab;
|
||||
|
||||
/**
|
||||
* Returns whether the list of all available settings tabs should be shown.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
* true if the list of available settings tabs should be shown, false
|
||||
* otherwise.
|
||||
*/
|
||||
$scope.showAvailableTabs = function showAvailableTabs() {
|
||||
return !!$scope.settingsPages && $scope.settingsPages.length > 1;
|
||||
};
|
||||
|
||||
// Retrieve settings pages
|
||||
userPageService.getSettingsPages()
|
||||
.then(function settingsPagesRetrieved(pages) {
|
||||
|
@@ -41,13 +41,18 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe
|
||||
var PermissionSet = $injector.get('PermissionSet');
|
||||
|
||||
// Required services
|
||||
var $location = $injector.get('$location');
|
||||
var $routeParams = $injector.get('$routeParams');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var dataSourceService = $injector.get('dataSourceService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
|
||||
// Identifier of the current user
|
||||
/**
|
||||
* The identifier of the current user.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var currentUsername = authenticationService.getCurrentUsername();
|
||||
|
||||
/**
|
||||
@@ -62,12 +67,19 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The identifier of the currently-selected data source.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
$scope.dataSource = $routeParams.dataSource;
|
||||
|
||||
/**
|
||||
* The root connection group of the connection group hierarchy.
|
||||
*
|
||||
* @type ConnectionGroup
|
||||
* @type Object.<String, ConnectionGroup>
|
||||
*/
|
||||
$scope.rootGroup = null;
|
||||
$scope.rootGroups = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can manage connections. If the current
|
||||
@@ -98,7 +110,7 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe
|
||||
* All permissions associated with the current user, or null if the
|
||||
* user's permissions have not yet been loaded.
|
||||
*
|
||||
* @type PermissionSet
|
||||
* @type Object.<String, PermissionSet>
|
||||
*/
|
||||
$scope.permissions = null;
|
||||
|
||||
@@ -111,57 +123,142 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe
|
||||
*/
|
||||
$scope.isLoaded = function isLoaded() {
|
||||
|
||||
return $scope.rootGroup !== null
|
||||
&& $scope.permissions !== null
|
||||
&& $scope.canManageConnections !== null
|
||||
&& $scope.canCreateConnections !== null
|
||||
&& $scope.canCreateConnectionGroups !== null;
|
||||
return $scope.rootGroup !== null
|
||||
&& $scope.permissions !== null;
|
||||
|
||||
};
|
||||
|
||||
// Retrieve current permissions
|
||||
permissionService.getPermissions(currentUsername)
|
||||
.success(function permissionsRetrieved(permissions) {
|
||||
/**
|
||||
* Returns whether the current user can create new connections
|
||||
* within at least one data source.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* true if the current user can create new connections within
|
||||
* at least one data source, false otherwise.
|
||||
*/
|
||||
$scope.canCreateConnections = function canCreateConnections() {
|
||||
|
||||
$scope.permissions = permissions;
|
||||
// Abort if permissions have not yet loaded
|
||||
if (!$scope.permissions)
|
||||
return false;
|
||||
|
||||
// For each data source
|
||||
for (var dataSource in $scope.permissions) {
|
||||
|
||||
// Retrieve corresponding permission set
|
||||
var permissionSet = $scope.permissions[dataSource];
|
||||
|
||||
// Can create connections if adminstrator or have explicit permission
|
||||
if (PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.CREATE_CONNECTION))
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// No data sources allow connection creation
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the current user can create new connection
|
||||
* groups within at least one data source.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* true if the current user can create new connection groups
|
||||
* within at least one data source, false otherwise.
|
||||
*/
|
||||
$scope.canCreateConnectionGroups = function canCreateConnectionGroups() {
|
||||
|
||||
// Abort if permissions have not yet loaded
|
||||
if (!$scope.permissions)
|
||||
return false;
|
||||
|
||||
// For each data source
|
||||
for (var dataSource in $scope.permissions) {
|
||||
|
||||
// Retrieve corresponding permission set
|
||||
var permissionSet = $scope.permissions[dataSource];
|
||||
|
||||
// Can create connections groups if adminstrator or have explicit permission
|
||||
if (PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP))
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// No data sources allow connection group creation
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the current user can create new connections or
|
||||
* connection groups or make changes to existing connections or
|
||||
* connection groups within at least one data source. The
|
||||
* connection management interface as a whole is useless if this
|
||||
* function returns false.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* true if the current user can create new connections/groups
|
||||
* or make changes to existing connections/groups within at
|
||||
* least one data source, false otherwise.
|
||||
*/
|
||||
$scope.canManageConnections = function canManageConnections() {
|
||||
|
||||
// Abort if permissions have not yet loaded
|
||||
if (!$scope.permissions)
|
||||
return false;
|
||||
|
||||
// Creating connections/groups counts as management
|
||||
if ($scope.canCreateConnections() || $scope.canCreateConnectionGroups())
|
||||
return true;
|
||||
|
||||
// Ignore permission to update root group
|
||||
PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER);
|
||||
|
||||
// Determine whether the current user can create new users
|
||||
$scope.canCreateConnections =
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION);
|
||||
// For each data source
|
||||
for (var dataSource in $scope.permissions) {
|
||||
|
||||
// Determine whether the current user can create new users
|
||||
$scope.canCreateConnectionGroups =
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP);
|
||||
// Retrieve corresponding permission set
|
||||
var permissionSet = $scope.permissions[dataSource];
|
||||
|
||||
// Determine whether the current user can manage other connections or groups
|
||||
$scope.canManageConnections =
|
||||
// Can manage connections if granted explicit update or delete
|
||||
if (PermissionSet.hasConnectionPermission(permissionSet, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasConnectionPermission(permissionSet, PermissionSet.ObjectPermissionType.DELETE))
|
||||
return true;
|
||||
|
||||
// Permission to manage connections
|
||||
$scope.canCreateConnections
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)
|
||||
// Can manage connections groups if granted explicit update or delete
|
||||
if (PermissionSet.hasConnectionGroupPermission(permissionSet, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissionSet, PermissionSet.ObjectPermissionType.DELETE))
|
||||
return true;
|
||||
|
||||
// Permission to manage groups
|
||||
|| $scope.canCreateConnectionGroups
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE);
|
||||
}
|
||||
|
||||
// Return to home if there's nothing to do here
|
||||
if (!$scope.canManageConnections)
|
||||
$location.path('/');
|
||||
// No data sources allow management of connections or groups
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
// Retrieve current permissions
|
||||
dataSourceService.apply(
|
||||
permissionService.getPermissions,
|
||||
[$scope.dataSource],
|
||||
currentUsername
|
||||
)
|
||||
.then(function permissionsRetrieved(permissions) {
|
||||
$scope.permissions = permissions;
|
||||
});
|
||||
|
||||
// Retrieve all connections for which we have UPDATE or DELETE permission
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER,
|
||||
[PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE])
|
||||
.success(function connectionGroupReceived(rootGroup) {
|
||||
$scope.rootGroup = rootGroup;
|
||||
dataSourceService.apply(
|
||||
connectionGroupService.getConnectionGroupTree,
|
||||
[$scope.dataSource],
|
||||
ConnectionGroup.ROOT_IDENTIFIER,
|
||||
[PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE]
|
||||
)
|
||||
.then(function connectionGroupsReceived(rootGroups) {
|
||||
$scope.rootGroups = rootGroups;
|
||||
});
|
||||
|
||||
}]
|
||||
|
@@ -66,6 +66,14 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe
|
||||
*/
|
||||
var username = authenticationService.getCurrentUsername();
|
||||
|
||||
/**
|
||||
* The identifier of the data source which authenticated the
|
||||
* current user.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
var dataSource = authenticationService.getDataSource();
|
||||
|
||||
/**
|
||||
* All currently-set preferences, or their defaults if not yet set.
|
||||
*
|
||||
@@ -140,7 +148,7 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe
|
||||
}
|
||||
|
||||
// Save the user with the new password
|
||||
userService.updateUserPassword(username, $scope.oldPassword, $scope.newPassword)
|
||||
userService.updateUserPassword(dataSource, username, $scope.oldPassword, $scope.newPassword)
|
||||
.success(function passwordUpdated() {
|
||||
|
||||
// Clear the password fields
|
||||
@@ -174,7 +182,7 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe
|
||||
});
|
||||
|
||||
// Retrieve current permissions
|
||||
permissionService.getPermissions(username)
|
||||
permissionService.getPermissions(dataSource, username)
|
||||
.success(function permissionsRetrieved(permissions) {
|
||||
|
||||
// Add action for changing password if permission is granted
|
||||
|
@@ -44,19 +44,20 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
// Required services
|
||||
var $filter = $injector.get('$filter');
|
||||
var $translate = $injector.get('$translate');
|
||||
var $q = $injector.get('$q');
|
||||
var activeConnectionService = $injector.get('activeConnectionService');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var connectionGroupService = $injector.get('connectionGroupService');
|
||||
var dataSourceService = $injector.get('dataSourceService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
|
||||
/**
|
||||
* All permissions associated with the current user, or null if the
|
||||
* user's permissions have not yet been loaded.
|
||||
* The identifiers of all data sources accessible by the current
|
||||
* user.
|
||||
*
|
||||
* @type PermissionSet
|
||||
* @type String[]
|
||||
*/
|
||||
$scope.permissions = null;
|
||||
var dataSources = authenticationService.getAvailableDataSources();
|
||||
|
||||
/**
|
||||
* The ActiveConnectionWrappers of all active sessions accessible
|
||||
@@ -93,20 +94,22 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
];
|
||||
|
||||
/**
|
||||
* All active connections, if known, or null if active connections
|
||||
* have not yet been loaded.
|
||||
* All active connections, if known, grouped by corresponding data
|
||||
* source identifier, or null if active connections have not yet
|
||||
* been loaded.
|
||||
*
|
||||
* @type ActiveConnection
|
||||
* @type Object.<String, Object.<String, ActiveConnection>>
|
||||
*/
|
||||
var activeConnections = null;
|
||||
var allActiveConnections = null;
|
||||
|
||||
/**
|
||||
* Map of all visible connections by object identifier, or null if
|
||||
* visible connections have not yet been loaded.
|
||||
* Map of all visible connections by data source identifier and
|
||||
* object identifier, or null if visible connections have not yet
|
||||
* been loaded.
|
||||
*
|
||||
* @type Object.<String, Connection>
|
||||
* @type Object.<String, Object.<String, Connection>>
|
||||
*/
|
||||
var connections = null;
|
||||
var allConnections = null;
|
||||
|
||||
/**
|
||||
* The date format for use for session-related dates.
|
||||
@@ -117,24 +120,28 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
|
||||
/**
|
||||
* Map of all currently-selected active connection wrappers by
|
||||
* identifier.
|
||||
* data source and identifier.
|
||||
*
|
||||
* @type Object.<String, ActiveConnectionWrapper>
|
||||
* @type Object.<String, Object.<String, ActiveConnectionWrapper>>
|
||||
*/
|
||||
var selectedWrappers = {};
|
||||
var allSelectedWrappers = {};
|
||||
|
||||
/**
|
||||
* Adds the given connection to the internal set of visible
|
||||
* connections.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The identifier of the data source associated with the given
|
||||
* connection.
|
||||
*
|
||||
* @param {Connection} connection
|
||||
* The connection to add to the internal set of visible
|
||||
* connections.
|
||||
*/
|
||||
var addConnection = function addConnection(connection) {
|
||||
var addConnection = function addConnection(dataSource, connection) {
|
||||
|
||||
// Add given connection to set of visible connections
|
||||
connections[connection.identifier] = connection;
|
||||
allConnections[dataSource][connection.identifier] = connection;
|
||||
|
||||
};
|
||||
|
||||
@@ -142,19 +149,25 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
* Adds all descendant connections of the given connection group to
|
||||
* the internal set of connections.
|
||||
*
|
||||
* @param {String} dataSource
|
||||
* The identifier of the data source associated with the given
|
||||
* connection group.
|
||||
*
|
||||
* @param {ConnectionGroup} connectionGroup
|
||||
* The connection group whose descendant connections should be
|
||||
* added to the internal set of connections.
|
||||
*/
|
||||
var addDescendantConnections = function addDescendantConnections(connectionGroup) {
|
||||
var addDescendantConnections = function addDescendantConnections(dataSource, connectionGroup) {
|
||||
|
||||
// Add all child connections
|
||||
if (connectionGroup.childConnections)
|
||||
connectionGroup.childConnections.forEach(addConnection);
|
||||
angular.forEach(connectionGroup.childConnections, function addConnectionForDataSource(connection) {
|
||||
addConnection(dataSource, connection);
|
||||
});
|
||||
|
||||
// Add all child connection groups
|
||||
if (connectionGroup.childConnectionGroups)
|
||||
connectionGroup.childConnectionGroups.forEach(addDescendantConnections);
|
||||
angular.forEach(connectionGroup.childConnectionGroups, function addConnectionGroupForDataSource(connectionGroup) {
|
||||
addDescendantConnections(dataSource, connectionGroup);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@@ -163,56 +176,66 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
* within the scope. If required data has not yet finished loading,
|
||||
* this function has no effect.
|
||||
*/
|
||||
var wrapActiveConnections = function wrapActiveConnections() {
|
||||
var wrapAllActiveConnections = function wrapAllActiveConnections() {
|
||||
|
||||
// Abort if not all required data is available
|
||||
if (!activeConnections || !connections || !sessionDateFormat)
|
||||
if (!allActiveConnections || !allConnections || !sessionDateFormat)
|
||||
return;
|
||||
|
||||
// Wrap all active connections for sake of display
|
||||
$scope.wrappers = [];
|
||||
for (var identifier in activeConnections) {
|
||||
angular.forEach(allActiveConnections, function wrapActiveConnections(activeConnections, dataSource) {
|
||||
angular.forEach(activeConnections, function wrapActiveConnection(activeConnection, identifier) {
|
||||
|
||||
var activeConnection = activeConnections[identifier];
|
||||
var connection = connections[activeConnection.connectionIdentifier];
|
||||
// Retrieve corresponding connection
|
||||
var connection = allConnections[dataSource][activeConnection.connectionIdentifier];
|
||||
|
||||
$scope.wrappers.push(new ActiveConnectionWrapper(
|
||||
connection.name,
|
||||
$filter('date')(activeConnection.startDate, sessionDateFormat),
|
||||
activeConnection
|
||||
));
|
||||
// Add wrapper
|
||||
$scope.wrappers.push(new ActiveConnectionWrapper({
|
||||
dataSource : dataSource,
|
||||
name : connection.name,
|
||||
startDate : $filter('date')(activeConnection.startDate, sessionDateFormat),
|
||||
activeConnection : activeConnection
|
||||
}));
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// Query the user's permissions
|
||||
permissionService.getPermissions(authenticationService.getCurrentUsername())
|
||||
.success(function permissionsReceived(retrievedPermissions) {
|
||||
$scope.permissions = retrievedPermissions;
|
||||
});
|
||||
|
||||
// Retrieve all connections
|
||||
connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER)
|
||||
.success(function connectionGroupReceived(retrievedRootGroup) {
|
||||
dataSourceService.apply(
|
||||
connectionGroupService.getConnectionGroupTree,
|
||||
dataSources,
|
||||
ConnectionGroup.ROOT_IDENTIFIER
|
||||
)
|
||||
.then(function connectionGroupsReceived(rootGroups) {
|
||||
|
||||
// Load connections from retrieved group tree
|
||||
connections = {};
|
||||
addDescendantConnections(retrievedRootGroup);
|
||||
allConnections = {};
|
||||
|
||||
// Load connections from each received root group
|
||||
angular.forEach(rootGroups, function connectionGroupReceived(rootGroup, dataSource) {
|
||||
allConnections[dataSource] = {};
|
||||
addDescendantConnections(dataSource, rootGroup);
|
||||
});
|
||||
|
||||
// Attempt to produce wrapped list of active connections
|
||||
wrapActiveConnections();
|
||||
wrapAllActiveConnections();
|
||||
|
||||
});
|
||||
|
||||
// Query active sessions
|
||||
activeConnectionService.getActiveConnections().success(function sessionsRetrieved(retrievedActiveConnections) {
|
||||
dataSourceService.apply(
|
||||
activeConnectionService.getActiveConnections,
|
||||
dataSources
|
||||
)
|
||||
.then(function sessionsRetrieved(retrievedActiveConnections) {
|
||||
|
||||
// Store received list
|
||||
activeConnections = retrievedActiveConnections;
|
||||
// Store received map of active connections
|
||||
allActiveConnections = retrievedActiveConnections;
|
||||
|
||||
// Attempt to produce wrapped list of active connections
|
||||
wrapActiveConnections();
|
||||
wrapAllActiveConnections();
|
||||
|
||||
});
|
||||
|
||||
@@ -223,7 +246,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
sessionDateFormat = retrievedSessionDateFormat;
|
||||
|
||||
// Attempt to produce wrapped list of active connections
|
||||
wrapActiveConnections();
|
||||
wrapAllActiveConnections();
|
||||
|
||||
});
|
||||
|
||||
@@ -235,11 +258,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
* to be useful, false otherwise.
|
||||
*/
|
||||
$scope.isLoaded = function isLoaded() {
|
||||
|
||||
return $scope.wrappers !== null
|
||||
&& $scope.sessionDateFormat !== null
|
||||
&& $scope.permissions !== null;
|
||||
|
||||
return $scope.wrappers !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -276,7 +295,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
className : "danger",
|
||||
// Handle action
|
||||
callback : function deleteCallback() {
|
||||
deleteSessionsImmediately();
|
||||
deleteAllSessionsImmediately();
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
@@ -285,24 +304,36 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
* Immediately deletes the selected sessions, without prompting the
|
||||
* user for confirmation.
|
||||
*/
|
||||
var deleteSessionsImmediately = function deleteSessionsImmediately() {
|
||||
var deleteAllSessionsImmediately = function deleteAllSessionsImmediately() {
|
||||
|
||||
// Perform deletion
|
||||
activeConnectionService.deleteActiveConnections(Object.keys(selectedWrappers))
|
||||
.success(function activeConnectionsDeleted() {
|
||||
var deletionRequests = [];
|
||||
|
||||
// Perform deletion for each relevant data source
|
||||
angular.forEach(allSelectedWrappers, function deleteSessionsImmediately(selectedWrappers, dataSource) {
|
||||
|
||||
// Delete sessions, if any are selected
|
||||
var identifiers = Object.keys(selectedWrappers);
|
||||
if (identifiers.length)
|
||||
deletionRequests.push(activeConnectionService.deleteActiveConnections(dataSource, identifiers));
|
||||
|
||||
});
|
||||
|
||||
// Update interface
|
||||
$q.all(deletionRequests)
|
||||
.then(function activeConnectionsDeleted() {
|
||||
|
||||
// Remove deleted connections from wrapper array
|
||||
$scope.wrappers = $scope.wrappers.filter(function activeConnectionStillExists(wrapper) {
|
||||
return !(wrapper.activeConnection.identifier in selectedWrappers);
|
||||
return !(wrapper.activeConnection.identifier in (allSelectedWrappers[wrapper.dataSource] || {}));
|
||||
});
|
||||
|
||||
// Clear selection
|
||||
selectedWrappers = {};
|
||||
allSelectedWrappers = {};
|
||||
|
||||
})
|
||||
},
|
||||
|
||||
// Notify of any errors
|
||||
.error(function activeConnectionDeletionFailed(error) {
|
||||
function activeConnectionDeletionFailed(error) {
|
||||
guacNotification.showStatus({
|
||||
'className' : 'error',
|
||||
'title' : 'SETTINGS_SESSIONS.DIALOG_HEADER_ERROR',
|
||||
@@ -335,8 +366,10 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
$scope.canDeleteSessions = function canDeleteSessions() {
|
||||
|
||||
// We can delete sessions if at least one is selected
|
||||
for (var identifier in selectedWrappers)
|
||||
return true;
|
||||
for (var dataSource in allSelectedWrappers) {
|
||||
for (var identifier in allSelectedWrappers[dataSource])
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -351,6 +384,11 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti
|
||||
*/
|
||||
$scope.wrapperSelectionChange = function wrapperSelectionChange(wrapper) {
|
||||
|
||||
// Get selection map for associated data source, creating if necessary
|
||||
var selectedWrappers = allSelectedWrappers[wrapper.dataSource];
|
||||
if (!selectedWrappers)
|
||||
selectedWrappers = allSelectedWrappers[wrapper.dataSource] = {};
|
||||
|
||||
// Add wrapper to map if selected
|
||||
if (wrapper.checked)
|
||||
selectedWrappers[wrapper.activeConnection.identifier] = wrapper;
|
||||
|
@@ -37,12 +37,13 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings
|
||||
controller: ['$scope', '$injector', function settingsUsersController($scope, $injector) {
|
||||
|
||||
// Required types
|
||||
var ManageableUser = $injector.get('ManageableUser');
|
||||
var PermissionSet = $injector.get('PermissionSet');
|
||||
var User = $injector.get('User');
|
||||
|
||||
// Required services
|
||||
var $location = $injector.get('$location');
|
||||
var authenticationService = $injector.get('authenticationService');
|
||||
var dataSourceService = $injector.get('dataSourceService');
|
||||
var guacNotification = $injector.get('guacNotification');
|
||||
var permissionService = $injector.get('permissionService');
|
||||
var userService = $injector.get('userService');
|
||||
@@ -63,27 +64,19 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings
|
||||
};
|
||||
|
||||
/**
|
||||
* All visible users.
|
||||
* The identifiers of all data sources accessible by the current
|
||||
* user.
|
||||
*
|
||||
* @type User[]
|
||||
* @type String[]
|
||||
*/
|
||||
$scope.users = null;
|
||||
var dataSources = authenticationService.getAvailableDataSources();
|
||||
|
||||
/**
|
||||
* Whether the current user can manage users. If the current
|
||||
* permissions have not yet been loaded, this will be null.
|
||||
* All visible users, along with their corresponding data sources.
|
||||
*
|
||||
* @type Boolean
|
||||
* @type ManageableUser[]
|
||||
*/
|
||||
$scope.canManageUsers = null;
|
||||
|
||||
/**
|
||||
* Whether the current user can create new users. If the current
|
||||
* permissions have not yet been loaded, this will be null.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
$scope.canCreateUsers = null;
|
||||
$scope.manageableUsers = null;
|
||||
|
||||
/**
|
||||
* The name of the new user to create, if any, when user creation
|
||||
@@ -94,10 +87,11 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings
|
||||
$scope.newUsername = "";
|
||||
|
||||
/**
|
||||
* All permissions associated with the current user, or null if the
|
||||
* Map of data source identifiers to all permissions associated
|
||||
* with the current user within that data source, or null if the
|
||||
* user's permissions have not yet been loaded.
|
||||
*
|
||||
* @type PermissionSet
|
||||
* @type Object.<String, PermissionSet>
|
||||
*/
|
||||
$scope.permissions = null;
|
||||
|
||||
@@ -110,80 +104,152 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings
|
||||
*/
|
||||
$scope.isLoaded = function isLoaded() {
|
||||
|
||||
return $scope.users !== null
|
||||
&& $scope.permissions !== null
|
||||
&& $scope.canManageUsers !== null
|
||||
&& $scope.canCreateUsers !== null;
|
||||
return $scope.manageableUsers !== null
|
||||
&& $scope.permissions !== null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the identifier of the data source that should be used by
|
||||
* default when creating a new user.
|
||||
*
|
||||
* @return {String}
|
||||
* The identifier of the data source that should be used by
|
||||
* default when creating a new user, or null if user creation
|
||||
* is not allowed.
|
||||
*/
|
||||
var getDefaultDataSource = function getDefaultDataSource() {
|
||||
|
||||
// Abort if permissions have not yet loaded
|
||||
if (!$scope.permissions)
|
||||
return null;
|
||||
|
||||
// For each data source
|
||||
for (var dataSource in $scope.permissions) {
|
||||
|
||||
// Retrieve corresponding permission set
|
||||
var permissionSet = $scope.permissions[dataSource];
|
||||
|
||||
// Can create users if adminstrator or have explicit permission
|
||||
if (PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.CREATE_USER))
|
||||
return dataSource;
|
||||
|
||||
}
|
||||
|
||||
// No data sources allow user creation
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the current user can create new users within at
|
||||
* least one data source.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* true if the current user can create new users within at
|
||||
* least one data source, false otherwise.
|
||||
*/
|
||||
$scope.canCreateUsers = function canCreateUsers() {
|
||||
return getDefaultDataSource() !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the current user can create new users or make
|
||||
* changes to existing users within at least one data source. The
|
||||
* user management interface as a whole is useless if this function
|
||||
* returns false.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* true if the current user can create new users or make
|
||||
* changes to existing users within at least one data source,
|
||||
* false otherwise.
|
||||
*/
|
||||
var canManageUsers = function canManageUsers() {
|
||||
|
||||
// Abort if permissions have not yet loaded
|
||||
if (!$scope.permissions)
|
||||
return false;
|
||||
|
||||
// Creating users counts as management
|
||||
if ($scope.canCreateUsers())
|
||||
return true;
|
||||
|
||||
// For each data source
|
||||
for (var dataSource in $scope.permissions) {
|
||||
|
||||
// Retrieve corresponding permission set
|
||||
var permissionSet = $scope.permissions[dataSource];
|
||||
|
||||
// Can manage users if granted explicit update or delete
|
||||
if (PermissionSet.hasUserPermission(permissionSet, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasUserPermission(permissionSet, PermissionSet.ObjectPermissionType.DELETE))
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// No data sources allow management of users
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
// Retrieve current permissions
|
||||
permissionService.getPermissions(currentUsername)
|
||||
.success(function permissionsRetrieved(permissions) {
|
||||
dataSourceService.apply(permissionService.getPermissions, dataSources, currentUsername)
|
||||
.then(function permissionsRetrieved(permissions) {
|
||||
|
||||
// Store retrieved permissions
|
||||
$scope.permissions = permissions;
|
||||
|
||||
// Determine whether the current user can create new users
|
||||
$scope.canCreateUsers =
|
||||
PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
|
||||
|| PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER);
|
||||
|
||||
// Determine whether the current user can manage other users
|
||||
$scope.canManageUsers =
|
||||
$scope.canCreateUsers
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)
|
||||
|| PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE);
|
||||
|
||||
// Return to home if there's nothing to do here
|
||||
if (!$scope.canManageUsers)
|
||||
if (!canManageUsers())
|
||||
$location.path('/');
|
||||
|
||||
});
|
||||
var userPromise;
|
||||
|
||||
// Retrieve all users for whom we have UPDATE or DELETE permission
|
||||
userService.getUsers([PermissionSet.ObjectPermissionType.UPDATE,
|
||||
PermissionSet.ObjectPermissionType.DELETE])
|
||||
.success(function usersReceived(users) {
|
||||
// If users can be created, list all readable users
|
||||
if ($scope.canCreateUsers())
|
||||
userPromise = dataSourceService.apply(userService.getUsers, dataSources);
|
||||
|
||||
// Otherwise, list only updateable/deletable users
|
||||
else
|
||||
userPromise = dataSourceService.apply(userService.getUsers, dataSources, [
|
||||
PermissionSet.ObjectPermissionType.UPDATE,
|
||||
PermissionSet.ObjectPermissionType.DELETE
|
||||
]);
|
||||
|
||||
userPromise.then(function usersReceived(userArrays) {
|
||||
|
||||
var addedUsers = {};
|
||||
$scope.manageableUsers = [];
|
||||
|
||||
// For each user in each data source
|
||||
angular.forEach(dataSources, function addUserList(dataSource) {
|
||||
angular.forEach(userArrays[dataSource], function addUser(user) {
|
||||
|
||||
// Do not add the same user twice
|
||||
if (addedUsers[user.username])
|
||||
return;
|
||||
|
||||
// Add user to overall list
|
||||
addedUsers[user.username] = user;
|
||||
$scope.manageableUsers.push(new ManageableUser ({
|
||||
'dataSource' : dataSource,
|
||||
'user' : user
|
||||
}));
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// Display only other users, not self
|
||||
$scope.users = users.filter(function isNotSelf(user) {
|
||||
return user.username !== currentUsername;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new user having the username specified in the user
|
||||
* creation interface.
|
||||
* Navigates to an interface for creating a new user having the
|
||||
* username specified.
|
||||
*/
|
||||
$scope.newUser = function newUser() {
|
||||
|
||||
// Create user skeleton
|
||||
var user = new User({
|
||||
username: $scope.newUsername || ''
|
||||
});
|
||||
|
||||
// Create specified user
|
||||
userService.createUser(user)
|
||||
|
||||
// Add user to visible list upon success
|
||||
.success(function userCreated() {
|
||||
$scope.users.push(user);
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
.error(function userCreationFailed(error) {
|
||||
guacNotification.showStatus({
|
||||
'className' : 'error',
|
||||
'title' : 'SETTINGS_USERS.DIALOG_HEADER_ERROR',
|
||||
'text' : error.message,
|
||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||
});
|
||||
});
|
||||
|
||||
// Reset username
|
||||
$scope.newUsername = "";
|
||||
|
||||
$location.url('/manage/' + encodeURIComponent(getDefaultDataSource()) + '/users/' + encodeURIComponent($scope.newUsername));
|
||||
};
|
||||
|
||||
}]
|
||||
|
@@ -34,36 +34,3 @@
|
||||
text-align: center;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: rgba(0, 0, 0, 0.0125);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href] {
|
||||
display: block;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
padding: 0.75em 1em;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href]:visited {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href]:hover {
|
||||
background-color: #CDA;
|
||||
}
|
||||
|
||||
.settings-tabs .page-list li a[href].current,
|
||||
.settings-tabs .page-list li a[href].current:hover {
|
||||
background: rgba(0,0,0,0.3);
|
||||
cursor: default;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<a ng-href="#/manage/connections/{{item.identifier}}">
|
||||
<a ng-href="#/manage/{{item.dataSource}}/connections/{{item.identifier}}">
|
||||
<!--
|
||||
Copyright (C) 2014 Glyptodon LLC
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<a ng-href="#/manage/connectionGroups/{{item.identifier}}">
|
||||
<a ng-href="#/manage/{{item.dataSource}}/connectionGroups/{{item.identifier}}">
|
||||
<!--
|
||||
Copyright (C) 2014 Glyptodon LLC
|
||||
|
||||
|
@@ -28,8 +28,8 @@ THE SOFTWARE.
|
||||
</div>
|
||||
|
||||
<!-- Available tabs -->
|
||||
<div class="settings-tabs">
|
||||
<guac-page-list pages="settingsPages" ng-show="showAvailableTabs()"></guac-page-list>
|
||||
<div class="page-tabs">
|
||||
<guac-page-list pages="settingsPages"></guac-page-list>
|
||||
</div>
|
||||
|
||||
<!-- Selected tab -->
|
||||
|
@@ -29,11 +29,11 @@
|
||||
|
||||
<a class="add-connection button"
|
||||
ng-show="canCreateConnections"
|
||||
href="#/manage/connections/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION' | translate}}</a>
|
||||
href="#/manage/{{dataSource}}/connections/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION' | translate}}</a>
|
||||
|
||||
<a class="add-connection-group button"
|
||||
ng-show="canCreateConnectionGroups"
|
||||
href="#/manage/connectionGroups/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION_GROUP' | translate}}</a>
|
||||
href="#/manage/{{dataSource}}/connectionGroups/">{{'SETTINGS_CONNECTIONS.ACTION_NEW_CONNECTION_GROUP' | translate}}</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<div class="connection-list">
|
||||
<guac-group-list
|
||||
page-size="25"
|
||||
connection-group="rootGroup"
|
||||
connection-groups="rootGroups"
|
||||
connection-template="'app/settings/templates/connection.html'"
|
||||
connection-group-template="'app/settings/templates/connectionGroup.html'"/>
|
||||
</div>
|
||||
|
@@ -25,24 +25,25 @@
|
||||
<p>{{'SETTINGS_USERS.HELP_USERS' | translate}}</p>
|
||||
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons" ng-show="canCreateUsers">
|
||||
<div class="action-buttons" ng-show="canCreateUsers()">
|
||||
<input type="text" ng-model="newUsername" class="name username" autocorrect="off" autocapitalize="off"/>
|
||||
<button class="add-user" ng-click="newUser()">{{'SETTINGS_USERS.ACTION_NEW_USER' | translate}}</button>
|
||||
</div>
|
||||
|
||||
<!-- List of users this user has access to -->
|
||||
<div class="user-list">
|
||||
<div ng-repeat="user in userPage" class="user list-item">
|
||||
<a ng-href="#/manage/users/{{user.username}}">
|
||||
<div ng-repeat="manageableUser in manageableUserPage" class="user list-item">
|
||||
<a ng-href="#/manage/{{manageableUser.dataSource}}/users/{{manageableUser.user.username}}">
|
||||
<div class="caption">
|
||||
<div class="icon user"></div>
|
||||
<span class="name">{{user.username}}</span>
|
||||
<span class="name">{{manageableUser.user.username}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pager controls for user list -->
|
||||
<guac-pager page="userPage" page-size="25" items="users | orderBy : 'username'"></guac-pager>
|
||||
<guac-pager page="manageableUserPage" page-size="25"
|
||||
items="manageableUsers | orderBy : 'user.username'"></guac-pager>
|
||||
|
||||
</div>
|
@@ -31,44 +31,47 @@ angular.module('settings').factory('ActiveConnectionWrapper', [
|
||||
* properties, such as a checked option.
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} name
|
||||
* The display name of the active connection.
|
||||
*
|
||||
* @param {String} startDate
|
||||
* The date and time this session began, pre-formatted for display.
|
||||
*
|
||||
* @param {ActiveConnection} activeConnection
|
||||
* The ActiveConnection to wrap.
|
||||
* @param {ActiveConnectionWrapper|Object} template
|
||||
* The object whose properties should be copied within the new
|
||||
* ActiveConnectionWrapper.
|
||||
*/
|
||||
var ActiveConnectionWrapper = function ActiveConnectionWrapper(name, startDate, activeConnection) {
|
||||
var ActiveConnectionWrapper = function ActiveConnectionWrapper(template) {
|
||||
|
||||
/**
|
||||
* The identifier of the data source associated with the
|
||||
* ActiveConnection wrapped by this ActiveConnectionWrapper.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.dataSource = template.dataSource;
|
||||
|
||||
/**
|
||||
* The display name of this connection.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.name = name;
|
||||
this.name = template.name;
|
||||
|
||||
/**
|
||||
* The date and time this session began, pre-formatted for display.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.startDate = startDate;
|
||||
this.startDate = template.startDate;
|
||||
|
||||
/**
|
||||
* The wrapped ActiveConnection.
|
||||
*
|
||||
* @type ActiveConnection
|
||||
*/
|
||||
this.activeConnection = activeConnection;
|
||||
this.activeConnection = template.activeConnection;
|
||||
|
||||
/**
|
||||
* A flag indicating that the active connection has been selected.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
this.checked = false;
|
||||
this.checked = template.checked || false;
|
||||
|
||||
};
|
||||
|
||||
|
BIN
guacamole/src/main/webapp/images/checkmark.png
Normal file
BIN
guacamole/src/main/webapp/images/checkmark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 569 B |
BIN
guacamole/src/main/webapp/images/plus.png
Normal file
BIN
guacamole/src/main/webapp/images/plus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 299 B |
@@ -125,6 +125,10 @@
|
||||
|
||||
},
|
||||
|
||||
"DATA_SOURCE_DEFAULT" : {
|
||||
"NAME" : "Default (XML)"
|
||||
},
|
||||
|
||||
"FORM" : {
|
||||
|
||||
"FIELD_PLACEHOLDER_DATE" : "YYYY-MM-DD",
|
||||
@@ -241,6 +245,8 @@
|
||||
"FIELD_HEADER_PASSWORD_AGAIN" : "@:APP.FIELD_HEADER_PASSWORD_AGAIN",
|
||||
"FIELD_HEADER_USERNAME" : "Username:",
|
||||
|
||||
"INFO_READ_ONLY" : "Sorry, but this user account cannot be edited.",
|
||||
|
||||
"SECTION_HEADER_CONNECTIONS" : "Connections",
|
||||
"SECTION_HEADER_EDIT_USER" : "Edit User",
|
||||
"SECTION_HEADER_PERMISSIONS" : "Permissions",
|
||||
|
Reference in New Issue
Block a user