diff --git a/cimi/src/com/sixsq/slipstream/ssclj/resources/common/schema.clj b/cimi/src/com/sixsq/slipstream/ssclj/resources/common/schema.clj index 5c7f0c264..c683dd289 100644 --- a/cimi/src/com/sixsq/slipstream/ssclj/resources/common/schema.clj +++ b/cimi/src/com/sixsq/slipstream/ssclj/resources/common/schema.clj @@ -31,6 +31,7 @@ :quarantine :upload :ready + :disable :download}) (def ^:const action-uri diff --git a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential.clj b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential.clj index 90feb0c24..1b9c2670e 100644 --- a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential.clj +++ b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential.clj @@ -5,7 +5,8 @@ [com.sixsq.slipstream.ssclj.resources.common.schema :as c] [com.sixsq.slipstream.ssclj.resources.common.std-crud :as std-crud] [com.sixsq.slipstream.ssclj.resources.common.utils :as u] - [com.sixsq.slipstream.ssclj.util.log :as logu])) + [com.sixsq.slipstream.ssclj.util.log :as logu]) + (:import (clojure.lang ExceptionInfo))) (def ^:const resource-tag :credentials) @@ -102,24 +103,6 @@ (def add-impl (std-crud/add-fn resource-name collection-acl resource-uri)) -;; -;; available operations -;; - -;; Use standard method for setting operations. -#_(defmethod crud/set-operations resource-uri - [resource request] - (try - (a/can-modify? resource request) - (let [href (:id resource) - ^String resourceURI (:resourceURI resource) - ops (if (.endsWith resourceURI "Collection") - [{:rel (:add c/action-uri) :href href}] - [{:rel (:delete c/action-uri) :href href}])] - (assoc resource :operations ops)) - (catch Exception e - (dissoc resource :operations)))) - (defn check-connector-exists "Use ADMIN role as we only want to check if href points to an existing resource." @@ -188,3 +171,41 @@ (defn initialize [] (std-crud/initialize resource-url nil)) + +;;; Disable operation + +(defmulti disable-subtype + (fn [resource _] (:type resource))) + +(defmethod disable-subtype :default + [resource _] + (let [err-msg (str "unknown Credential type: " (:type resource))] + (throw (ex-info err-msg {:status 400 + :message err-msg + :body resource})))) + +(defmethod crud/do-action [resource-url "disable"] + [{{uuid :uuid} :params :as request}] + (try + (let [id (str resource-url "/" uuid)] + (-> (crud/retrieve-by-id id {:user-name "INTERNAL" + :user-roles ["ADMIN"]}) + (disable-subtype request))) + (catch ExceptionInfo ei + (ex-data ei)))) + +;;; set subtype operations + +(defmulti set-subtype-ops + (fn [resource _] (:type resource))) + +(defmethod set-subtype-ops :default + [resource request] + (crud/set-standard-operations resource request)) + +(defmethod crud/set-operations resource-uri + [{:keys [id credentialTemplate] :as resource} request] + (let [disable-href (str id "/disable") + disable-op {:rel (:disable c/action-uri) :href disable-href}] + (cond-> (set-subtype-ops resource request) + (:href credentialTemplate) (update-in [:operations] conj disable-op)))) \ No newline at end of file diff --git a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_api_key.clj b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_api_key.clj index b7d6ddbf1..080e3b873 100644 --- a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_api_key.clj +++ b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_api_key.clj @@ -6,7 +6,14 @@ [com.sixsq.slipstream.ssclj.resources.credential :as p] [com.sixsq.slipstream.ssclj.resources.credential-template-api-key :as tpl] [com.sixsq.slipstream.ssclj.resources.credential.key-utils :as key-utils] - [com.sixsq.slipstream.ssclj.resources.spec.credential-api-key])) + [com.sixsq.slipstream.ssclj.resources.spec.credential-api-key] + [clojure.tools.logging :as log] + [com.sixsq.slipstream.ssclj.util.log :as logu] + [com.sixsq.slipstream.db.impl :as db] + [com.sixsq.slipstream.auth.acl :as a] + [com.sixsq.slipstream.ssclj.resources.common.crud :as crud] + [com.sixsq.slipstream.ssclj.resources.common.schema :as c]) + (:import (clojure.lang ExceptionInfo))) (defn strip-session-role [roles] @@ -31,6 +38,7 @@ :type type :method method :digest digest + :enabled true :claims (extract-claims request)} (valid-ttl? ttl) (assoc :expiry (u/ttl->timestamp ttl)))] [{:secretKey secret-key} resource])) @@ -56,3 +64,39 @@ (defn initialize [] (std-crud/initialize p/resource-url :cimi/credential.api-key)) + +;; +;; Disable operation +;; +(defn disable-fn [{enabled :enabled id :id :as credential}] + (if (or enabled (nil? enabled)) + (do + (log/warn "Disabling credential : " id) + (assoc credential :enabled false)) + (logu/log-and-throw-400 (str "Bad enabled field value " enabled)))) + + +(defmethod p/disable-subtype tpl/credential-type + [_ {{uuid :uuid} :params :as request}] + (try + (let [id (str (u/de-camelcase p/resource-name) "/" uuid)] + (-> (db/retrieve id request) + (a/can-modify? request) + (disable-fn) + (db/edit request))) + (catch ExceptionInfo ei + (ex-data ei)))) + + +;; Set operation +(def set-subtype-ops-fn + (fn [{:keys [id] :as resource} request] + (let [ + href-disable (str id "/disable") + disable-op {:rel (:disable c/action-uri) :href href-disable}] + (-> (crud/set-standard-operations resource request) + (update-in [:operations] conj disable-op))))) + +(defmethod p/set-subtype-ops tpl/credential-type + [resource request] + (set-subtype-ops-fn resource request)) diff --git a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_ssh_public_key.clj b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_ssh_public_key.clj index 1bc40082b..e88acd608 100644 --- a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_ssh_public_key.clj +++ b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_ssh_public_key.clj @@ -6,7 +6,13 @@ [com.sixsq.slipstream.ssclj.resources.credential-template-ssh-public-key :as tpl] [com.sixsq.slipstream.ssclj.resources.credential.ssh-utils :as ssh-utils] [com.sixsq.slipstream.ssclj.resources.spec.credential-ssh-public-key] - [com.sixsq.slipstream.ssclj.util.log :as logu])) + [com.sixsq.slipstream.ssclj.util.log :as logu] + [com.sixsq.slipstream.db.impl :as db] + [com.sixsq.slipstream.auth.acl :as a] + [clojure.tools.logging :as log] + [com.sixsq.slipstream.ssclj.resources.common.crud :as crud] + [com.sixsq.slipstream.ssclj.resources.common.schema :as c]) + (:import (clojure.lang ExceptionInfo))) (defn import-key [common-info publicKey] [nil (merge (ssh-utils/load publicKey) common-info)]) @@ -23,7 +29,8 @@ [{:keys [type method publicKey algorithm size]} request] (let [common-info {:resourceURI p/resource-uri :type type - :method method}] + :method method + :enabled true}] (try (if publicKey (import-key common-info publicKey) @@ -52,3 +59,40 @@ (defn initialize [] (std-crud/initialize p/resource-url :cimi/credential.ssh-public-key)) + +;; +;; Disable operation +;; +(defn disable-fn [{enabled :enabled id :id :as credential}] + (if (or enabled (nil? enabled)) + (do + (log/warn "Disabling credential : " id) + (assoc credential :enabled false)) + (logu/log-and-throw-400 (str "Bad enabled field value " enabled)))) + + +(defmethod p/disable-subtype tpl/credential-type + [_ {{uuid :uuid} :params :as request}] + (try + (let [id (str (u/de-camelcase p/resource-name) "/" uuid)] + (-> (db/retrieve id request) + (a/can-modify? request) + (disable-fn) + (db/edit request))) + (catch ExceptionInfo ei + (ex-data ei)))) + + +;; Set operation +(def set-subtype-ops-fn + (fn [{:keys [id] :as resource} request] + (let [ + href-disable (str id "/disable") + disable-op {:rel (:disable c/action-uri) :href href-disable}] + (-> (crud/set-standard-operations resource request) + (update-in [:operations] conj disable-op))))) + +(defmethod p/set-subtype-ops tpl/credential-type + [resource request] + (set-subtype-ops-fn resource request)) + diff --git a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template.clj b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template.clj index 4c4b600ac..373b10e30 100644 --- a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template.clj +++ b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template.clj @@ -102,6 +102,13 @@ :type "string" :mandatory true :readOnly true + :order 11} + :enabled {:displayName "Credential availability flag" + :category "general" + :description "true if credential can be used" + :type "boolean" + :mandatory false + :readOnly false :order 11}})) ;; ;; multimethods for validation diff --git a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_api_key.clj b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_api_key.clj index 6ff6ba111..1ffd26a59 100644 --- a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_api_key.clj +++ b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_api_key.clj @@ -23,6 +23,7 @@ :name "Generate API Key" :description "generates an API key and stores hash" :ttl 0 + :enabled true :acl resource-acl}) ;; diff --git a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_ssh_key_pair.clj b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_ssh_key_pair.clj index a5bbf2c1b..fa0062375 100644 --- a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_ssh_key_pair.clj +++ b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_ssh_key_pair.clj @@ -25,7 +25,9 @@ :description "public key of a generated SSH key pair" :size 1024 :algorithm "rsa" - :acl resource-acl}) + :acl resource-acl + :enabled true + }) ;; ;; description diff --git a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_ssh_public_key.clj b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_ssh_public_key.clj index 08429bc2d..511fa3c26 100644 --- a/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_ssh_public_key.clj +++ b/cimi/src/com/sixsq/slipstream/ssclj/resources/credential_template_ssh_public_key.clj @@ -24,7 +24,9 @@ :name "Import SSH Public Key" :description "import public key of an existing SSH key pair" :publicKey "ssh-public-key" - :acl resource-acl}) + :acl resource-acl + :enabled true + }) ;; ;; description diff --git a/cimi/src/com/sixsq/slipstream/ssclj/resources/spec/credential.cljc b/cimi/src/com/sixsq/slipstream/ssclj/resources/spec/credential.cljc index c0fcbc090..c6f07c9dc 100644 --- a/cimi/src/com/sixsq/slipstream/ssclj/resources/spec/credential.cljc +++ b/cimi/src/com/sixsq/slipstream/ssclj/resources/spec/credential.cljc @@ -11,4 +11,5 @@ (def credential-keys-spec (su/merge-keys-specs [c/common-attrs {:req-un [:cimi.credential/type - :cimi.credential/method]}])) + :cimi.credential/method] + :opt-un [:cimi.credential-template/enabled]}])) diff --git a/cimi/src/com/sixsq/slipstream/ssclj/resources/spec/credential_template.cljc b/cimi/src/com/sixsq/slipstream/ssclj/resources/spec/credential_template.cljc index 366142d3c..8618202c4 100644 --- a/cimi/src/com/sixsq/slipstream/ssclj/resources/spec/credential_template.cljc +++ b/cimi/src/com/sixsq/slipstream/ssclj/resources/spec/credential_template.cljc @@ -14,6 +14,8 @@ ;; credential templates must provide a method name. (s/def :cimi.credential-template/method ::cimi-core/identifier) +(s/def :cimi.credential-template/enabled boolean?) + (def credential-template-regex #"^credential-template/[a-zA-Z0-9]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$") (s/def :cimi.credential-template/href (s/and string? #(re-matches credential-template-regex %))) @@ -24,10 +26,8 @@ ;; (def credential-template-keys-spec {:req-un [:cimi.credential-template/type - :cimi.credential-template/method]}) - -(def credential-template-keys-spec-opt {:opt-un [:cimi.credential-template/type - :cimi.credential-template/method]}) + :cimi.credential-template/method] + :opt-un [:cimi.credential-template/enabled]}) (def resource-keys-spec (su/merge-keys-specs [c/common-attrs @@ -39,5 +39,5 @@ ;; subclasses MUST provide the href to the template to use (def template-keys-spec (su/merge-keys-specs [c/template-attrs - credential-template-keys-spec-opt])) + credential-template-keys-spec])) diff --git a/cimi/test/com/sixsq/slipstream/ssclj/resources/credential_api_key_lifecycle_test.clj b/cimi/test/com/sixsq/slipstream/ssclj/resources/credential_api_key_lifecycle_test.clj index 1e204e6d5..b06354529 100644 --- a/cimi/test/com/sixsq/slipstream/ssclj/resources/credential_api_key_lifecycle_test.clj +++ b/cimi/test/com/sixsq/slipstream/ssclj/resources/credential_api_key_lifecycle_test.clj @@ -15,7 +15,15 @@ (use-fixtures :each ltu/with-test-server-fixture) -(def base-uri (str p/service-context (u/de-camelcase credential/resource-url))) +(def base-uri (str p/service-context (u/de-camelcase credential/resource-name))) + +(def session-anon (-> (ltu/ring-app) + session + (content-type "application/json"))) +(def session-admin (header session-anon authn-info-header "root ADMIN")) +(def session-user (header session-anon authn-info-header "jane USER ANON")) + +(def template-url (str p/service-context ct/resource-url "/" akey/credential-type)) (deftest check-strip-session-role @@ -23,14 +31,7 @@ (is (= [] (t/strip-session-role ["session/2d273461-2778-4a66-9017-668f6fed43ae"])))) (deftest lifecycle - (let [session (-> (ltu/ring-app) - session - (content-type "application/json")) - session-admin (header session authn-info-header "root ADMIN USER ANON") - session-user (header session authn-info-header "jane USER ANON") - session-anon (header session authn-info-header "unknown ANON") - - name-attr "name" + (let [name-attr "name" description-attr "description" properties-attr {:a "one", :b "two"} @@ -45,6 +46,7 @@ create-import-no-href {:credentialTemplate (ltu/strip-unwanted-attrs template)} + create-import-href {:name name-attr :description description-attr :properties properties-attr @@ -172,16 +174,17 @@ (ltu/is-operation-present "edit"))) ;; ensure credential contains correct information - (let [{:keys [digest expiry claims]} (-> session-user - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - :response - :body)] + (let [{:keys [digest expiry claims enabled]} (-> session-user + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + :response + :body)] (is digest) (is (key-utils/valid? secret-key digest)) (is (nil? expiry)) - (is claims)) + (is claims) + (is enabled)) ;; delete the credential (-> session-user @@ -257,4 +260,60 @@ (ltu/body->edn) (ltu/is-status 200))))) +(defn get-valid-create-tmpl + [] + {:name "name" + :description "description" + :properties {:a "one", :b "two"} + :credentialTemplate {:href (str ct/resource-url "/" akey/method) + :ttl 1000}}) + +(deftest disable-operation + "Check the disable operation" + (let [valid-create (get-valid-create-tmpl) + uri (-> session-admin + (request base-uri + :request-method :post + :body (json/write-str valid-create)) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location)) + abs-uri (str p/service-context (u/de-camelcase uri)) + disable-op (-> session-admin + (request abs-uri) + (ltu/body->edn) + (ltu/is-operation-present "disable") + (ltu/is-status 200) + (ltu/get-op "disable")) + abs-disable-uri (str p/service-context (u/de-camelcase disable-op))] + + ;; can not disable credential as anon + (-> session-anon + (request abs-disable-uri + :request-method :post) + (ltu/body->edn) + (ltu/is-status 403)) + + ;can not disable credential as user + (-> session-user + (request abs-disable-uri + :request-method :post) + (ltu/body->edn) + (ltu/is-status 403)) + + ;enabled should now be set to false + (-> session-admin + (request abs-disable-uri + :request-method :post) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-key-value :enabled false)) + + ;disabling an already disabled should not be possible + (-> session-admin + (request abs-disable-uri + :request-method :post) + (ltu/body->edn) + (ltu/is-status 400)))) + diff --git a/cimi/test/com/sixsq/slipstream/ssclj/resources/credential_ssh_public_key_lifecycle_test.clj b/cimi/test/com/sixsq/slipstream/ssclj/resources/credential_ssh_public_key_lifecycle_test.clj index e647c06b0..40607ae51 100644 --- a/cimi/test/com/sixsq/slipstream/ssclj/resources/credential_ssh_public_key_lifecycle_test.clj +++ b/cimi/test/com/sixsq/slipstream/ssclj/resources/credential_ssh_public_key_lifecycle_test.clj @@ -17,17 +17,17 @@ (use-fixtures :each ltu/with-test-server-fixture) (def base-uri (str p/service-context (u/de-camelcase credential/resource-url))) +(def session-anon (-> (ltu/ring-app) + session + (content-type "application/json"))) +(def session-admin (header session-anon authn-info-header "root ADMIN")) +(def session-user (header session-anon authn-info-header "jane USER ANON")) + +(def template-url (str p/service-context ct/resource-url "/" spk/credential-type)) (deftest lifecycle-import - (let [session (-> (ltu/ring-app) - session - (content-type "application/json")) - session-admin (header session authn-info-header "root ADMIN USER ANON") - session-user (header session authn-info-header "jane USER ANON") - session-anon (header session authn-info-header "unknown ANON") - - href (str ct/resource-url "/" spk/method) + (let [href (str ct/resource-url "/" spk/method) template-url (str p/service-context ct/resource-url "/" spk/method) template (-> session-admin @@ -96,6 +96,7 @@ :request-method :post :body (json/write-str create-import-href)) (ltu/body->edn) + (ltu/is-status 201)) id (get-in resp [:response :body :resource-id]) uri (-> resp @@ -122,6 +123,7 @@ :response :body)] (is (= "rsa" (:algorithm resource))) + (is (:enabled resource)) (is (= (:fingerprint resource) (:fingerprint imported-ssh-key-info))) (is (= (:publicKey resource) (:publicKey imported-ssh-key-info)))) @@ -133,14 +135,7 @@ (ltu/is-status 200))))) (deftest lifecycle-generate - (let [session (-> (ltu/ring-app) - session - (content-type "application/json")) - session-admin (header session authn-info-header "root ADMIN USER ANON") - session-user (header session authn-info-header "jane USER ANON") - session-anon (header session authn-info-header "unknown ANON") - - href (str ct/resource-url "/" skp/method) + (let [href (str ct/resource-url "/" skp/method) template-url (str p/service-context ct/resource-url "/" skp/method) template (-> session-admin @@ -216,3 +211,56 @@ :request-method :delete) (ltu/body->edn) (ltu/is-status 200))))) + +(defn get-valid-create-tmpl + [] + {:credentialTemplate {:href (str ct/resource-url "/" spk/method) + :publicKey (:publicKey (ssh-utils/generate))}}) + +(deftest disable-operation + "Check the disable operation" + (let [valid-create (get-valid-create-tmpl) + uri (-> session-admin + (request base-uri + :request-method :post + :body (json/write-str valid-create)) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location)) + abs-uri (str p/service-context (u/de-camelcase uri)) + disable-op (-> session-admin + (request abs-uri) + (ltu/body->edn) + (ltu/is-operation-present "disable") + (ltu/is-status 200) + (ltu/get-op "disable")) + abs-disable-uri (str p/service-context (u/de-camelcase disable-op))] + + ;; can not disable credential as anon + (-> session-anon + (request abs-disable-uri + :request-method :post) + (ltu/body->edn) + (ltu/is-status 403)) + + ;can not disable credential as user + (-> session-user + (request abs-disable-uri + :request-method :post) + (ltu/body->edn) + (ltu/is-status 403)) + + ;enabled should now be set to false + (-> session-admin + (request abs-disable-uri + :request-method :post) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-key-value :enabled false)) + + ;disabling an already disabled should not be possible + (-> session-admin + (request abs-disable-uri + :request-method :post) + (ltu/body->edn) + (ltu/is-status 400)))) diff --git a/cimi/test/com/sixsq/slipstream/ssclj/resources/spec/credential_test.cljc b/cimi/test/com/sixsq/slipstream/ssclj/resources/spec/credential_test.cljc new file mode 100644 index 000000000..5008897c8 --- /dev/null +++ b/cimi/test/com/sixsq/slipstream/ssclj/resources/spec/credential_test.cljc @@ -0,0 +1,31 @@ +(ns com.sixsq.slipstream.ssclj.resources.spec.credential-test + (:require [clojure.test :refer :all] + [com.sixsq.slipstream.ssclj.resources.spec.credential-template :as cs] + [com.sixsq.slipstream.ssclj.util.spec :as su] + [clojure.spec.alpha :as s] + [com.sixsq.slipstream.ssclj.resources.credential :refer :all] + )) + + +(s/def :cimi.test/credential (su/only-keys-maps cs/resource-keys-spec)) + + +(def valid-acl {:owner {:principal "ADMIN" + :type "ROLE"} + :rules [{:type "ROLE", + :principal "ADMIN", + :right "ALL"}]}) + +(deftest test-configuration-schema-check + (let [timestamp "1964-08-25T10:00:00.0Z" + cred {:id (str resource-url "/slipstream") + :resourceURI resource-uri + :created timestamp + :updated timestamp + :acl valid-acl + :type "type" + :method "method" + :enabled true}] + (is (s/valid? :cimi.test/credential cred)) + (doseq [k (into #{} (keys (dissoc cred :enabled)))] + (is (not (s/valid? :cimi.test/credential (dissoc cred k))))))) \ No newline at end of file