diff --git a/guacamole/pom.xml b/guacamole/pom.xml
index e737bb189..c0b6b3bc3 100644
--- a/guacamole/pom.xml
+++ b/guacamole/pom.xml
@@ -290,6 +290,11 @@
guice
3.0
+
+ com.google.inject.extensions
+ guice-assistedinject
+ 3.0
+
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectResource.java
new file mode 100644
index 000000000..298f76b56
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectResource.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.rest.directory;
+
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.Identifiable;
+
+/**
+ * A REST resource which abstracts the operations available on an existing
+ * Guacamole object that is contained within a Directory, such as modification,
+ * deletion, or individual retrieval.
+ *
+ * @author Michael Jumper
+ * @param
+ * The type of object that this DirectoryObjectResource represents. To
+ * avoid coupling the REST API too tightly to the extension API, these
+ * objects are not directly serialized or deserialized when handling REST
+ * requests.
+ *
+ * @param
+ * The type of object used in interchange (ie: serialized/deserialized as
+ * JSON) between REST clients and this DirectoryObjectResource to
+ * represent the InternalType.
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class DirectoryObjectResource {
+
+ /**
+ * The Directory which contains the object represented by this
+ * DirectoryObjectResource.
+ */
+ private final Directory directory;
+
+ /**
+ * The object represented by this DirectoryObjectResource.
+ */
+ private final InternalType object;
+
+ /**
+ * A DirectoryObjectTranslator implementation which handles the type of
+ * objects represented by this DirectoryObjectResource.
+ */
+ private final DirectoryObjectTranslator translator;
+
+ /**
+ * Creates a new DirectoryObjectResource which exposes the operations
+ * available for the given object.
+ *
+ * @param directory
+ * The Directory which contains the given object.
+ *
+ * @param object
+ * The object that this DirectoryObjectResource should represent.
+ *
+ * @param translator
+ * A DirectoryObjectTranslator implementation which handles the type of
+ * object given.
+ */
+ @AssistedInject
+ public DirectoryObjectResource(@Assisted Directory directory,
+ @Assisted InternalType object,
+ DirectoryObjectTranslator translator) {
+ this.directory = directory;
+ this.object = object;
+ this.translator = translator;
+ }
+
+ /**
+ * Returns the object represented by this DirectoryObjectResource, in a
+ * format intended for interchange.
+ *
+ * @return
+ * The object that this DirectoryObjectResource represents, in a format
+ * intended for interchange.
+ *
+ * @throws GuacamoleException
+ * If an error is encountered while retrieving the object.
+ */
+ @GET
+ public ExternalType getObject() throws GuacamoleException {
+ return translator.toExternalObject(object);
+ }
+
+ /**
+ * Updates an existing object. The changes to be made to the corresponding
+ * object within the directory indicated by the provided data.
+ *
+ * @param modifiedObject
+ * The data to update the corresponding object with.
+ *
+ * @throws GuacamoleException
+ * If an error occurs while updating the object.
+ */
+ @PUT
+ public void updateObject(ExternalType modifiedObject) throws GuacamoleException {
+ translator.applyExternalChanges(object, modifiedObject);
+ directory.update(object);
+ }
+
+ /**
+ * Removes this object from the containing directory.
+ *
+ * @throws GuacamoleException
+ * If an error occurs while removing the object.
+ */
+ @DELETE
+ public void deleteObject() throws GuacamoleException {
+ directory.remove(object.getIdentifier());
+ }
+
+}
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectResourceFactory.java b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectResourceFactory.java
new file mode 100644
index 000000000..ba173828d
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectResourceFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.rest.directory;
+
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.Identifiable;
+
+/**
+ * Factory which creates DirectoryObjectResource instances exposing objects of
+ * a particular type.
+ *
+ * @param
+ * The type of object exposed by the DirectoryObjectResource instances
+ * created by this DirectoryResourceFactory.
+ *
+ * @param
+ * The type of object used in interchange (ie: serialized or deserialized
+ * as JSON) between REST clients and resources when representing the
+ * InternalType.
+ */
+public interface DirectoryObjectResourceFactory {
+
+ /**
+ * Creates a new DirectoryObjectResource which exposes the given object.
+ *
+ * @param directory
+ * The Directory which contains the object being exposed.
+ *
+ * @param object
+ * The object which should be exposed by the created
+ * DirectoryObjectResource.
+ *
+ * @return
+ * A new DirectoryObjectResource which exposes the given object.
+ */
+ DirectoryObjectResource
+ create(Directory directory, InternalType object);
+
+}
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectTranslator.java b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectTranslator.java
new file mode 100644
index 000000000..bac47957b
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryObjectTranslator.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.rest.directory;
+
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.Identifiable;
+
+/**
+ * Provides bidirectional conversion between REST-specific objects and the
+ * internal objects defined by the Guacamole extension API.
+ *
+ * @author Michael Jumper
+ * @param
+ * The type of object converted by this DirectoryObjectTranslator which is
+ * not necessarily intended for use in interchange.
+ *
+ * @param
+ * The type of object used in interchange (ie: serialized or
+ * deserialized as JSON) between REST clients and resource implementations
+ * when representing the InternalType.
+ */
+public interface DirectoryObjectTranslator {
+
+ /**
+ * Converts the given object to an object which is intended to be used in
+ * interchange.
+ *
+ * @param object
+ * The object to convert for the sake of interchange.
+ *
+ * @return
+ * A new object containing the same data as the given internal object,
+ * but intended for use in interchange.
+ *
+ * @throws GuacamoleException
+ * If the provided object cannot be converted for any reason.
+ */
+ ExternalType toExternalObject(InternalType object)
+ throws GuacamoleException;
+
+ /**
+ * Converts the given object to an object which is intended to be used
+ * within the Guacamole extension API.
+ *
+ * @param object
+ * An object of the type intended for use in interchange, such as that
+ * produced by toExternalObject() or received from a user via REST.
+ *
+ * @return
+ * A new object containing the same data as the given external object,
+ * but intended for use within the Guacamole extension API.
+ *
+ * @throws GuacamoleException
+ * If the provided object cannot be converted for any reason.
+ */
+ InternalType toInternalObject(ExternalType object)
+ throws GuacamoleException;
+
+ /**
+ * Overlays the changes indicated by the given external object, modifying
+ * the given existing object from the Guacamole extension API.
+ *
+ * @param existingObject
+ * The existing object from the Guacamole extension API which should be
+ * modified.
+ *
+ * @param object
+ * The external object representing the modifications to the existing
+ * internal object.
+ *
+ * @throws GuacamoleException
+ * If the provided modifications cannot be applied for any reason.
+ */
+ void applyExternalChanges(InternalType existingObject, ExternalType object)
+ throws GuacamoleException;
+
+}
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java
new file mode 100644
index 000000000..8c748b004
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.rest.directory;
+
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import org.apache.guacamole.GuacamoleClientException;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleResourceNotFoundException;
+import org.apache.guacamole.GuacamoleUnsupportedException;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.Identifiable;
+import org.apache.guacamole.net.auth.User;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.net.auth.permission.ObjectPermission;
+import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
+import org.apache.guacamole.net.auth.permission.SystemPermission;
+import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
+import org.apache.guacamole.rest.APIPatch;
+import org.apache.guacamole.rest.PATCH;
+
+/**
+ * A REST resource which abstracts the operations available on all Guacamole
+ * Directory implementations, such as the creation of new objects, or listing
+ * of existing objects. A DirectoryResource functions as the parent of any
+ * number of child DirectoryObjectResources, which are created with the factory
+ * provided at the time of this object's construction.
+ *
+ * @author Michael Jumper
+ * @param
+ * The type of object contained within the Directory that this
+ * DirectoryResource exposes. To avoid coupling the REST API too tightly to
+ * the extension API, these objects are not directly serialized or
+ * deserialized when handling REST requests.
+ *
+ * @param
+ * The type of object used in interchange (ie: serialized/deserialized as
+ * JSON) between REST clients and this DirectoryResource when representing
+ * the InternalType.
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class DirectoryResource {
+
+ /**
+ * The UserContext associated with the Directory being exposed by this
+ * DirectoryResource.
+ */
+ private final UserContext userContext;
+
+ /**
+ * The Directory being exposed by this DirectoryResource.
+ */
+ private final Directory directory;
+
+ /**
+ * A DirectoryObjectTranslator implementation which handles the type of
+ * objects contained within the Directory exposed by this DirectoryResource.
+ */
+ private final DirectoryObjectTranslator translator;
+
+ /**
+ * A factory which can be used to create instances of resources representing
+ * individual objects contained within the Directory exposed by this
+ * DirectoryResource.
+ */
+ private final DirectoryObjectResourceFactory resourceFactory;
+
+ /**
+ * Creates a new DirectoryResource which exposes the operations available
+ * for the given Directory.
+ *
+ * @param userContext
+ * The UserContext associated with the given Directory.
+ *
+ * @param directory
+ * The Directory being exposed by this DirectoryResource.
+ *
+ * @param translator
+ * A DirectoryObjectTranslator implementation which handles the type of
+ * objects contained within the given Directory.
+ *
+ * @param resourceFactory
+ * A factory which can be used to create instances of resources
+ * representing individual objects contained within the given Directory.
+ */
+ @AssistedInject
+ public DirectoryResource(@Assisted UserContext userContext,
+ @Assisted Directory directory,
+ DirectoryObjectTranslator translator,
+ DirectoryObjectResourceFactory resourceFactory) {
+ this.userContext = userContext;
+ this.directory = directory;
+ this.translator = translator;
+ this.resourceFactory = resourceFactory;
+ }
+
+ /**
+ * Returns a map of all objects available within this DirectoryResource,
+ * filtering the returned map by the given permission, if specified.
+ *
+ * @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.
+ * If null, no filtering will be performed.
+ *
+ * @return
+ * A map of all visible objects. If a permission was specified, this
+ * map will contain only those objects for which the current user has
+ * that permission.
+ *
+ * @throws GuacamoleException
+ * If an error is encountered while retrieving the objects.
+ */
+ @GET
+ public Map getObjects(
+ @QueryParam("permission") List permissions)
+ throws GuacamoleException {
+
+ // An admin user has access to all objects
+ User self = userContext.self();
+ SystemPermissionSet systemPermissions = self.getSystemPermissions();
+ boolean isAdmin = systemPermissions.hasPermission(SystemPermission.Type.ADMINISTER);
+
+ // Filter objects, if requested
+ Collection identifiers = directory.getIdentifiers();
+ if (!isAdmin && permissions != null && !permissions.isEmpty()) {
+ ObjectPermissionSet objectPermissions = self.getUserPermissions();
+ identifiers = objectPermissions.getAccessibleObjects(permissions, identifiers);
+ }
+
+ Map apiObjects = new HashMap();
+ for (InternalType object : directory.getAll(identifiers))
+ apiObjects.put(object.getIdentifier(), translator.toExternalObject(object));
+
+ return apiObjects;
+
+ }
+
+ /**
+ * Applies the given object patches, updating the underlying directory
+ * accordingly. This operation currently only supports deletion of objects
+ * through the "remove" patch operation. The path of each patch operation is
+ * of the form "/ID" where ID is the identifier of the object being
+ * modified.
+ *
+ * @param patches
+ * The patches to apply for this request.
+ *
+ * @throws GuacamoleException
+ * If an error occurs while deleting the objects.
+ */
+ @PATCH
+ public void patchObjects(List> patches)
+ throws GuacamoleException {
+
+ // Apply each operation specified within the patch
+ for (APIPatch patch : patches) {
+
+ // Only remove is supported
+ if (patch.getOp() != APIPatch.Operation.remove)
+ throw new GuacamoleUnsupportedException("Only the \"remove\" "
+ + "operation is supported.");
+
+ // Retrieve and validate path
+ String path = patch.getPath();
+ if (!path.startsWith("/"))
+ throw new GuacamoleClientException("Patch paths must start with \"/\".");
+
+ // Remove specified object
+ directory.remove(path.substring(1));
+
+ }
+
+ }
+
+ /**
+ * Creates a new object within the underlying Directory, returning the
+ * object that was created. The identifier of the created object will be
+ * populated, if applicable.
+ *
+ * @param object
+ * The object to create.
+ *
+ * @return
+ * The object that was created.
+ *
+ * @throws GuacamoleException
+ * If an error occurs while adding the object to the underlying
+ * directory.
+ */
+ @POST
+ public ExternalType createObject(ExternalType object)
+ throws GuacamoleException {
+
+ // Create the new object within the directory
+ directory.add(translator.toInternalObject(object));
+
+ return object;
+
+ }
+
+ /**
+ * Retrieves an individual object, returning a DirectoryObjectResource
+ * implementation which exposes operations available on that object.
+ *
+ * @param identifier
+ * The identifier of the object to retrieve.
+ *
+ * @return
+ * A DirectoryObjectResource exposing operations available on the
+ * object having the given identifier.
+ *
+ * @throws GuacamoleException
+ * If an error occurs while retrieving the object.
+ */
+ @Path("{identifier}")
+ public DirectoryObjectResource
+ getObjectResource(@PathParam("identifier") String identifier)
+ throws GuacamoleException {
+
+ // Retrieve the object having the given identifier
+ InternalType object = directory.get(identifier);
+ if (object == null)
+ throw new GuacamoleResourceNotFoundException("Not found: \"" + identifier + "\"");
+
+ // Return a resource which provides access to the retrieved object
+ return resourceFactory.create(directory, object);
+
+ }
+
+}
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResourceFactory.java b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResourceFactory.java
new file mode 100644
index 000000000..357777a6f
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResourceFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.rest.directory;
+
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.Identifiable;
+import org.apache.guacamole.net.auth.UserContext;
+
+/**
+ * Factory which creates DirectoryResource instances exposing Directory
+ * objects of a particular type.
+ *
+ * @param
+ * The type of object contained within the Directory objects exposed by the
+ * DirectoryResource instances created by this DirectoryResourceFactory.
+ *
+ * @param
+ * The type of object used in interchange (ie: serialized or deserialized
+ * as JSON) between REST clients and resources when representing the
+ * InternalType.
+ */
+public interface DirectoryResourceFactory {
+
+ /**
+ * Creates a new DirectoryResource which exposes the given Directory.
+ *
+ * @param userContext
+ * The UserContext from which the given Directory was obtained.
+ *
+ * @param directory
+ * The Directory which should be exposed by the created
+ * DirectoryResource.
+ *
+ * @return
+ * A new DirectoryResource which exposes the given Directory.
+ */
+ DirectoryResource
+ create(UserContext userContext, Directory directory);
+
+}
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/directory/package-info.java b/guacamole/src/main/java/org/apache/guacamole/rest/directory/package-info.java
new file mode 100644
index 000000000..5283e8719
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/directory/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Base classes providing support for exposing and manipulating Directory
+ * objects and their contents via the Guacamole REST API.
+ */
+package org.apache.guacamole.rest.directory;