diff --git a/drafter-client/src/drafter_client/client.clj b/drafter-client/src/drafter_client/client.clj index aabcbc36f..be0694cc0 100644 --- a/drafter-client/src/drafter_client/client.clj +++ b/drafter-client/src/drafter_client/client.clj @@ -155,6 +155,16 @@ (i/request client i/submit-draftset-to access-token id :role (name role))) +(defn share-with-user [client access-token id user] + (i/request client i/share-draftset-with access-token id :user user)) + +(defn share-with-permission [client access-token id permission] + (i/request client i/share-draftset-with access-token id + :permission (name permission))) + +(defn unshare [client access-token id] + (i/request client i/unshare-draftset access-token id)) + (defn claim [client access-token id] (i/request client i/claim-draftset access-token id)) diff --git a/drafter-client/src/drafter_client/client/impl.clj b/drafter-client/src/drafter_client/client/impl.clj index 7fc97a5a2..10215391c 100644 --- a/drafter-client/src/drafter_client/client/impl.clj +++ b/drafter-client/src/drafter_client/client/impl.clj @@ -367,6 +367,22 @@ :submit-draftset-to (merge {:id id} opts))) +(defn share-draftset-with + "Share a Draftset with a user or permission" + [client id & {:keys [user permission] :as opts}] + (martian/response-for + client + :share-draftset-with + (merge {:id id} opts))) + +(defn unshare-draftset + "Share a Draftset with a user or permission" + [client id] + (martian/response-for + client + :unshare-draftset + {:id id})) + (defn- assert-client [client] (when-not client (throw (ex-info "Trying to make request to drafter with `nil` client." diff --git a/drafter-client/test/drafter_client/client_test.clj b/drafter-client/test/drafter_client/client_test.clj index 193e85dce..68485b9be 100644 --- a/drafter-client/test/drafter_client/client_test.clj +++ b/drafter-client/test/drafter_client/client_test.clj @@ -217,24 +217,28 @@ (t/deftest draftsets-include-test (let [client (drafter-client) - token (auth-util/publisher-token) - ds-1 (sut/new-draftset client token "first" "description") - ds-2 (sut/new-draftset client token "second" "description")] - (sut/submit-to-permission client token (draftset/id ds-2) :drafter:draft:claim) + publisher-token (auth-util/publisher-token) + editor-token (auth-util/editor-token) + ds-1 (sut/new-draftset client publisher-token "first" "description") + ds-2 (sut/new-draftset client publisher-token "second" "description") + ds-3 (sut/new-draftset client editor-token "third" "description")] + (sut/submit-to-permission client publisher-token (draftset/id ds-2) :drafter:draft:claim) + (sut/share-with-permission client editor-token (draftset/id ds-3) :drafter:draft:view) (t/testing "default" - (let [draftsets (sut/draftsets client token)] - (t/is (= #{(draftset/id ds-1) (draftset/id ds-2)} + (let [draftsets (sut/draftsets client publisher-token)] + (t/is (= #{(draftset/id ds-1) (draftset/id ds-2) (draftset/id ds-3)} (set (map draftset/id draftsets)))))) + (sut/unshare client editor-token (draftset/id ds-3)) (t/testing "all" - (let [draftsets (sut/draftsets client token {:include :all})] + (let [draftsets (sut/draftsets client publisher-token {:include :all})] (t/is (= #{(draftset/id ds-1) (draftset/id ds-2)} (set (map draftset/id draftsets)))))) (t/testing "owned" - (let [owned (sut/draftsets client token {:include :owned})] + (let [owned (sut/draftsets client publisher-token {:include :owned})] (is (= #{(draftset/id ds-1)} (set (map draftset/id owned)))))) (t/testing "claimable" - (let [claimable (sut/draftsets client token {:include :claimable})] + (let [claimable (sut/draftsets client publisher-token {:include :claimable})] (is (= #{(draftset/id ds-2)} (set (map draftset/id claimable)))))))) diff --git a/drafter-client/test/drafter_client/test_util/auth.clj b/drafter-client/test/drafter_client/test_util/auth.clj index d8b526222..95542f888 100644 --- a/drafter-client/test/drafter_client/test_util/auth.clj +++ b/drafter-client/test/drafter_client/test_util/auth.clj @@ -20,3 +20,10 @@ "publisher@swirrl.com" "drafter:publisher" (role->permissions :publisher))) + +(defn editor-token [] + (token (env :auth0-domain) + (env :auth0-aud) + "editor@swirrl.com" + "drafter:editor" + (role->permissions :editor))) diff --git a/drafter/doc/drafter.yml b/drafter/doc/drafter.yml index b58736f1d..514aaccda 100644 --- a/drafter/doc/drafter.yml +++ b/drafter/doc/drafter.yml @@ -56,6 +56,10 @@ info: depending on their permissions then choose to make further ammendments, `/publish` or submit the draftset to a new owner. + - `/share` to allow other users read only access to this draft without + relinquishing ownership of it -- either by specifying specific users, + or permissions that users must have. + - `/publish` which can be used by clients with the `drafter:draft:publish` permission to publish a reviewed draftset to the live site. @@ -512,6 +516,47 @@ paths: $ref: '#/definitions/Draftset' '422': description: The submit request could not be processed. + /draftset/{id}/share: + post: + operationId: share-draftset-with + summary: Share a Draftset with a user or permission holder + description: | + Shares this draftset for read only access by another user. Draftsets + are shared directly with a user, or with a pool of users with a given + permission. + security: + - jws-auth: [] + parameters: + - $ref: '#/parameters/id' + - $ref: '#/parameters/permission' + - $ref: '#/parameters/share-user' + tags: + - Draftsets + responses: + '200': + description: The Draftset was successfully shared. + schema: + $ref: '#/definitions/Draftset' + '422': + description: The share request could not be processed. + delete: + operationId: unshare-draftset + summary: Unshare a Draftset + description: | + Unshares this draftset so that only its owner can view it. + security: + - jws-auth: [] + parameters: + - $ref: '#/parameters/id' + tags: + - Draftsets + responses: + '200': + description: The Draftset was successfully unshared. + schema: + $ref: '#/definitions/Draftset' + '422': + description: The unshare request could not be processed. /draftset/{id}/claim: put: operationId: claim-draftset @@ -875,6 +920,12 @@ parameters: in: query required: false type: string + share-user: + name: user + description: The username of the user to share the draftset with. + in: query + required: false + type: string include: name: include diff --git a/drafter/resources/drafter-base-config.edn b/drafter/resources/drafter-base-config.edn index 733ce5e76..3d45f82d6 100644 --- a/drafter/resources/drafter-base-config.edn +++ b/drafter/resources/drafter-base-config.edn @@ -82,6 +82,10 @@ {:drafter/backend #ig/ref :drafter/backend :wrap-authenticate #ig/ref :drafter.middleware/wrap-authenticate} + :drafter.feature.middleware/wrap-as-draftset-viewer + {:drafter/backend #ig/ref :drafter/backend + :wrap-authenticate #ig/ref :drafter.middleware/wrap-authenticate} + :drafter.feature.draftset.delete/handler {:drafter/backend #ig/ref :drafter/backend :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} @@ -89,9 +93,10 @@ {:drafter/backend #ig/ref :drafter/backend :wrap-authenticate #ig/ref :drafter.middleware/wrap-authenticate} - :drafter.feature.draftset-data.show/handler {:drafter/backend #ig/ref :drafter/backend - :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner - :timeout-fn #ig/ref [:drafter.timeouts/timeout-query :drafter/draftset-timeout]} + :drafter.feature.draftset-data.show/handler + {:drafter/backend #ig/ref :drafter/backend + :wrap-as-draftset-viewer #ig/ref :drafter.feature.middleware/wrap-as-draftset-viewer + :timeout-fn #ig/ref [:drafter.timeouts/timeout-query :drafter/draftset-timeout]} :drafter.feature.draftset-data.delete/delete-data-handler {:drafter/manager #ig/ref :drafter/manager :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} @@ -117,9 +122,10 @@ :drafter.feature.draftset-data.append-by-graph/handler {:drafter/manager #ig/ref :drafter/manager :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} - :drafter.feature.draftset.query/handler {:drafter/backend #ig/ref :drafter/backend - :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner - :timeout-fn #ig/ref [:drafter.timeouts/timeout-query :drafter/draftset-timeout]} + :drafter.feature.draftset.query/handler + {:drafter/backend #ig/ref :drafter/backend + :wrap-as-draftset-viewer #ig/ref :drafter.feature.middleware/wrap-as-draftset-viewer + :timeout-fn #ig/ref [:drafter.timeouts/timeout-query :drafter/draftset-timeout]} :drafter.feature.draftset.update/handler {:drafter/manager #ig/ref :drafter/manager :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner @@ -135,6 +141,16 @@ :drafter.user/repo #ig/ref :drafter.user/repo :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} + :drafter.feature.draftset.share/post + {:drafter/manager #ig/ref :drafter/manager + :drafter.user/repo #ig/ref :drafter.user/repo + :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} + + :drafter.feature.draftset.share/delete + {:drafter/manager #ig/ref :drafter/manager + :drafter.user/repo #ig/ref :drafter.user/repo + :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} + :drafter.feature.draftset.claim/handler {:drafter/manager #ig/ref :drafter/manager :wrap-authenticate #ig/ref :drafter.middleware/wrap-authenticate} @@ -151,37 +167,39 @@ :drafter.middleware/wrap-authenticate {:auth-methods #ig/refset :drafter.auth/auth-method} - [:drafter/routes :draftset/api] {:context "/v1" - :routes [[:get "/users" #ig/ref :drafter.feature.users.list/get-users-handler] + [:drafter/routes :draftset/api] + {:context "/v1" + :routes + [[:get "/users" #ig/ref :drafter.feature.users.list/get-users-handler] - [:get "/draftsets" #ig/ref :drafter.feature.draftset.list/get-draftsets-handler] - [:post "/draftsets" #ig/ref :drafter.feature.draftset.create/handler] + [:get "/draftsets" #ig/ref :drafter.feature.draftset.list/get-draftsets-handler] + [:post "/draftsets" #ig/ref :drafter.feature.draftset.create/handler] - [:get "/draftset/:id" #ig/ref :drafter.feature.draftset.show/handler] - [:delete "/draftset/:id" #ig/ref :drafter.feature.draftset.delete/handler] - [:options "/draftset/:id" #ig/ref :drafter.feature.draftset.options/handler] + [:get "/draftset/:id" #ig/ref :drafter.feature.draftset.show/handler] + [:delete "/draftset/:id" #ig/ref :drafter.feature.draftset.delete/handler] + [:options "/draftset/:id" #ig/ref :drafter.feature.draftset.options/handler] - [:get "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.show/handler] - [:delete "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.delete/delete-data-handler] + [:get "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.show/handler] + [:delete "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.delete/delete-data-handler] - [:delete "/draftset/:id/graph" #ig/ref :drafter.feature.draftset-data.delete-by-graph/remove-graph-handler] - [:delete "/draftset/:id/changes" #ig/ref :drafter.feature.draftset.changes/delete-changes-handler] + [:delete "/draftset/:id/graph" #ig/ref :drafter.feature.draftset-data.delete-by-graph/remove-graph-handler] + [:delete "/draftset/:id/changes" #ig/ref :drafter.feature.draftset.changes/delete-changes-handler] + [:put "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.append/data-handler] + [:put "/draftset/:id/graph" #ig/ref :drafter.feature.draftset-data.append-by-graph/handler] + [nil "/draftset/:id/query" #ig/ref :drafter.feature.draftset.query/handler] + [:post "/draftset/:id/update" #ig/ref :drafter.feature.draftset.update/handler] - [:put "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.append/data-handler] - [:put "/draftset/:id/graph" #ig/ref :drafter.feature.draftset-data.append-by-graph/handler] - [nil "/draftset/:id/query" #ig/ref :drafter.feature.draftset.query/handler] - [:post "/draftset/:id/update" #ig/ref :drafter.feature.draftset.update/handler] + [:post "/draftset/:id/publish" #ig/ref :drafter.feature.draftset.publish/handler] + [:put "/draftset/:id" #ig/ref :drafter.feature.draftset.set-metadata/handler] + [:post "/draftset/:id/submit-to" #ig/ref :drafter.feature.draftset.submit/handler] + [:put "/draftset/:id/claim" #ig/ref :drafter.feature.draftset.claim/handler] + [:post "/draftset/:id/share" #ig/ref :drafter.feature.draftset.share/post] + [:delete "/draftset/:id/share" #ig/ref :drafter.feature.draftset.share/delete] - [:post "/draftset/:id/publish" #ig/ref :drafter.feature.draftset.publish/handler] - [:put "/draftset/:id" #ig/ref :drafter.feature.draftset.set-metadata/handler] - [:post "/draftset/:id/submit-to" #ig/ref :drafter.feature.draftset.submit/handler] - [:put "/draftset/:id/claim" #ig/ref :drafter.feature.draftset.claim/handler] + [:get "/endpoint/public" #ig/ref :drafter.feature.endpoint.show/handler] + [:get "/endpoints" #ig/ref :drafter.feature.endpoint.list/handler]]} - [:get "/endpoint/public" #ig/ref :drafter.feature.endpoint.show/handler] - [:get "/endpoints" #ig/ref :drafter.feature.endpoint.list/handler] - ] - } :drafter.swagger/swagger-routes {:auth-methods #ig/refset :drafter.auth/auth-method :global-auth? #ig/ref :drafter/global-auth?} diff --git a/drafter/src/drafter/backend/draftset/operations.clj b/drafter/src/drafter/backend/draftset/operations.clj index 88d467287..b05296170 100644 --- a/drafter/src/drafter/backend/draftset/operations.clj +++ b/drafter/src/drafter/backend/draftset/operations.clj @@ -16,7 +16,8 @@ (:import org.eclipse.rdf4j.model.impl.ContextStatementImpl [org.eclipse.rdf4j.query GraphQuery TupleQueryResult TupleQueryResultHandler BindingSet GraphQueryResult] org.eclipse.rdf4j.queryrender.RenderUtils - org.eclipse.rdf4j.rio.RDFHandler)) + org.eclipse.rdf4j.rio.RDFHandler + java.net.URI)) (defn- create-draftset-statements [user-uri title description draftset-uri created-date] (let [ss [draftset-uri @@ -169,6 +170,18 @@ " ?ds <" drafter:hasSubmission "> ?submission ." " ?submission <" drafter:claimPermission "> ?permission ." "}" + ;; Makes the assumption that usernames and permissions don't contain + ;; spaces, so we can use space as a separator. Usernames are URIs, which + ;; cannot contain spaces. Permissions are OAuth 2.0 scopes, which also + ;; cannot contain spaces. + "OPTIONAL {" + " SELECT ?ds (GROUP_CONCAT(?p; SEPARATOR=\" \") AS ?viewpermissions)" + " WHERE { ?ds <" drafter:viewPermission "> ?p } GROUP BY ?ds" + "}" + "OPTIONAL {" + " SELECT ?ds (GROUP_CONCAT(?u; SEPARATOR=\" \") AS ?viewusers)" + " WHERE { ?ds <" drafter:viewUser "> ?u } GROUP BY ?ds" + "}" "{" " SELECT DISTINCT ?ds WHERE {" " ?ds <" rdf:a "> <" drafter:DraftSet "> ." @@ -210,7 +223,9 @@ claimuser submitter modified - version] :as ds}] + version + viewpermissions + viewusers] :as ds}] (let [required-fields {:id (str (ds/->draftset-id draftset-ref)) :type "Draftset" :created-at created @@ -224,7 +239,15 @@ :claim-role (keyword (user/canonical-permission->role permission)) :claim-user (some-> claimuser (user/uri->username)) - :submitted-by (some-> submitter (user/uri->username))}] + :submitted-by (some-> submitter (user/uri->username)) + :view-permissions + (when viewpermissions + (set (map keyword + (string/split viewpermissions #" ")))) + :view-users + (when viewusers + (set (map #(user/uri->username (URI. %)) + (string/split viewusers #" "))))}] (merge required-fields (remove (comp nil? second) optional-fields)))) (defn- combine-draftset-properties-and-graph-states [ds-properties graph-states] @@ -263,6 +286,9 @@ (defn get-draftset-info [repo draftset-ref] (first (get-all-draftsets-by repo [(draftset-uri-clause draftset-ref)]))) +(defn is-draftset-viewer? [backend draftset-ref user] + (user/can-view? user (get-draftset-info backend draftset-ref))) + (defn- delete-draftset-query [draftset-ref draft-graph-uris] (let [delete-drafts-query (map mgmt/delete-draft-graph-and-remove-from-state-query draft-graph-uris) delete-draftset-query (delete-draftset-statements-query draftset-ref)] @@ -340,6 +366,45 @@ (submit-draftset-to-permission-query draftset-ref (util/create-uuid) owner permission))) +(defn- share-draftset-with-permission-query + [draftset-ref owner permission] + (let [draftset-uri (ds/->draftset-uri draftset-ref)] + (str + "INSERT {" + (with-state-graph + "<" draftset-uri "> <" drafter:viewPermission "> \"" (name permission) "\" .") + "} WHERE {" + (with-state-graph + "<" draftset-uri "> <" rdf:a "> <" drafter:DraftSet "> ." + "<" draftset-uri "> <" drafter:hasOwner "> <" (user/user->uri owner) "> .") + "}"))) + +(defn share-draftset-with-permission! + "Shares a draftset with users with the specified permission. If the given + user is not the current owner of the draftset, no changes are made." + [backend draftset-ref owner permission] + (sparql/update! backend (share-draftset-with-permission-query + draftset-ref owner permission))) + +(defn unshare-draftset! + "Removes all shares from a draftset, so only the owner can view it. If the + given user is not the current owner of the draftset, no changes are made." + [backend draftset-ref owner] + (let [draftset-uri (ds/->draftset-uri draftset-ref)] + (sparql/update! backend + (str + "DELETE {" + (with-state-graph + "<" draftset-uri "> <" drafter:viewPermission "> ?vp ;" + " <" drafter:viewUser "> ?vu .") + "} WHERE {" + (with-state-graph + "<" draftset-uri "> <" drafter:hasOwner "> <" (user/user->uri owner) "> ;" + " <" rdf:a "> <" drafter:DraftSet "> ." + " OPTIONAL { <" draftset-uri "> <" drafter:viewPermission "> ?vp . }" + " OPTIONAL { <" draftset-uri "> <" drafter:viewUser "> ?vu . }") + "}")))) + (defn- submit-to-user-query [draftset-ref submission-id submitter target] (let [submitter-uri (user/user->uri submitter) target-uri (user/user->uri target) @@ -371,6 +436,22 @@ (let [q (submit-to-user-query draftset-ref (util/create-uuid) submitter target)] (sparql/update! backend q))) +(defn- share-with-user-query [draftset-ref owner target] + (let [draftset-uri (ds/->draftset-uri draftset-ref)] + (str + "INSERT {" + (with-state-graph + "<" draftset-uri "> <" drafter:viewUser "> <" (user/user->uri target) "> .") + "} WHERE {" + (with-state-graph + "<" draftset-uri "> <" rdf:a "> <" drafter:DraftSet "> ." + "<" draftset-uri "> <" drafter:hasOwner "> <" (user/user->uri owner) "> .") + "}"))) + +(defn share-draftset-with-user! [backend draftset-ref submitter target] + (let [q (share-with-user-query draftset-ref submitter target)] + (sparql/update! backend q))) + (defn- try-claim-draftset-query [draftset-ref claimant] (let [draftset-uri (ds/->draftset-uri draftset-ref) user-uri (user/user->uri claimant)] diff --git a/drafter/src/drafter/feature/draftset/list.clj b/drafter/src/drafter/feature/draftset/list.clj index dceb11ea2..520ecad0b 100644 --- a/drafter/src/drafter/feature/draftset/list.clj +++ b/drafter/src/drafter/feature/draftset/list.clj @@ -34,6 +34,15 @@ "}") ) +;; Fetches all drafts with any view permission set, we still need to check that +;; the user actually has the set permission. +(def view-permission-clause + (str "{ ?ds <" drafter:viewPermission "> ?permission }")) + +(defn- user-is-view-user-clause [user] + (str + "{ ?ds <" drafter:viewUser "> <" (user/user->uri user) "> }")) + (defn- user-is-owner-clause [user] (str "{ ?ds <" drafter:hasOwner "> <" (user/user->uri user) "> . }")) @@ -44,7 +53,9 @@ (defn user-all-visible-clauses [user] (conj (user-claimable-clauses user) - (user-is-owner-clause user))) + (user-is-owner-clause user) + view-permission-clause + (user-is-view-user-clause user))) (defn get-all-draftsets-info [repo user] (filter #(user/can-view? user %) diff --git a/drafter/src/drafter/feature/draftset/query.clj b/drafter/src/drafter/feature/draftset/query.clj index 9ea514724..06dd48ceb 100644 --- a/drafter/src/drafter/feature/draftset/query.clj +++ b/drafter/src/drafter/feature/draftset/query.clj @@ -8,8 +8,8 @@ [integrant.core :as ig])) (defn handler - [{backend :drafter/backend :keys [wrap-as-draftset-owner timeout-fn]}] - (wrap-as-draftset-owner :drafter:draft:view + [{backend :drafter/backend :keys [wrap-as-draftset-viewer timeout-fn]}] + (wrap-as-draftset-viewer :drafter:draft:view (parse-union-with-live-handler (fn [{{:keys [draftset-id union-with-live]} :params :as request}] (let [executor (backend/endpoint-repo backend draftset-id {:union-with-live? union-with-live}) @@ -18,7 +18,7 @@ (defmethod ig/pre-init-spec ::handler [_] (s/keys :req [:drafter/backend] - :req-un [::wrap-as-draftset-owner ::sp/timeout-fn])) + :req-un [::wrap-as-draftset-viewer ::sp/timeout-fn])) (def cors-allowed-headers #{"Accept" diff --git a/drafter/src/drafter/feature/draftset/share.clj b/drafter/src/drafter/feature/draftset/share.clj new file mode 100644 index 000000000..9a90b400b --- /dev/null +++ b/drafter/src/drafter/feature/draftset/share.clj @@ -0,0 +1,63 @@ +(ns drafter.feature.draftset.share + (:require + [drafter.backend.draftset.operations :as dsops] + [drafter.feature.common :as feat-common] + [drafter.responses :refer [unprocessable-entity-response]] + [drafter.user :as user] + [integrant.core :as ig])) + +(defn handle-user + [{:keys [backend] :as manager} repo user draftset-id owner] + (if-let [target-user (user/find-user-by-username repo user)] + (feat-common/run-sync + manager + (:email owner) + 'share-draftset-with-user + draftset-id + #(dsops/share-draftset-with-user! backend draftset-id owner target-user) + #(feat-common/draftset-sync-write-response % backend draftset-id)) + (unprocessable-entity-response (str "User: " user " not found")))) + +(defn handle-permission + [{:keys [backend] :as manager} permission draftset-id owner] + (feat-common/run-sync + manager + (:email owner) + 'share-draftset-with-permission + draftset-id + #(dsops/share-draftset-with-permission! backend + draftset-id + owner + (keyword permission)) + #(feat-common/draftset-sync-write-response % backend draftset-id))) + +(defmethod ig/init-key :drafter.feature.draftset.share/post + [_ {:keys [drafter/manager drafter.user/repo wrap-as-draftset-owner]}] + (wrap-as-draftset-owner :drafter:draft:share + (fn [{{:keys [user permission draftset-id]} :params owner :identity}] + (cond + (and (some? user) (some? permission)) + (unprocessable-entity-response + "Only one of user and permission parameters permitted") + + (some? user) + (handle-user manager repo user draftset-id owner) + + (some? permission) + (handle-permission manager permission draftset-id owner) + + :else + (unprocessable-entity-response "user or permission parameter required"))))) + +(defmethod ig/init-key :drafter.feature.draftset.share/delete + [_ {:keys [drafter/manager drafter.user/repo wrap-as-draftset-owner]}] + (wrap-as-draftset-owner :drafter:draft:share + (fn [{{:keys [draftset-id]} :params owner :identity}] + (feat-common/run-sync + manager + (:email owner) + 'unshare-draftset + draftset-id + #(dsops/unshare-draftset! (:backend manager) draftset-id owner) + #(feat-common/draftset-sync-write-response + % (:backend manager) draftset-id))))) diff --git a/drafter/src/drafter/feature/draftset_data/show.clj b/drafter/src/drafter/feature/draftset_data/show.clj index 3b2704f51..8d061e9c2 100644 --- a/drafter/src/drafter/feature/draftset_data/show.clj +++ b/drafter/src/drafter/feature/draftset_data/show.clj @@ -8,10 +8,10 @@ [integrant.core :as ig])) (defn handler - [{wrap-as-draftset-owner :wrap-as-draftset-owner + [{wrap-as-draftset-viewer :wrap-as-draftset-viewer backend :drafter/backend draftset-query-timeout-fn :timeout-fn}] - (wrap-as-draftset-owner :drafter:draft:view + (wrap-as-draftset-viewer :drafter:draft:view (parse-union-with-live-handler (fn [{{:keys [draftset-id graph union-with-live] :as params} :params :as request}] (let [executor (ep/build-draftset-endpoint backend draftset-id union-with-live) @@ -30,7 +30,7 @@ (defmethod ig/pre-init-spec ::handler [_] (s/keys :req [:drafter/backend] - :req-un [::wrap-as-draftset-owner ::sp/timeout-fn])) + :req-un [::wrap-as-draftset-viewer ::sp/timeout-fn])) (defmethod ig/init-key ::handler [_ opts] (handler opts)) diff --git a/drafter/src/drafter/feature/middleware.clj b/drafter/src/drafter/feature/middleware.clj index 2387e6853..57a6f5cbf 100644 --- a/drafter/src/drafter/feature/middleware.clj +++ b/drafter/src/drafter/feature/middleware.clj @@ -52,6 +52,15 @@ (inner-handler request) (response/forbidden-response "Operation only permitted by draftset owner")))) +(defn restrict-to-draftset-viewer + "Middleware to enforce authentication and check the user making the request + has permission to view the draftset" + [backend inner-handler] + (fn [{user :identity {:keys [draftset-id]} :params :as request}] + (if (dsops/is-draftset-viewer? backend draftset-id user) + (inner-handler request) + (response/forbidden-response "Operation only permitted by draftset viewers")))) + (defn wrap-as-draftset-owner [{:keys [:drafter/backend wrap-authenticate]}] (fn [permission handler] @@ -60,8 +69,19 @@ backend (restrict-to-draftset-owner backend handler))))) +(defn wrap-as-draftset-viewer + [{:keys [:drafter/backend wrap-authenticate]}] + (fn [permission handler] + (middleware/wrap-authorize wrap-authenticate permission + (existing-draftset-handler + backend + (restrict-to-draftset-viewer backend handler))))) + (defmethod ig/pre-init-spec ::wrap-as-draftset-owner [_] (s/keys :req [:drafter/backend])) (defmethod ig/init-key ::wrap-as-draftset-owner [_ opts] (wrap-as-draftset-owner opts)) + +(defmethod ig/init-key ::wrap-as-draftset-viewer [_ opts] + (wrap-as-draftset-viewer opts)) diff --git a/drafter/src/drafter/rdf/drafter_ontology.clj b/drafter/src/drafter/rdf/drafter_ontology.clj index b13c92b63..abfb5ad7d 100644 --- a/drafter/src/drafter/rdf/drafter_ontology.clj +++ b/drafter/src/drafter/rdf/drafter_ontology.clj @@ -49,6 +49,10 @@ (def drafter:claimUser (url/append-path-segments drafter "claimUser")) +(def drafter:viewPermission (url/append-path-segments drafter "viewPermission")) + +(def drafter:viewUser (url/append-path-segments drafter "viewUser")) + (def drafter:submittedBy (url/append-path-segments drafter "submittedBy")) (def drafter:draft (url/append-path-segments drafter)) diff --git a/drafter/src/drafter/user.clj b/drafter/src/drafter/user.clj index a56dbb7ad..141bb895b 100644 --- a/drafter/src/drafter/user.clj +++ b/drafter/src/drafter/user.clj @@ -174,10 +174,10 @@ (has-permission? user claim-permission)) (= claim-user (username user)))) -;; Currently the conditions for viewing a draft (in a list of all drafts, etc) -;; happen to be the same as the conditions for claiming a draft. This needn't -;; be the case in the future. -(def can-view? can-claim?) +(defn can-view? [user draftset] + (or (can-claim? user draftset) + (contains? (:view-users draftset) (username user)) + (some #(has-permission? user %) (:view-permissions draftset)))) (defn permitted-draftset-operations [draftset user] (cond diff --git a/drafter/test/drafter/feature/draftset/list_test.clj b/drafter/test/drafter/feature/draftset/list_test.clj index 5ab395447..b9ab68c70 100644 --- a/drafter/test/drafter/feature/draftset/list_test.clj +++ b/drafter/test/drafter/feature/draftset/list_test.clj @@ -56,17 +56,20 @@ [{:keys [drafter.feature.draftset.list/get-draftsets-handler] :as system} "drafter/feature/draftset/list_test-2.edn"] (t/testing "All draftsets" - (assert-visibility #{"owned" "publishing"} - (get-draftsets-handler (get-draftsets-request test-publisher :include :all)) - (str"Expected publisher to see owned and publishing draftsets")) - - (assert-visibility #{"editing" "publishing" "admining"} - (get-draftsets-handler (get-draftsets-request test-editor :include :all)) - (str "Expected editor to see editing, publishing & admining draftsets")) - - (assert-visibility #{"publishing" "admining"} - (get-draftsets-handler (get-draftsets-request test-manager :include :all)) - (str "Expected manager to see publishing and admining draftsets")))) + (assert-visibility + #{"owned" "publishing" "shared-with-manager"} + (get-draftsets-handler (get-draftsets-request test-publisher :include :all)) + (str"Expected publisher to see owned and publishing draftsets")) + + (assert-visibility + #{"editing" "publishing" "admining"} + (get-draftsets-handler (get-draftsets-request test-editor :include :all)) + (str "Expected editor to see editing, publishing & admining draftsets")) + + (assert-visibility + #{"publishing" "admining" "shared-with-manager"} + (get-draftsets-handler (get-draftsets-request test-manager :include :all)) + (str "Expected manager to see publishing and admining draftsets")))) (t/deftest get-draftsets-union-with-live-test (tc/with-system diff --git a/drafter/test/drafter/feature/draftset/share_test.clj b/drafter/test/drafter/feature/draftset/share_test.clj new file mode 100644 index 000000000..505d08b6c --- /dev/null +++ b/drafter/test/drafter/feature/draftset/share_test.clj @@ -0,0 +1,178 @@ +(ns ^:rest-api drafter.feature.draftset.share-test + (:require + [clojure.test :as t :refer [is]] + [drafter.draftset :as ds] + [drafter.draftset.spec :as dss] + [drafter.feature.draftset.query-test :as query] + [drafter.feature.draftset.test-helper :as help] + [drafter.test-common :as tc] + [drafter.user :as user] + [drafter.user-test :refer [test-editor test-manager test-publisher]])) + +(def keys-for-test [:drafter.fixture-data/loader [:drafter/routes :draftset/api]]) + +(tc/deftest-system-with-keys share-non-existent-draftset-with-permission + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (tc/assert-is-not-found-response + (handler (help/create-share-with-permission-request + test-editor "/v1/draftset/missing" :drafter:draft:view)))) + +(tc/deftest-system-with-keys share-draftset-with-permission-by-non-owner + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (tc/assert-is-forbidden-response + (handler (help/create-share-with-permission-request + test-publisher + (help/create-draftset-through-api handler test-editor) + :drafter:draft:view)))) + +(tc/deftest-system-with-keys share-draftset-with-user + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (let [{body :body :as share-response} + (handler (help/share-draftset-with-user-request + (help/create-draftset-through-api handler test-editor) + test-publisher + test-editor))] + (tc/assert-is-ok-response share-response) + (tc/assert-spec ::ds/Draftset body) + ;; Current owner doesn't change when sharing + (is (= (user/username test-editor) (:current-owner body))) + (is (= #{(user/username test-publisher)} (:view-users body))))) + +(tc/deftest-system-with-keys share-draftset-with-user-as-non-owner + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (tc/assert-is-forbidden-response + (handler + (help/share-draftset-with-user-request + (help/create-draftset-through-api handler test-editor) + test-manager + test-publisher)))) + +(tc/deftest-system-with-keys share-non-existent-draftset-with-user + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (tc/assert-is-not-found-response + (handler (help/share-draftset-with-user-request + "/v1/draftset/missing" test-publisher test-editor)))) + +(tc/deftest-system-with-keys share-draftset-with-non-existent-user + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (tc/assert-is-unprocessable-response + (handler (help/share-draftset-with-username-request + (help/create-draftset-through-api handler test-editor) + "invalid-user@example.com" + test-editor)))) + +(tc/deftest-system-with-keys share-draftset-without-user-param + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (let [draftset-location (help/create-draftset-through-api handler test-editor) + share-request (help/share-draftset-with-user-request + draftset-location test-publisher test-editor) + share-request (update-in share-request [:params] dissoc :user)] + (tc/assert-is-unprocessable-response (handler share-request)))) + +(tc/deftest-system-with-keys share-with-with-both-user-and-permission-params + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (let [draftset-location (help/create-draftset-through-api handler test-editor) + request (help/share-draftset-with-user-request + draftset-location test-publisher test-editor) + request (assoc-in request [:params :permission] "drafter:draft:view")] + (tc/assert-is-unprocessable-response (handler request)))) + +(tc/deftest-system-with-keys share-draftset-with-permission + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (let [{body :body :as share-response} + (handler + (help/create-share-with-permission-request + test-editor + (help/create-draftset-through-api handler test-editor) + :drafter:draft:view))] + (tc/assert-is-ok-response share-response) + (tc/assert-spec ::ds/Draftset body) + ;; Current owner doesn't change when sharing + (is (= (user/username test-editor) (:current-owner body))) + (is (= #{:drafter:draft:view} (:view-permissions body))))) + +(tc/deftest-system-with-keys share-draftset-with-multiple-users-and-permissions + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (let [draftset (help/create-draftset-through-api handler test-manager)] + (tc/assert-is-ok-response + (handler (help/create-share-with-permission-request + test-manager draftset :drafter:draft:view))) + (tc/assert-is-ok-response + (handler (help/create-share-with-permission-request + test-manager draftset :drafter:draft:view:special))) + (tc/assert-is-ok-response + (handler (help/share-draftset-with-user-request + draftset test-publisher test-manager))) + (let [res (handler (help/share-draftset-with-user-request + draftset test-editor test-manager))] + (tc/assert-is-ok-response res) + (is (= #{:drafter:draft:view :drafter:draft:view:special} + (:view-permissions (:body res))) + (is (= #{"publisher@swirrl.com" "editor@swirrl.com"} + (:view-users (:body res)))))))) + +(tc/deftest-system-with-keys query-draftset-shared-with-user + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (let [draftset (help/create-draftset-through-api handler test-manager)] + (tc/assert-is-ok-response + (handler (help/share-draftset-with-user-request draftset + test-editor + test-manager))) + (tc/assert-is-ok-response + (handler (help/get-draftset-quads-request draftset test-editor :nq "true"))) + (tc/assert-is-ok-response + (handler + (query/create-query-request test-editor + draftset + "select * where { ?s ?p ?o }" + "application/sparql-results+json" + :union-with-live? "true"))))) + +(tc/deftest-system-with-keys query-draftset-shared-with-permission + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (let [draftset (help/create-draftset-through-api handler test-manager)] + (tc/assert-is-ok-response + (handler (help/create-share-with-permission-request + test-manager draftset :drafter:draft:view))) + (tc/assert-is-ok-response + (handler (help/get-draftset-quads-request draftset test-editor :nq "true"))) + (tc/assert-is-ok-response + (handler + (query/create-query-request test-editor + draftset + "select * where { ?s ?p ?o }" + "application/sparql-results+json" + :union-with-live? "true"))))) + +(tc/deftest-system-with-keys unshare-draftset + keys-for-test + [{handler [:drafter/routes :draftset/api]} "test-system.edn"] + (let [draftset (help/create-draftset-through-api handler test-manager)] + (tc/assert-is-ok-response + (handler (help/create-share-with-permission-request + test-manager draftset :drafter:draft:view))) + (tc/assert-is-ok-response + (handler (help/create-share-with-permission-request + test-manager draftset :drafter:draft:view:special))) + (tc/assert-is-ok-response + (handler (help/share-draftset-with-user-request + draftset test-publisher test-manager))) + (tc/assert-is-ok-response + (handler (help/share-draftset-with-user-request + draftset test-editor test-manager))) + (let [res (handler (help/unshare-draftset-request draftset test-manager))] + (tc/assert-is-ok-response res) + (is (nil? (:view-permissions (:body res))) + (is (nil? (:view-users (:body res)))))))) diff --git a/drafter/test/drafter/feature/draftset/test_helper.clj b/drafter/test/drafter/feature/draftset/test_helper.clj index 6a6bf0233..70554123a 100644 --- a/drafter/test/drafter/feature/draftset/test_helper.clj +++ b/drafter/test/drafter/feature/draftset/test_helper.clj @@ -42,6 +42,11 @@ :request-method :post :params {:role (name role)}})) +(defn create-share-with-permission-request [user draftset-location permission] + (tc/with-identity user {:uri (str draftset-location "/share") + :request-method :post + :params {:permission (name permission)}})) + (defn create-draftset-through-api ([handler] (create-draftset-through-api handler test-editor)) ([handler user] (create-draftset-through-api handler user nil)) @@ -60,6 +65,19 @@ (defn submit-draftset-to-user-request [draftset-location target-user user] (submit-draftset-to-username-request draftset-location (user/username target-user) user)) +(defn share-draftset-with-username-request [draftset-location target-username user] + (tc/with-identity user {:uri (str draftset-location "/share") + :request-method :post + :params {:user target-username}})) + +(defn share-draftset-with-user-request [draftset-location target-user user] + (share-draftset-with-username-request + draftset-location (user/username target-user) user)) + +(defn unshare-draftset-request [draftset-location user] + (tc/with-identity user {:uri (str draftset-location "/share") + :request-method :delete})) + (defn submit-draftset-to-user-through-api [handler draftset-location target-user user] (let [request (submit-draftset-to-user-request draftset-location target-user user) response (handler request)] diff --git a/drafter/test/drafter/middleware_test.clj b/drafter/test/drafter/middleware_test.clj index 886b5a340..b3ec7118b 100644 --- a/drafter/test/drafter/middleware_test.clj +++ b/drafter/test/drafter/middleware_test.clj @@ -209,14 +209,14 @@ (t/is (nil? (sut/authenticate-request auth-methods request))))) (t/testing "Authentication succeeds" - (let [user (user/create-authenticated-user "test@example.com" #{:draft:view}) + (let [user (user/create-authenticated-user "test@example.com" #{:drafter:draft:view}) auth-methods [(succeeds-with-auth-method user)] request {:uri "/test" :request-method :get}] (t/is (= user (sut/authenticate-request auth-methods request))))) (t/testing "Authenticates with first matching handler" - (let [user1 (user/create-authenticated-user "test1@example.com" #{:draft:view}) - user2 (user/create-authenticated-user "test2@example.com" #{:draft:view}) + (let [user1 (user/create-authenticated-user "test1@example.com" #{:drafter:draft:view}) + user2 (user/create-authenticated-user "test2@example.com" #{:drafter:draft:view}) auth-methods [(succeeds-with-auth-method user1) (succeeds-with-auth-method user2)] request {:uri "/test" :request-method :get}] @@ -245,13 +245,13 @@ (t/testing "Allows requests with existing identity" (let [auth-methods [always-fails-auth-method] handler (sut/wrap-authenticate identity auth-methods) - user (user/create-authenticated-user "test@example.com" #{:draft:view}) + user (user/create-authenticated-user "test@example.com" #{:drafter:draft:view}) request {:uri "/test" :request-method :get :identity user} response (handler request)] (t/is (= request response)))) (t/testing "Authenticates user" - (let [user (user/create-authenticated-user "test@example.com" #{:draft:view}) + (let [user (user/create-authenticated-user "test@example.com" #{:drafter:draft:view}) auth-methods [(succeeds-with-auth-method user)] handler (sut/wrap-authenticate identity auth-methods) request {:uri "/test" :request-method :get} diff --git a/drafter/test/resources/drafter/feature/draftset/list_test-2.trig b/drafter/test/resources/drafter/feature/draftset/list_test-2.trig index 31138f5cf..dafaa33f0 100644 --- a/drafter/test/resources/drafter/feature/draftset/list_test-2.trig +++ b/drafter/test/resources/drafter/feature/draftset/list_test-2.trig @@ -20,6 +20,15 @@ draftset:35d0ddb6-e8eb-4672-aab4-a3f53d14c6fe a drafter:DraftSet ; drafter:hasOwner ; rdfs:label "owned" . +draftset:8cca48a4-b626-46b5-b581-4326ecf144d5 a drafter:DraftSet ; + dcterms:modified "2018-01-16T12:31:23.516Z"^^xsd:dateTime ; + drafter:version version:af5b52d4-1c9a-4415-9aa8-08a448eae564 ; + dcterms:created "2018-01-16T12:31:23.516Z"^^xsd:dateTime ; + dcterms:creator ; + drafter:hasOwner ; + drafter:viewUser ; + rdfs:label "shared-with-manager" . + draftset:32159abf-d2ca-4f8b-8cc0-9609704c2553 a drafter:DraftSet ; dcterms:modified "2018-01-16T12:31:30.865Z"^^xsd:dateTime ; drafter:version version:d5f3935d-56b6-4d91-8f4f-9ea59d7fa0b0 ; diff --git a/drafter/test/resources/web.edn b/drafter/test/resources/web.edn index 5251cb831..286ad054f 100644 --- a/drafter/test/resources/web.edn +++ b/drafter/test/resources/web.edn @@ -43,6 +43,10 @@ {:drafter/backend #ig/ref :drafter/backend :wrap-authenticate #ig/ref :drafter.middleware/wrap-authenticate} + :drafter.feature.middleware/wrap-as-draftset-viewer + {:drafter/backend #ig/ref :drafter/backend + :wrap-authenticate #ig/ref :drafter.middleware/wrap-authenticate} + :drafter.feature.draftset.delete/handler {:drafter/backend #ig/ref :drafter/backend :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} @@ -50,9 +54,10 @@ {:drafter/backend #ig/ref :drafter/backend :wrap-authenticate #ig/ref :drafter.middleware/wrap-authenticate} - :drafter.feature.draftset-data.show/handler {:drafter/backend #ig/ref :drafter/backend - :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner - :timeout-fn #ig/ref [:drafter.timeouts/timeout-query :drafter/draftset-timeout]} + :drafter.feature.draftset-data.show/handler + {:drafter/backend #ig/ref :drafter/backend + :wrap-as-draftset-viewer #ig/ref :drafter.feature.middleware/wrap-as-draftset-viewer + :timeout-fn #ig/ref [:drafter.timeouts/timeout-query :drafter/draftset-timeout]} :drafter.feature.draftset-data.delete/delete-data-handler {:drafter/manager #ig/ref :drafter/manager :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} @@ -78,9 +83,10 @@ :drafter.feature.draftset-data.append-by-graph/handler {:drafter/manager #ig/ref :drafter/manager :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} - :drafter.feature.draftset.query/handler {:drafter/backend #ig/ref :drafter/backend - :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner - :timeout-fn #ig/ref [:drafter.timeouts/timeout-query :drafter/draftset-timeout]} + :drafter.feature.draftset.query/handler + {:drafter/backend #ig/ref :drafter/backend + :wrap-as-draftset-viewer #ig/ref :drafter.feature.middleware/wrap-as-draftset-viewer + :timeout-fn #ig/ref [:drafter.timeouts/timeout-query :drafter/draftset-timeout]} :drafter.feature.draftset.update/handler {:drafter/manager #ig/ref :drafter/manager :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner @@ -96,6 +102,16 @@ :drafter.user/repo #ig/ref :drafter.user/repo :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} + :drafter.feature.draftset.share/post + {:drafter/manager #ig/ref :drafter/manager + :drafter.user/repo #ig/ref :drafter.user/repo + :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} + + :drafter.feature.draftset.share/delete + {:drafter/manager #ig/ref :drafter/manager + :drafter.user/repo #ig/ref :drafter.user/repo + :wrap-as-draftset-owner #ig/ref :drafter.feature.middleware/wrap-as-draftset-owner} + :drafter.feature.draftset.claim/handler {:drafter/manager #ig/ref :drafter/manager :wrap-authenticate #ig/ref :drafter.middleware/wrap-authenticate} @@ -109,37 +125,38 @@ {:drafter/backend #ig/ref :drafter/backend :wrap-authenticate #ig/ref :drafter.middleware/wrap-authenticate} - [:drafter/routes :draftset/api] {:context "/v1" - :routes [[:get "/users" #ig/ref :drafter.feature.users.list/get-users-handler] - - [:get "/draftsets" #ig/ref :drafter.feature.draftset.list/get-draftsets-handler] - [:post "/draftsets" #ig/ref :drafter.feature.draftset.create/handler] + [:drafter/routes :draftset/api] + {:context "/v1" + :routes + [[:get "/users" #ig/ref :drafter.feature.users.list/get-users-handler] - [:get "/draftset/:id" #ig/ref :drafter.feature.draftset.show/handler] - [:delete "/draftset/:id" #ig/ref :drafter.feature.draftset.delete/handler] - [:options "/draftset/:id" #ig/ref :drafter.feature.draftset.options/handler] + [:get "/draftsets" #ig/ref :drafter.feature.draftset.list/get-draftsets-handler] + [:post "/draftsets" #ig/ref :drafter.feature.draftset.create/handler] - [:get "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.show/handler] - [:delete "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.delete/delete-data-handler] + [:get "/draftset/:id" #ig/ref :drafter.feature.draftset.show/handler] + [:delete "/draftset/:id" #ig/ref :drafter.feature.draftset.delete/handler] + [:options "/draftset/:id" #ig/ref :drafter.feature.draftset.options/handler] - [:delete "/draftset/:id/graph" #ig/ref :drafter.feature.draftset-data.delete-by-graph/remove-graph-handler] - [:delete "/draftset/:id/changes" #ig/ref :drafter.feature.draftset.changes/delete-changes-handler] + [:get "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.show/handler] + [:delete "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.delete/delete-data-handler] + [:delete "/draftset/:id/graph" #ig/ref :drafter.feature.draftset-data.delete-by-graph/remove-graph-handler] + [:delete "/draftset/:id/changes" #ig/ref :drafter.feature.draftset.changes/delete-changes-handler] - [:put "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.append/data-handler] - [:put "/draftset/:id/graph" #ig/ref :drafter.feature.draftset-data.append-by-graph/handler] - [nil "/draftset/:id/query" #ig/ref :drafter.feature.draftset.query/handler] - [:post "/draftset/:id/update" #ig/ref :drafter.feature.draftset.update/handler] + [:put "/draftset/:id/data" #ig/ref :drafter.feature.draftset-data.append/data-handler] + [:put "/draftset/:id/graph" #ig/ref :drafter.feature.draftset-data.append-by-graph/handler] + [nil "/draftset/:id/query" #ig/ref :drafter.feature.draftset.query/handler] + [:post "/draftset/:id/update" #ig/ref :drafter.feature.draftset.update/handler] - [:post "/draftset/:id/publish" #ig/ref :drafter.feature.draftset.publish/handler] - [:put "/draftset/:id" #ig/ref :drafter.feature.draftset.set-metadata/handler] - [:post "/draftset/:id/submit-to" #ig/ref :drafter.feature.draftset.submit/handler] - [:put "/draftset/:id/claim" #ig/ref :drafter.feature.draftset.claim/handler] + [:post "/draftset/:id/publish" #ig/ref :drafter.feature.draftset.publish/handler] + [:put "/draftset/:id" #ig/ref :drafter.feature.draftset.set-metadata/handler] + [:post "/draftset/:id/submit-to" #ig/ref :drafter.feature.draftset.submit/handler] + [:put "/draftset/:id/claim" #ig/ref :drafter.feature.draftset.claim/handler] + [:post "/draftset/:id/share" #ig/ref :drafter.feature.draftset.share/post] + [:delete "/draftset/:id/share" #ig/ref :drafter.feature.draftset.share/delete] - [:get "/endpoint/public" #ig/ref :drafter.feature.endpoint.show/handler] - [:get "/endpoints" #ig/ref :drafter.feature.endpoint.list/handler] - ] - } + [:get "/endpoint/public" #ig/ref :drafter.feature.endpoint.show/handler] + [:get "/endpoints" #ig/ref :drafter.feature.endpoint.list/handler]]} ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;