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;