From 2281d3dc5691b18543302a641efd3bcd01807ace Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Thu, 17 Aug 2023 17:38:55 +0200 Subject: [PATCH 01/17] Events for session password api-key switch-group --- code/src/sixsq/nuvla/auth/password.clj | 4 + code/src/sixsq/nuvla/server/app/routes.clj | 11 ++ code/src/sixsq/nuvla/server/app/server.clj | 2 + .../sixsq/nuvla/server/middleware/eventer.clj | 17 +++ .../server/resources/common/event_config.clj | 41 +++++ .../server/resources/common/event_context.clj | 34 +++++ .../nuvla/server/resources/common/utils.clj | 24 +++ .../nuvla/server/resources/event/utils.clj | 144 ++++++++++++++++-- .../sixsq/nuvla/server/resources/session.clj | 27 ++++ .../server/resources/session_api_key.clj | 2 + .../nuvla/server/resources/spec/common.cljc | 8 - .../nuvla/server/resources/spec/event.cljc | 75 ++++++++- .../nuvla/server/middleware/eventer_test.clj | 27 ++++ .../resources/common/event_config_test.clj | 26 ++++ .../resources/common/event_context_test.clj | 16 ++ .../nuvla/server/resources/event_test.clj | 17 ++- .../server/resources/event_utils_test.clj | 71 ++++++++- .../server/resources/lifecycle_test_utils.clj | 22 ++- .../session_api_key_lifecycle_test.clj | 7 + .../session_password_lifecycle_test.clj | 14 +- .../server/resources/spec/event_test.cljc | 7 +- 21 files changed, 560 insertions(+), 36 deletions(-) create mode 100644 code/src/sixsq/nuvla/server/middleware/eventer.clj create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config.clj create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_context.clj create mode 100644 code/test/sixsq/nuvla/server/middleware/eventer_test.clj create mode 100644 code/test/sixsq/nuvla/server/resources/common/event_config_test.clj create mode 100644 code/test/sixsq/nuvla/server/resources/common/event_context_test.clj diff --git a/code/src/sixsq/nuvla/auth/password.clj b/code/src/sixsq/nuvla/auth/password.clj index ab734c71d..013aad7c8 100644 --- a/code/src/sixsq/nuvla/auth/password.clj +++ b/code/src/sixsq/nuvla/auth/password.clj @@ -7,6 +7,7 @@ [sixsq.nuvla.db.filter.parser :as parser] [sixsq.nuvla.db.impl :as db] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-context :as ec] [sixsq.nuvla.server.resources.user-identifier :as user-identifier])) @@ -74,6 +75,9 @@ :credential-password credential-id->credential :hash)] + (ec/add-to-context :visible-to [(:id user)]) + (ec/add-linked-identifier (:id user)) + (ec/add-linked-identifier (:credential-password user)) (when (valid-password? password password-hash) user))) diff --git a/code/src/sixsq/nuvla/server/app/routes.clj b/code/src/sixsq/nuvla/server/app/routes.clj index bfc0ae30b..27371b417 100644 --- a/code/src/sixsq/nuvla/server/app/routes.clj +++ b/code/src/sixsq/nuvla/server/app/routes.clj @@ -6,18 +6,23 @@ [sixsq.nuvla.server.app.params :as p] [sixsq.nuvla.server.resources.common.crud :as crud] [sixsq.nuvla.server.resources.common.dynamic-load :as dyn] + [sixsq.nuvla.server.resources.common.event-context :as ec] [sixsq.nuvla.server.util.response :as r])) (def collection-routes (let-routes [uri (str p/service-context ":resource-name")] (POST uri request + (ec/add-to-context :params (:params request)) (crud/add request)) (PUT uri request + (ec/add-to-context :params (:params request)) (crud/query request)) (GET uri request + (ec/add-to-context :params (:params request)) (crud/query request)) (DELETE uri request + (ec/add-to-context :params (:params request)) (crud/bulk-delete request)) (ANY uri request (throw (r/ex-bad-method request))))) @@ -26,10 +31,13 @@ (def resource-routes (let-routes [uri (str p/service-context ":resource-name/:uuid")] (GET uri request + (ec/add-to-context :params (:params request)) (crud/retrieve request)) (PUT uri request + (ec/add-to-context :params (:params request)) (crud/edit request)) (DELETE uri request + (ec/add-to-context :params (:params request)) (crud/delete request)) (ANY uri request (throw (r/ex-bad-method request))))) @@ -38,12 +46,14 @@ (def action-routes (let-routes [uri (str p/service-context ":resource-name/:uuid/:action")] (ANY uri request + (ec/add-to-context :params (:params request)) (crud/do-action request)))) (def bulk-action-routes (let-routes [uri (str p/service-context ":resource-name/:action")] (PATCH uri request + (ec/add-to-context :params (:params request)) (crud/bulk-action request)))) @@ -76,3 +86,4 @@ (route/resources (str p/service-context "static"))] (dyn/resource-routes) final-routes)))) + diff --git a/code/src/sixsq/nuvla/server/app/server.clj b/code/src/sixsq/nuvla/server/app/server.clj index ebb21c479..0ae16d3e2 100644 --- a/code/src/sixsq/nuvla/server/app/server.clj +++ b/code/src/sixsq/nuvla/server/app/server.clj @@ -17,6 +17,7 @@ [sixsq.nuvla.server.middleware.cimi-params :refer [wrap-cimi-params]] [sixsq.nuvla.server.middleware.default-content-type :refer [default-content-type]] [sixsq.nuvla.server.middleware.exception-handler :refer [wrap-exceptions]] + [sixsq.nuvla.server.middleware.eventer :refer [wrap-eventer]] [sixsq.nuvla.server.middleware.gzip :refer [wrap-gzip-uncompress]] [sixsq.nuvla.server.middleware.logger :refer [wrap-logger]] [sixsq.nuvla.server.middleware.redirect-cep :refer [redirect-cep]] @@ -49,6 +50,7 @@ wrap-authn-info (wrap-json-body {:keywords? true}) wrap-gzip-uncompress + wrap-eventer (wrap-json-response {:pretty true :escape-non-ascii true}) (default-content-type "application/json") diff --git a/code/src/sixsq/nuvla/server/middleware/eventer.clj b/code/src/sixsq/nuvla/server/middleware/eventer.clj new file mode 100644 index 000000000..852870c48 --- /dev/null +++ b/code/src/sixsq/nuvla/server/middleware/eventer.clj @@ -0,0 +1,17 @@ +(ns sixsq.nuvla.server.middleware.eventer + (:require [sixsq.nuvla.server.resources.common.event-config :as config] + [sixsq.nuvla.server.resources.common.event-context :as ec] + [sixsq.nuvla.server.resources.event.utils :as eu])) + +(defn wrap-eventer + "Creates an event after handler execution, if eventing is enabled for the resource type." + [handler] + (fn [request] + (ec/with-context + (let [response (handler request) + resource-type (-> (ec/get-context) :params :resource-name)] + (when (config/events-enabled? resource-type) + (let [event (eu/build-event (ec/get-context) request response)] + (when (config/log-event? event response) + (eu/add-event event)))) + response)))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config.clj b/code/src/sixsq/nuvla/server/resources/common/event_config.clj new file mode 100644 index 000000000..3587acae3 --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_config.clj @@ -0,0 +1,41 @@ +(ns sixsq.nuvla.server.resources.common.event-config) + +;; +;; Dispatch functions +;; + +(defn resource-type-dispatch [resource-type] + resource-type) + + +(defn event-type-dispatch [{:keys [event-type] :as _event} _response] + event-type) + + +;; +;; Enabled/disabled events +;; + +(defmulti events-enabled? + "Returns true if events should be logged for the given resource-type, false otherwise." + resource-type-dispatch) + + +(defmethod events-enabled? :default + [_resource-type] + false) + + +;; +;; Whitelist and blacklist event types per resource type +;; + +(defmulti log-event? + "Returns true if the event should be logged, false otherwise." + event-type-dispatch) + + +(defmethod log-event? :default + [{:keys [event-type] :as _event} {:keys [status] :as _response}] + (and (not= 405 status) + (some? event-type))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_context.clj b/code/src/sixsq/nuvla/server/resources/common/event_context.clj new file mode 100644 index 000000000..d095c4168 --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_context.clj @@ -0,0 +1,34 @@ +(ns sixsq.nuvla.server.resources.common.event-context + (:require [sixsq.nuvla.server.util.time :as t])) + + +(def ^:dynamic *context*) + + +(defmacro with-context + "Initializes an event context map. + Information can be added to the context via `add-to-context`. + The context can be retrieved via `get-context`." + [& body] + `(binding [*context* (atom {:timestamp (t/now-str)})] + ~@body)) + + +(defn get-context + "Returns the current context." + [] + (some-> *context* deref)) + + +(defn add-to-context + "Adds value `v` to the current context under key `k`." + [k v] + (when @*context* + (swap! *context* assoc k v))) + + +(defn add-linked-identifier + "Adds the identifier of a linked entity to the context." + [id] + (when (and (some? id) @*context*) + (swap! *context* update :linked-identifiers conj id))) diff --git a/code/src/sixsq/nuvla/server/resources/common/utils.clj b/code/src/sixsq/nuvla/server/resources/common/utils.clj index f085ae4ea..b5bf9b4fe 100644 --- a/code/src/sixsq/nuvla/server/resources/common/utils.clj +++ b/code/src/sixsq/nuvla/server/resources/common/utils.clj @@ -67,6 +67,11 @@ (str resource-name "/" (random-uuid))) +(defn resource-id + [resource-type uuid] + (str resource-type "/" uuid)) + + (defn parse-id "Parses a resource id to provide a tuple of [resource-type uuid] for an id. For ids that don't have an identifier part (e.g. the cloud-entry-point), the @@ -86,6 +91,25 @@ uuid (assoc :uuid uuid)))) +(defn request->resource-type + "Extracts the resource type from a request map." + [request] + (get-in request [:params :resource-name])) + + +(defn request->resource-uuid + "Extracts the resource uuid from a request map." + [request] + (get-in request [:params :uuid])) + + +(defn request->resource-id + "Extracts the resource id from a request map." + [request] + (some->> (request->resource-uuid request) + (resource-id (request->resource-type request)))) + + (defn id->resource-type "Parses a resource id to provide a tuple of [resource-type uuid] for an id. For ids that don't have an identifier part (e.g. the cloud-entry-point), the diff --git a/code/src/sixsq/nuvla/server/resources/event/utils.clj b/code/src/sixsq/nuvla/server/resources/event/utils.clj index c4fb85526..79feb65ca 100644 --- a/code/src/sixsq/nuvla/server/resources/event/utils.clj +++ b/code/src/sixsq/nuvla/server/resources/event/utils.clj @@ -4,40 +4,164 @@ [sixsq.nuvla.auth.utils :as auth] [sixsq.nuvla.db.filter.parser :as parser] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.event :as event] + [sixsq.nuvla.server.util.time :as t] [sixsq.nuvla.server.util.time :as time])) +(defn request-event-type + "Returns a string of the form ." + [{{:keys [resource-name uuid action]} :params :as _context} + {:keys [request-method] :as _request}] + (if uuid + (if action + (some->> action (str resource-name ".")) + (case request-method + :put (str resource-name ".edit") + :delete (str resource-name ".delete") + nil)) + (case request-method + :post (str resource-name ".add") + :delete (str resource-name ".bulk.delete") + :patch (some->> action (str resource-name ".bulk.")) + nil))) + + +(defn get-success + [{:keys [status] :as _response}] + (<= 200 status 399)) + + +(defn get-event-type + [{:keys [event-type] :as context} request] + (or event-type + (request-event-type context request))) + + +(defn get-category + [{:keys [category] :as _context}] + (or category "action")) + + +(defn get-timestamp + [{:keys [timestamp] :as _context}] + (or timestamp (t/now-str))) + + +(defn retrieve-by-id + [id] + (try + (crud/retrieve-by-id-as-admin id) + (catch Exception _ex + nil))) + + +(defn get-acl + [{:keys [visible-to] :as _context}] + (let [visible-to (remove nil? visible-to)] + (or (when (seq visible-to) + {:owners (conj (vec visible-to) "group/nuvla-admin")}) + {:owners ["group/nuvla-admin"]}))) + + +(defn get-severity + [{:keys [severity] :as _context}] + (or severity "medium")) + + +(defn get-resource + [{{:keys [resource-name uuid]} :params :as _context} _request response] + {:href (or (some->> uuid (str resource-name "/")) + (-> response :body :resource-id))}) + + +(defn get-linked-identifiers + [{:keys [linked-identifiers] :or {linked-identifiers []} :as _context}] + linked-identifiers) + + +(defn build-event + [context request response] + {:resource-type event/resource-type + :event-type (get-event-type context request) + :success (get-success response) + :category (get-category context) + :timestamp (get-timestamp context) + :authn-info (auth/current-authentication request) + :acl (get-acl context) + :severity (get-severity context) + :content {:resource (get-resource context request response) + :state "to be removed" + :linked-identifiers (get-linked-identifiers context)}}) + + +(defn add-event + [event] + (let [create-request {:params {:resource-name event/resource-type} + :body event + :nuvla/authn auth/internal-identity}] + (crud/add create-request))) + + (def topic event/resource-type) +;; FIXME: duplicated (defn create-event [resource-href state acl & {:keys [severity category timestamp] - :or {severity "medium" - category "action"}}] - (let [event-map {:resource-type event/resource-type + :or {severity "medium" + category "action"}}] + (let [event-map {:event-type "legacy" + :success true + :resource-type event/resource-type :content {:resource {:href resource-href} :state state} :severity severity :category category :timestamp (or timestamp (time/now-str)) - :acl acl} + :acl acl + :authn-info {}} create-request {:params {:resource-name event/resource-type} :body event-map :nuvla/authn auth/internal-identity}] (crud/add create-request))) +(defn query-events + ([resource-href opts] + (query-events (assoc opts :resource-href resource-href))) + ([{:keys [resource-href event-type linked-identifier category state start end orderby last] :as opts}] + (some-> event/resource-type + (crud/query-as-admin + {:cimi-params + (cond-> + {:filter (parser/parse-cimi-filter + (str/join " and " + (cond-> [] + resource-href (conj (str "content/resource/href='" resource-href "'")) + (and (contains? opts :resource-href) (nil? resource-href)) (conj (str "content/resource/href=null")) + event-type (conj (str "event-type='" event-type "'")) + category (conj (str "category='" category "'")) + state (conj (str "content/state='" state "'")) + linked-identifier (conj (str "content/linked-identifiers='" linked-identifier "'")) + start (conj (str "timestamp>='" start "'")) + end (conj (str "timestamp<'" end "'")))))} + orderby (assoc :orderby orderby) + last (assoc :last last))}) + second))) + +;; FIXME: duplicated (defn search-event [resource-href {:keys [category state start end]}] (some-> event/resource-type (crud/query-as-admin {:cimi-params - {:filter (parser/parse-cimi-filter - (str/join " and " + {:filter (parser/parse-cimi-filter + (str/join " and " (cond-> [(str "content/resource/href='" resource-href "'")] - category (conj (str "category='" category "'")) - state (conj (str "content/state='" state "'")) - start (conj (str "timestamp>='" start "'")) - end (conj (str "timestamp<'" end "'")))))}}) + category (conj (str "category='" category "'")) + state (conj (str "content/state='" state "'")) + start (conj (str "timestamp>='" start "'")) + end (conj (str "timestamp<'" end "'")))))}}) second)) diff --git a/code/src/sixsq/nuvla/server/resources/session.clj b/code/src/sixsq/nuvla/server/resources/session.clj index 3c3d6f38e..ad8f37c36 100644 --- a/code/src/sixsq/nuvla/server/resources/session.clj +++ b/code/src/sixsq/nuvla/server/resources/session.clj @@ -90,6 +90,8 @@ status, a 'set-cookie' header, and a 'location' header with the created [sixsq.nuvla.server.middleware.authn-info :as authn-info] [sixsq.nuvla.server.resources.common.crud :as crud] [sixsq.nuvla.server.resources.common.std-crud :as std-crud] + [sixsq.nuvla.server.resources.common.event-config :as ec] + [sixsq.nuvla.server.resources.common.event-context :as ectx] [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.configuration-nuvla :as config-nuvla] [sixsq.nuvla.server.resources.email :as email] @@ -461,9 +463,16 @@ status, a 'set-cookie' header, and a 'location' header with the created (format "Switch group cannot be done to requested group: %s!" claim) 403)))) +(defn set-event-context + [{{:keys [claim]} :body :as _request}] + (ectx/add-linked-identifier claim) + (ectx/add-to-context :visible-to [claim])) + + (defmethod crud/do-action [resource-type "switch-group"] [{{uuid :uuid} :params :as request}] (try + (set-event-context request) (let [id (str resource-type "/" uuid)] (-> (db/retrieve id request) (a/throw-cannot-edit request) @@ -506,6 +515,24 @@ status, a 'set-cookie' header, and a 'location' header with the created (catch Exception e (or (ex-data e) (throw e))))) +;; +;; Events +;; + +(defmethod ec/events-enabled? resource-type + [_resource-type] + true) + + +(defmethod ec/log-event? "session.get-peers" + [_event _response] + false) + + +(defmethod ec/log-event? "session.get-groups" + [_event _response] + false) + ;; ;; initialization: no schema for this parent resource diff --git a/code/src/sixsq/nuvla/server/resources/session_api_key.clj b/code/src/sixsq/nuvla/server/resources/session_api_key.clj index c9ae785c0..c7fc29a09 100644 --- a/code/src/sixsq/nuvla/server/resources/session_api_key.clj +++ b/code/src/sixsq/nuvla/server/resources/session_api_key.clj @@ -9,6 +9,7 @@ an API key-secret pair. [sixsq.nuvla.auth.utils.timestamp :as ts] [sixsq.nuvla.server.middleware.authn-info :as authn-info] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-context :as ec] [sixsq.nuvla.server.resources.common.std-crud :as std-crud] [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.credential-template-api-key :as api-key-tpl] @@ -77,6 +78,7 @@ an API key-secret pair. (defmethod p/tpl->session authn-method [{:keys [href key secret] :as _resource} {:keys [headers] :as _request}] + (ec/add-linked-identifier (uuid->id key)) (let [{{:keys [identity roles]} :claims :as api-key} (retrieve-credential-by-id key)] (if (valid-api-key? api-key secret) (let [session (sutils/create-session identity identity {:href href} headers authn-method) diff --git a/code/src/sixsq/nuvla/server/resources/spec/common.cljc b/code/src/sixsq/nuvla/server/resources/spec/common.cljc index 60d1ba529..ab20479db 100644 --- a/code/src/sixsq/nuvla/server/resources/spec/common.cljc +++ b/code/src/sixsq/nuvla/server/resources/spec/common.cljc @@ -14,14 +14,6 @@ :json-schema/description "reference to another resource"))) -(s/def ::resource-link - (-> (st/spec (s/keys :req-un [::href])) - (assoc :name "resource-link" - :json-schema/type "map" - :json-schema/display-name "resource link" - :json-schema/description "map containing a reference (href) to a resource"))) - - ;; ;; core meta ;; diff --git a/code/src/sixsq/nuvla/server/resources/spec/event.cljc b/code/src/sixsq/nuvla/server/resources/spec/event.cljc index 0349114d3..ab628e964 100644 --- a/code/src/sixsq/nuvla/server/resources/spec/event.cljc +++ b/code/src/sixsq/nuvla/server/resources/spec/event.cljc @@ -1,12 +1,22 @@ (ns sixsq.nuvla.server.resources.spec.event (:require + [clojure.spec.alpha :as s] [clojure.spec.alpha :as s] [sixsq.nuvla.server.resources.spec.common :as common] [sixsq.nuvla.server.resources.spec.core :as core] + [sixsq.nuvla.server.resources.spec.session :as session] + [sixsq.nuvla.server.resources.spec.user :as user] [sixsq.nuvla.server.util.spec :as su] [spec-tools.core :as st])) +(s/def ::event-type + (-> (st/spec string?) + (assoc :name "event-type" + :json-schema/type "string" + :json-schema/description "type of event"))) + + (s/def ::category (-> (st/spec #{"state" "alarm" "action" "system" "user" "email"}) (assoc :name "category" @@ -46,7 +56,7 @@ ;; Events may need to reference resources that do not follow the CIMI. ;; conventions. Allow for a more flexible schema to be used here. (s/def ::href - (-> (st/spec (s/and string? #(re-matches #"^[a-zA-Z0-9]+[a-zA-Z0-9_./-]*$" %))) + (-> (st/spec (s/nilable (s/and string? #(re-matches #"^[a-zA-Z0-9]+[a-zA-Z0-9_./-]*$" %)))) (assoc :name "href" :json-schema/type "string" :json-schema/description "reference to associated resource"))) @@ -59,8 +69,30 @@ :json-schema/description "link to associated resource"))) +(s/def ::identifier + (-> (st/spec string?) + (assoc :name "event-type" + :json-schema/type "string" + :json-schema/description "type of event"))) + + +(s/def ::identifier + (-> (st/spec string?) + (assoc :name "identifier" + :json-schema/type "string" + :json-schema/description "identifier"))) + + +(s/def ::linked-identifiers + (-> (st/spec (s/coll-of ::identifier)) + (assoc :name "linked-identifiers" + :json-schema/type "array" + :json-schema/description "list of linked identifiers"))) + + (s/def ::content - (-> (st/spec (su/only-keys :req-un [::resource ::state])) + (-> (st/spec (su/only-keys :req-un [::resource ::state] + :opt-un [::linked-identifiers])) (assoc :name "content" :json-schema/type "map" :json-schema/description "content describing event" @@ -68,9 +100,44 @@ :json-schema/order 33))) +(s/def ::user-id + (-> (st/spec ::user/id) + (assoc :name "user-id" + :json-schema/type "string" + :json-schema/description "user id"))) + + +(s/def ::claims + (-> (st/spec (s/coll-of ::core/nonblank-string)) + (assoc :name "claims" + :json-schema/type "array" + :json-schema/description "claims"))) + + +(s/def ::authn-info + (-> (st/spec (su/only-keys :opt-un [::user-id ::session/active-claim ::claims])) + (assoc :name "authn-info" + :json-schema/type "map" + :json-schema/description "authentication info" + + :json-schema/order 34))) + + +(s/def ::success + (-> (st/spec boolean?) + (assoc :name "success" + :json-schema/type "boolean" + :json-schema/description "success" + + :json-schema/order 35))) + + (s/def ::schema (su/only-keys-maps common/common-attrs - {:req-un [::timestamp + {:req-un [::event-type + ::timestamp ::content ::category - ::severity]})) + ::severity + ::authn-info + ::success]})) diff --git a/code/test/sixsq/nuvla/server/middleware/eventer_test.clj b/code/test/sixsq/nuvla/server/middleware/eventer_test.clj new file mode 100644 index 000000000..80594c488 --- /dev/null +++ b/code/test/sixsq/nuvla/server/middleware/eventer_test.clj @@ -0,0 +1,27 @@ +(ns sixsq.nuvla.server.middleware.eventer-test + (:require + [clojure.test :refer [deftest is use-fixtures]] + [peridot.core :refer [content-type request session]] + [sixsq.nuvla.server.app.params :as p] + [sixsq.nuvla.server.resources.lifecycle-test-utils :as ltu])) + + +(use-fixtures :each ltu/with-test-server-fixture) + + +(def test-url (str p/service-context "session")) + + +(deftest event-wrapper + (let [session-anon (-> (ltu/ring-app) + session + (content-type "application/json")) + event-added (atom false)] + + (with-redefs [sixsq.nuvla.server.resources.event.utils/add-event + (fn [_event] (reset! event-added true))] + (-> session-anon + (request test-url :request-method :post) + (ltu/is-status 200)) + (is (true? @event-added))))) + diff --git a/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj new file mode 100644 index 000000000..d1b6397ba --- /dev/null +++ b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj @@ -0,0 +1,26 @@ +(ns sixsq.nuvla.server.resources.common.event-config-test + (:require [clojure.test :refer [deftest is]] + [sixsq.nuvla.server.resources.common.event-config :as t])) + + +(def logged-event {:event-type "resource.add"}) + + +(def not-logged-event {}) + + +(def disabled-event {:event-type "resource.validate"}) + + +(defmethod t/log-event? + "resource.validate" + [_event _response] + false) + + +(deftest log-event + (is (true? (t/log-event? logged-event {}))) + (is (false? (t/log-event? not-logged-event {}))) + (is (false? (t/log-event? disabled-event {}))) + (is (false? (t/log-event? logged-event {:status 405})))) + diff --git a/code/test/sixsq/nuvla/server/resources/common/event_context_test.clj b/code/test/sixsq/nuvla/server/resources/common/event_context_test.clj new file mode 100644 index 000000000..132d2ffe6 --- /dev/null +++ b/code/test/sixsq/nuvla/server/resources/common/event_context_test.clj @@ -0,0 +1,16 @@ +(ns sixsq.nuvla.server.resources.common.event-context-test + (:require [clojure.test :refer [deftest is]] + [sixsq.nuvla.server.resources.common.event-context :as t])) + + +(deftest event-context + (let [k :test-key + info "something" + linked-id "linked-identifier"] + (t/with-context + (is (= [:timestamp] (keys (t/get-context)))) + (t/add-to-context k info) + (is (= info (get (t/get-context) k))) + + (t/add-linked-identifier linked-id) + (is (some #{linked-id} (:linked-identifiers (t/get-context))))))) diff --git a/code/test/sixsq/nuvla/server/resources/event_test.clj b/code/test/sixsq/nuvla/server/resources/event_test.clj index 7337af9b1..0df2336c1 100644 --- a/code/test/sixsq/nuvla/server/resources/event_test.clj +++ b/code/test/sixsq/nuvla/server/resources/event_test.clj @@ -16,13 +16,16 @@ (def ^:private nb-events 20) -(def valid-event {:acl {:owners ["user/joe"]} - :created "2015-01-16T08:05:00.00Z" - :updated "2015-01-16T08:05:00.00Z" - :timestamp "2015-01-16T08:05:00.00Z" - :content {:resource {:href "run/45614147-aed1-4a24-889d-6365b0b1f2cd"} - :state "Started"} - :severity "critical"}) +(def valid-event {:acl {:owners ["user/joe"]} + :authn-info {} + :event-type "legacy" + :success true + :created "2015-01-16T08:05:00.00Z" + :updated "2015-01-16T08:05:00.00Z" + :timestamp "2015-01-16T08:05:00.00Z" + :content {:resource {:href "run/45614147-aed1-4a24-889d-6365b0b1f2cd"} + :state "Started"} + :severity "critical"}) (def valid-events diff --git a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj index c9ae05479..77c839790 100644 --- a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj +++ b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj @@ -1,6 +1,7 @@ (ns sixsq.nuvla.server.resources.event-utils-test (:require - [clojure.test :refer [deftest is use-fixtures]] + [clojure.test :refer [deftest is testing use-fixtures]] + [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.event.utils :as t] [sixsq.nuvla.server.resources.lifecycle-test-utils :as ltu] [sixsq.nuvla.server.util.time :as time])) @@ -8,6 +9,69 @@ (use-fixtures :each ltu/with-test-server-fixture) + +(defn req + [{:keys [nuvla-authn-info method body]}] + {:request-method method + :params {:resource-name "resource" + :uuid "12345"} + :headers {"nuvla-authn-info" nuvla-authn-info} + :body body}) + + +;; TODO: test getters in sixsq.nuvla.server.resources.event.utils + + +(deftest build-event + (with-redefs [time/now-str (constantly "2023-08-17T07:25:57.259Z")] + (let [context {:category "action" + :params {:resource-name "resource"}} + request (req {:nuvla-authn-info "super super group/nuvla-admin" + :method :post + :body {:k "v"}})] + (testing "success" + (let [uuid (u/random-uuid) + id (str "resource/" uuid) + event (t/build-event context request {:status 201 :body {:resource-id id}})] + (is (= {:event-type "resource.add" + :category "action" + :content {:resource {:href id} + :state "to be removed" + :linked-identifiers []} + :authn-info {} + :success true + :severity "medium" + :resource-type "event" + :acl {:owners ["group/nuvla-admin"]} + :timestamp "2023-08-17T07:25:57.259Z"} + event)))) + (testing "failure" + (let [event (t/build-event context request {:status 400})] + (is (= {:event-type "resource.add" + :category "action" + :content {:resource {:href nil} + :state "to be removed" + :linked-identifiers []} + :authn-info {} + :success false + :severity "medium" + :resource-type "event" + :acl {:owners ["group/nuvla-admin"]} + :timestamp "2023-08-17T07:25:57.259Z"} + event))))))) + + +(deftest add-event + (let [context {:category "action" + :params {:resource-name "resource"}} + request (req {:nuvla-authn-info "super super group/nuvla-admin" + :method :post + :body {:k "v"}}) + event (t/build-event context request {:status 200})] + (let [{:keys [status]} (t/add-event event)] + (is (= 201 status))))) + + (deftest search-event (doseq [category ["action" "system"] timestamp ["2015-01-16T08:05:00.000Z" "2015-01-17T08:05:00.000Z" (time/now-str)]] @@ -18,6 +82,7 @@ (is (= 0 (count (t/search-event "user/2" {})))) (is (= 3 (count (t/search-event "user/1" {:category "action"})))) (is (= 6 (count (t/search-event "user/1" {:start "2015-01-16T08:05:00.000Z"})))) - (is (= 2 (count (t/search-event "user/1" {:end "2015-01-16T08:06:00.000Z"})))) + (is (= 2 (count (t/search-event "user/1" {:end "2015-01-16T08:06:00.000Z"})))) (is (= 1 (count (t/search-event "user/1" {:category "action" - :start "now/d" :end "now+1d/d"}))))) + :start "now/d" :end "now+1d/d"}))))) + diff --git a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj index c6f59e828..47e504445 100644 --- a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj +++ b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj @@ -25,8 +25,10 @@ [sixsq.nuvla.server.middleware.base-uri :refer [wrap-base-uri]] [sixsq.nuvla.server.middleware.cimi-params :refer [wrap-cimi-params]] [sixsq.nuvla.server.middleware.exception-handler :refer [wrap-exceptions]] + [sixsq.nuvla.server.middleware.eventer :refer [wrap-eventer]] [sixsq.nuvla.server.middleware.logger :refer [wrap-logger]] [sixsq.nuvla.server.resources.common.dynamic-load :as dyn] + [sixsq.nuvla.server.resources.event.utils :as event-utils] [sixsq.nuvla.server.util.kafka :as ka] [sixsq.nuvla.server.util.zookeeper :as uzk] [zookeeper :as zk]) @@ -472,11 +474,12 @@ wrap-authn-info wrap-exceptions (wrap-json-body {:keywords? true}) + wrap-eventer (wrap-json-response {:pretty true :escape-non-ascii true}) wrap-logger)) -(def ^:private ring-app-cache (atom nil)) +(defonce ^:private ring-app-cache (atom nil)) (defn set-ring-app-cache "Sets the value of the cached ring application. If the current value is nil, @@ -538,7 +541,7 @@ (let [ts (System/currentTimeMillis)] (.close kafka) (log/debug (str "--->: close kafka done in: " - (- (System/currentTimeMillis) ts))) ) + (- (System/currentTimeMillis) ts)))) (ke/delete-dir log-dir)))))) @@ -599,4 +602,19 @@ :body (json/write-str {:dummy "value"})) (is-status 405))))) +;; +;; events +;; +(defmacro is-last-event + [resource-id {:keys [event-type linked-identifiers success acl]}] + `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1}))] + (is (some? event#)) + (when ~event-type + (is (= ~event-type (:event-type event#)))) + (when ~linked-identifiers + (is (= (set ~linked-identifiers) (set (get-in event# [:content :linked-identifiers]))))) + (when (some? ~success) + (is (= ~success (:success event#)))) + (when (some? ~acl) + (is (= ~acl (:acl event#)))))) diff --git a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj index 01512d4d8..bb8116bca 100644 --- a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj @@ -140,6 +140,10 @@ (ltu/body->edn) (ltu/is-status 403)) + (ltu/is-last-event uuid {:event-type "session.add" + :success false + :linked-identifiers [(str "credential/" uuid)]}) + ;; anonymous create must succeed; also with redirect (let [resp (-> session-anon (request base-uri @@ -149,6 +153,9 @@ (ltu/is-set-cookie) (ltu/is-status 201)) id (ltu/body-resource-id resp) + _ (ltu/is-last-event id {:event-type "session.add" + :success true + :linked-identifiers [(str "credential/" uuid)]}) token (get-in resp [:response :cookies authn-cookie :value]) cookie-info (if token (sign/unsign-cookie-info token) {}) diff --git a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj index b4b771b2b..33e9dda58 100644 --- a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj @@ -5,6 +5,7 @@ [clojure.test :refer [deftest is testing use-fixtures]] [peridot.core :refer [content-type header request session]] [postal.core :as postal] + [sixsq.nuvla.auth.password :as auth-password] [sixsq.nuvla.auth.utils :as auth] [sixsq.nuvla.auth.utils.sign :as sign] [sixsq.nuvla.server.app.params :as p] @@ -314,6 +315,12 @@ (ltu/is-status 201)) session-user-id (ltu/body-resource-id session-user) sesssion-user-url (ltu/location-url session-user) + credential-id (:credential-password (auth-password/user-id->user user-id)) + _ (ltu/is-last-event session-user-id + {:event-type "session.add" + :success true + :linked-identifiers [user-id credential-id] + :acl {:owners ["group/nuvla-admin" user-id]}}) handler (wrap-authn-info identity) authn-session-user (-> session-user :response @@ -336,7 +343,12 @@ :request-method :post] authn-session-user)) (ltu/body->edn) (ltu/is-status 403) - (ltu/message-matches #"Switch group cannot be done to requested group:.*"))) + (ltu/message-matches #"Switch group cannot be done to requested group:.*")) + + (ltu/is-last-event session-user-id {:event-type "session.switch-group" + :success false + :linked-identifiers [group-b] + :acl {:owners ["group/nuvla-admin" group-b]}})) (testing "User can switch to a group that he is part of." (-> session-admin diff --git a/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc b/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc index 67a69ddda..0054aa872 100644 --- a/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc +++ b/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc @@ -11,6 +11,8 @@ (def valid-event {:id "event/262626262626262" + :event-type "test" + :success true :resource-type t/resource-type :created event-timestamp :updated event-timestamp @@ -21,7 +23,10 @@ :content {:resource {:href "module/HNSciCloud-RHEA/S3"} :state "Started"} :category "state" - :severity "critical"}) + :severity "critical" + :authn-info {:user-id "user/a978c1c0-f958-4238-9eba-aab85714b114" + :claims ["group/nuvla-anon" "group/nuvla-user"] + :active-claim "group/nuvla-user"}}) (deftest check-reference From 33a6c2b2e5a1304030c53819af428684800867b3 Mon Sep 17 00:00:00 2001 From: khaled basbous Date: Fri, 18 Aug 2023 10:02:35 +0200 Subject: [PATCH 02/17] session acl distinct claim bugfix, add-to-visible-to, session delete event make it visible to session user --- .../nuvla/server/resources/common/event_context.clj | 6 ++++++ code/src/sixsq/nuvla/server/resources/event/utils.clj | 2 +- code/src/sixsq/nuvla/server/resources/session.clj | 3 ++- .../nuvla/server/resources/common/event_context_test.clj | 8 +++++++- .../server/resources/session_api_key_lifecycle_test.clj | 2 +- .../server/resources/session_password_lifecycle_test.clj | 9 +++++++++ 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_context.clj b/code/src/sixsq/nuvla/server/resources/common/event_context.clj index d095c4168..a90c2176b 100644 --- a/code/src/sixsq/nuvla/server/resources/common/event_context.clj +++ b/code/src/sixsq/nuvla/server/resources/common/event_context.clj @@ -26,6 +26,12 @@ (when @*context* (swap! *context* assoc k v))) +(defn add-to-visible-to + "Adds `v` to visible to" + [& claims] + (when @*context* + (swap! *context* update :visible-to concat claims))) + (defn add-linked-identifier "Adds the identifier of a linked entity to the context." diff --git a/code/src/sixsq/nuvla/server/resources/event/utils.clj b/code/src/sixsq/nuvla/server/resources/event/utils.clj index 79feb65ca..b876eec07 100644 --- a/code/src/sixsq/nuvla/server/resources/event/utils.clj +++ b/code/src/sixsq/nuvla/server/resources/event/utils.clj @@ -61,7 +61,7 @@ [{:keys [visible-to] :as _context}] (let [visible-to (remove nil? visible-to)] (or (when (seq visible-to) - {:owners (conj (vec visible-to) "group/nuvla-admin")}) + {:owners (-> visible-to (conj "group/nuvla-admin") distinct vec)}) {:owners ["group/nuvla-admin"]}))) diff --git a/code/src/sixsq/nuvla/server/resources/session.clj b/code/src/sixsq/nuvla/server/resources/session.clj index ad8f37c36..0aa1a2ea2 100644 --- a/code/src/sixsq/nuvla/server/resources/session.clj +++ b/code/src/sixsq/nuvla/server/resources/session.clj @@ -304,6 +304,7 @@ status, a 'set-cookie' header, and a 'location' header with the created (defmethod crud/delete resource-type [request] + (ectx/add-to-visible-to (auth/current-user-id request)) (let [response (delete-impl request) cookies (delete-cookie response)] (merge response cookies))) @@ -466,7 +467,7 @@ status, a 'set-cookie' header, and a 'location' header with the created (defn set-event-context [{{:keys [claim]} :body :as _request}] (ectx/add-linked-identifier claim) - (ectx/add-to-context :visible-to [claim])) + (ectx/add-to-visible-to claim)) (defmethod crud/do-action [resource-type "switch-group"] diff --git a/code/test/sixsq/nuvla/server/resources/common/event_context_test.clj b/code/test/sixsq/nuvla/server/resources/common/event_context_test.clj index 132d2ffe6..4492eb9a6 100644 --- a/code/test/sixsq/nuvla/server/resources/common/event_context_test.clj +++ b/code/test/sixsq/nuvla/server/resources/common/event_context_test.clj @@ -13,4 +13,10 @@ (is (= info (get (t/get-context) k))) (t/add-linked-identifier linked-id) - (is (some #{linked-id} (:linked-identifiers (t/get-context))))))) + (is (some #{linked-id} (:linked-identifiers (t/get-context)))) + + (t/add-to-visible-to "user/toto") + (is (= #{"user/toto"} (set (:visible-to (t/get-context))))) + (t/add-to-visible-to "user/tata" "user/titi") + (is (= #{"user/tata" "user/titi" "user/toto"} (set (:visible-to (t/get-context))))) + ))) diff --git a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj index bb8116bca..8a03e503b 100644 --- a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj @@ -228,7 +228,7 @@ ;; user with session role can delete resource (-> (session app) - (header authn-info-header (str "user group/nuvla-user " id)) + (header authn-info-header (str "user/user group/nuvla-user " id)) (request abs-uri :request-method :delete) (ltu/is-unset-cookie) diff --git a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj index 33e9dda58..21fbf5d8e 100644 --- a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj @@ -245,6 +245,15 @@ (ltu/body->edn) (ltu/is-status 200)) + (ltu/is-last-event id + {:event-type "session.delete" + :success true + :linked-identifiers [] + :acl {:owners ["group/nuvla-admin" + "user/user"]}}) + + + ; create with invalid template fails (-> session-anon (request base-uri From 208ecb9360cce13ed66fe946b053ecb50a741ddf Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Fri, 18 Aug 2023 11:59:50 +0200 Subject: [PATCH 03/17] Events on modules --- .../nuvla/server/resources/event/utils.clj | 36 +++- .../sixsq/nuvla/server/resources/module.clj | 28 ++- .../resources/module_lifecycle_test.clj | 204 ++++++++++++------ 3 files changed, 192 insertions(+), 76 deletions(-) diff --git a/code/src/sixsq/nuvla/server/resources/event/utils.clj b/code/src/sixsq/nuvla/server/resources/event/utils.clj index b876eec07..51626dad1 100644 --- a/code/src/sixsq/nuvla/server/resources/event/utils.clj +++ b/code/src/sixsq/nuvla/server/resources/event/utils.clj @@ -52,16 +52,39 @@ (defn retrieve-by-id [id] (try - (crud/retrieve-by-id-as-admin id) + (:body (crud/retrieve {:params (u/id->request-params id) + :request-method :get + :nuvla/authn auth/internal-identity})) (catch Exception _ex nil))) +(defn get-resource-href + [{{:keys [resource-name uuid]} :params :as _context} response] + (or (some->> uuid (str resource-name "/")) + (-> response :body :resource-id))) + + +(defn transform-acl + [acl] + (when acl + {:owners (vec (concat (:edit-data acl) (:owners acl)))})) + +(defn derive-acl-from-resource + [context response] + (when-let [acl (some-> (get-resource-href context response) + retrieve-by-id + :acl)] + (transform-acl acl))) + + (defn get-acl - [{:keys [visible-to] :as _context}] + [{:keys [visible-to acl] :as context} response] (let [visible-to (remove nil? visible-to)] (or (when (seq visible-to) {:owners (-> visible-to (conj "group/nuvla-admin") distinct vec)}) + (transform-acl acl) + (derive-acl-from-resource context response) {:owners ["group/nuvla-admin"]}))) @@ -71,9 +94,8 @@ (defn get-resource - [{{:keys [resource-name uuid]} :params :as _context} _request response] - {:href (or (some->> uuid (str resource-name "/")) - (-> response :body :resource-id))}) + [context response] + {:href (get-resource-href context response)}) (defn get-linked-identifiers @@ -89,9 +111,9 @@ :category (get-category context) :timestamp (get-timestamp context) :authn-info (auth/current-authentication request) - :acl (get-acl context) + :acl (get-acl context response) :severity (get-severity context) - :content {:resource (get-resource context request response) + :content {:resource (get-resource context response) :state "to be removed" :linked-identifiers (get-linked-identifiers context)}}) diff --git a/code/src/sixsq/nuvla/server/resources/module.clj b/code/src/sixsq/nuvla/server/resources/module.clj index 13bc9ee0d..80f51e3a8 100644 --- a/code/src/sixsq/nuvla/server/resources/module.clj +++ b/code/src/sixsq/nuvla/server/resources/module.clj @@ -10,6 +10,8 @@ component, or application. [sixsq.nuvla.db.filter.parser :as parser] [sixsq.nuvla.db.impl :as db] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-config :as ec] + [sixsq.nuvla.server.resources.common.event-context :as ectx] [sixsq.nuvla.server.resources.common.std-crud :as std-crud] [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.configuration-nuvla :as config-nuvla] @@ -359,11 +361,12 @@ component, or application. (defmethod crud/delete resource-type [request] (try - (-> request - utils/retrieve-module-meta - throw-project-cannot-delete-if-has-children - (a/throw-cannot-edit request) - (delete-all request)) + (let [module-meta (utils/retrieve-module-meta request)] + (ectx/add-to-context :acl (:acl module-meta)) + (-> module-meta + throw-project-cannot-delete-if-has-children + (a/throw-cannot-edit request) + (delete-all request))) (catch Exception e (or (ex-data e) (throw e))))) @@ -481,6 +484,21 @@ component, or application. deploy-op-present? (update :operations conj deploy-op) can-delete? (update :operations conj delete-version-op)))) + +;; +;; Events +;; + +(defmethod ec/events-enabled? resource-type + [_resource-type] + true) + + +(defmethod ec/log-event? "validate-docker-compose" + [_event _response] + false) + + ;; ;; initialization ;; diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj index 95bc25dc7..2aaaa9538 100644 --- a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj @@ -20,26 +20,26 @@ (defn- get-path-segments [path] (reduce - (fn [acu cur] - (conj acu (if (seq acu) - (str (last acu) "/" cur) - cur))) - [] - (str/split path #"/"))) + (fn [acu cur] + (conj acu (if (seq acu) + (str (last acu) "/" cur) + cur))) + [] + (str/split path #"/"))) (defn create-parent-projects [path user] (let [paths (get-path-segments (utils/get-parent-path path))] (run! - (fn [path-segment] - (-> user - (request base-uri - :request-method :post - :body (json/write-str {:subtype utils/subtype-project - :path path-segment - :parent-path (utils/get-parent-path path-segment)})) - ltu/body->edn - (ltu/is-status 201))) - paths))) + (fn [path-segment] + (-> user + (request base-uri + :request-method :post + :body (json/write-str {:subtype utils/subtype-project + :path path-segment + :parent-path (utils/get-parent-path path-segment)})) + ltu/body->edn + (ltu/is-status 201))) + paths))) (defn lifecycle-test-module [subtype valid-content] @@ -71,6 +71,12 @@ (ltu/body->edn) (ltu/is-status 403)) + (ltu/is-last-event nil + {:event-type "module.add" + :success false + :linked-identifiers [] + :acl {:owners ["group/nuvla-admin"]}}) + ;; queries: NOK for anon (-> session-anon (request base-uri) @@ -95,6 +101,12 @@ (ltu/body->edn) (ltu/is-status 400)) + (ltu/is-last-event nil + {:event-type "module.add" + :success false + :linked-identifiers [] + :acl {:owners ["group/nuvla-admin"]}}) + (when (utils/is-application? valid-entry) (testing "application should have compatibility attribute set" @@ -107,7 +119,8 @@ (ltu/message-matches "Application subtype should have compatibility attribute set!")))) ;; adding, retrieving and deleting entry as user should succeed - (doseq [session [session-admin session-user]] + (doseq [[session event-owners] [[session-admin ["group/nuvla-admin"]] + [session-user ["group/nuvla-admin" "user/jane"]]]] (let [uri (-> session (request base-uri :request-method :post @@ -118,22 +131,27 @@ abs-uri (str p/service-context uri)] + (ltu/is-last-event uri + {:event-type "module.add" + :success true + :linked-identifiers [] + :acl {:owners event-owners}}) + ;; retrieve: NOK for anon (-> session-anon (request abs-uri) (ltu/body->edn) (ltu/is-status 403)) - (let [content (-> session-admin - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-key-value :compatibility "docker-compose") - (as-> m (if (utils/is-application? valid-entry) - (ltu/is-operation-present m :validate-docker-compose) - (ltu/is-operation-absent m :validate-docker-compose))) - (ltu/body) - :content)] + (let [{:keys [content acl]} (-> session-admin + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-key-value :compatibility "docker-compose") + (as-> m (if (utils/is-application? valid-entry) + (ltu/is-operation-present m :validate-docker-compose) + (ltu/is-operation-absent m :validate-docker-compose))) + (ltu/body))] (is (= valid-content (select-keys content (keys valid-content))))) ;; edit: NOK for anon @@ -144,6 +162,12 @@ (ltu/body->edn) (ltu/is-status 403)) + (ltu/is-last-event uri + {:event-type "module.edit" + :success false + :linked-identifiers [] + :acl {:owners event-owners}}) + ;; insert 5 more versions (doseq [_ (range 5)] (-> session-admin @@ -151,7 +175,13 @@ :request-method :put :body (json/write-str valid-entry)) (ltu/body->edn) - (ltu/is-status 200))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:event-type "module.edit" + :success true + :linked-identifiers [] + :acl {:owners event-owners}})) (let [versions (-> session-admin (request abs-uri @@ -190,7 +220,13 @@ (request publish-url) (ltu/body->edn) (ltu/is-status 200) - (ltu/message-matches "published successfully"))) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event uri + {:event-type "module.publish" + :success true + :linked-identifiers [] + :acl {:owners event-owners}})) (testing "operation urls of specific version" (let [abs-uri-v2 (str abs-uri "_2") @@ -214,7 +250,13 @@ (request (str abs-uri "_2/publish")) (ltu/body->edn) (ltu/is-status 200) - (ltu/message-matches "published successfully"))) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event (str uri "_2") + {:event-type "module.publish" + :success true + :linked-identifiers [] + :acl {:owners event-owners}})) (let [unpublish-url (-> session (request abs-uri) @@ -233,6 +275,12 @@ (ltu/is-status 200) (ltu/message-matches "unpublished successfully"))) + (ltu/is-last-event uri + {:event-type "module.unpublish" + :success true + :linked-identifiers [] + :acl {:owners event-owners}}) + ; publish is idempotent (-> session (request (str abs-uri "_2/publish")) @@ -240,6 +288,12 @@ (ltu/is-status 200) (ltu/message-matches "published successfully")) + (ltu/is-last-event (str uri "_2") + {:event-type "module.publish" + :success true + :linked-identifiers [] + :acl {:owners event-owners}}) + (-> session (request abs-uri) (ltu/body->edn) @@ -256,6 +310,12 @@ (ltu/is-status 200) (ltu/message-matches "unpublished successfully")) + (ltu/is-last-event (str uri "_2") + {:event-type "module.unpublish" + :success true + :linked-identifiers [] + :acl {:owners event-owners}}) + (-> session (request abs-uri) (ltu/body->edn) @@ -283,6 +343,7 @@ (ltu/body->edn) (ltu/is-status 200)) + (-> session-admin (request (str abs-uri i)) (ltu/body->edn) @@ -295,6 +356,14 @@ (ltu/body->edn) (ltu/is-status 200))) + + (ltu/is-last-event uri + {:event-type "module.delete-version" + :success true + :linked-identifiers [] + :acl {:owners event-owners}}) + + (testing "delete out of bound index should return 404" (-> session-admin (request (str abs-uri "_50/delete-version")) @@ -319,6 +388,13 @@ (ltu/body->edn) (ltu/is-status 200)) + + (ltu/is-last-event uri + {:event-type "module.delete" + :success true + :linked-identifiers [] + :acl {:owners event-owners}}) + ;; verify that the resource was deleted. (-> session-admin (request abs-uri) @@ -353,12 +429,12 @@ session-user (header session-anon authn-info-header "user/jane user/jane group/nuvla-user group/nuvla-anon") - project {:resource-type module/resource-type - :created timestamp - :updated timestamp - :parent-path "" - :path "example" - :subtype utils/subtype-project} + project {:resource-type module/resource-type + :created timestamp + :updated timestamp + :parent-path "" + :path "example" + :subtype utils/subtype-project} valid-app {:parent-path "example" :path "example/app" @@ -393,13 +469,13 @@ (testing "Failure creating application 3: user does not have edit rights in parent project" ;; Creating a parent project with nuvla-admin as owner - (let [uri (-> session-admin - (request base-uri - :request-method :post - :body (json/write-str project)) - ltu/body->edn - (ltu/is-status 201) - ltu/location-url)] + (let [uri (-> session-admin + (request base-uri + :request-method :post + :body (json/write-str project)) + ltu/body->edn + (ltu/is-status 201) + ltu/location-url)] ;; If user has no view rights, failure message says that parent project does not exist. (-> session-user @@ -415,11 +491,11 @@ (request uri :request-method :put :body (json/write-str - (assoc project - :acl {:owners ["group/nuvla-admin"] - :view-meta ["user/jane"] - :view-data ["user/jane"] - :view-acl ["user/jane"]}))) + (assoc project + :acl {:owners ["group/nuvla-admin"] + :view-meta ["user/jane"] + :view-data ["user/jane"] + :view-acl ["user/jane"]}))) ltu/body->edn (ltu/is-status 200))) @@ -432,7 +508,7 @@ (ltu/is-status 403) (ltu/message-matches "You do not have edit rights for:"))) - ;; Trying to add app to parent app should fail + ;; Trying to add app to parent app should fail (testing "Failure creating application 4: Parent is not a project." (-> session-user (request base-uri @@ -456,18 +532,18 @@ (testing "new application can be in a project nested inside another project" ;; Creating a parent project with wrong edit rights - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (assoc project :path "grandparent"))) - ltu/body->edn - (ltu/is-status 201)) - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (assoc project :path "grandparent/parent"))) - ltu/body->edn - (ltu/is-status 201)) + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (assoc project :path "grandparent"))) + ltu/body->edn + (ltu/is-status 201)) + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (assoc project :path "grandparent/parent"))) + ltu/body->edn + (ltu/is-status 201)) (-> session-user (request base-uri :request-method :post @@ -529,9 +605,9 @@ (request app-1-uri :request-method :put :body (json/write-str - (update valid-app-1 :content assoc - :docker-compose "content changed" - :commit "second commit"))) + (update valid-app-1 :content assoc + :docker-compose "content changed" + :commit "second commit"))) (ltu/body->edn) (ltu/is-status 200)) From abd9fcfcbef2b078f3592f51062f0e756e3cc3c1 Mon Sep 17 00:00:00 2001 From: khaled basbous Date: Fri, 18 Aug 2023 14:51:18 +0200 Subject: [PATCH 04/17] fix ring handler order, fix disable event for validate docker compose --- code/src/sixsq/nuvla/server/app/server.clj | 2 +- code/src/sixsq/nuvla/server/resources/module.clj | 2 +- code/src/sixsq/nuvla/server/resources/spec/event.cljc | 3 +-- .../test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/code/src/sixsq/nuvla/server/app/server.clj b/code/src/sixsq/nuvla/server/app/server.clj index 0ae16d3e2..68fdc35a2 100644 --- a/code/src/sixsq/nuvla/server/app/server.clj +++ b/code/src/sixsq/nuvla/server/app/server.clj @@ -47,10 +47,10 @@ wrap-nested-params wrap-params wrap-exceptions - wrap-authn-info (wrap-json-body {:keywords? true}) wrap-gzip-uncompress wrap-eventer + wrap-authn-info (wrap-json-response {:pretty true :escape-non-ascii true}) (default-content-type "application/json") diff --git a/code/src/sixsq/nuvla/server/resources/module.clj b/code/src/sixsq/nuvla/server/resources/module.clj index 80f51e3a8..d3fd48ba9 100644 --- a/code/src/sixsq/nuvla/server/resources/module.clj +++ b/code/src/sixsq/nuvla/server/resources/module.clj @@ -494,7 +494,7 @@ component, or application. true) -(defmethod ec/log-event? "validate-docker-compose" +(defmethod ec/log-event? "module.validate-docker-compose" [_event _response] false) diff --git a/code/src/sixsq/nuvla/server/resources/spec/event.cljc b/code/src/sixsq/nuvla/server/resources/spec/event.cljc index ab628e964..2d14925c7 100644 --- a/code/src/sixsq/nuvla/server/resources/spec/event.cljc +++ b/code/src/sixsq/nuvla/server/resources/spec/event.cljc @@ -5,7 +5,6 @@ [sixsq.nuvla.server.resources.spec.common :as common] [sixsq.nuvla.server.resources.spec.core :as core] [sixsq.nuvla.server.resources.spec.session :as session] - [sixsq.nuvla.server.resources.spec.user :as user] [sixsq.nuvla.server.util.spec :as su] [spec-tools.core :as st])) @@ -101,7 +100,7 @@ (s/def ::user-id - (-> (st/spec ::user/id) + (-> (st/spec ::core/nonblank-string) (assoc :name "user-id" :json-schema/type "string" :json-schema/description "user id"))) diff --git a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj index 47e504445..49c860001 100644 --- a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj +++ b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj @@ -471,10 +471,10 @@ wrap-nested-params wrap-params wrap-base-uri - wrap-authn-info wrap-exceptions (wrap-json-body {:keywords? true}) wrap-eventer + wrap-authn-info (wrap-json-response {:pretty true :escape-non-ascii true}) wrap-logger)) From c3c810ee1bab4a1fddce94de5c333369145540af Mon Sep 17 00:00:00 2001 From: khaled basbous Date: Mon, 21 Aug 2023 11:29:05 +0200 Subject: [PATCH 05/17] make event state attribute optionnal --- code/src/sixsq/nuvla/server/resources/event/utils.clj | 1 - code/src/sixsq/nuvla/server/resources/spec/event.cljc | 5 +++-- code/test/sixsq/nuvla/server/resources/event_utils_test.clj | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/code/src/sixsq/nuvla/server/resources/event/utils.clj b/code/src/sixsq/nuvla/server/resources/event/utils.clj index 51626dad1..b9477310f 100644 --- a/code/src/sixsq/nuvla/server/resources/event/utils.clj +++ b/code/src/sixsq/nuvla/server/resources/event/utils.clj @@ -114,7 +114,6 @@ :acl (get-acl context response) :severity (get-severity context) :content {:resource (get-resource context response) - :state "to be removed" :linked-identifiers (get-linked-identifiers context)}}) diff --git a/code/src/sixsq/nuvla/server/resources/spec/event.cljc b/code/src/sixsq/nuvla/server/resources/spec/event.cljc index 2d14925c7..d9bcaf899 100644 --- a/code/src/sixsq/nuvla/server/resources/spec/event.cljc +++ b/code/src/sixsq/nuvla/server/resources/spec/event.cljc @@ -90,8 +90,9 @@ (s/def ::content - (-> (st/spec (su/only-keys :req-un [::resource ::state] - :opt-un [::linked-identifiers])) + (-> (st/spec (su/only-keys :req-un [::resource] + :opt-un [::linked-identifiers + ::state])) (assoc :name "content" :json-schema/type "map" :json-schema/description "content describing event" diff --git a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj index 77c839790..81ff54ebd 100644 --- a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj +++ b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj @@ -36,7 +36,6 @@ (is (= {:event-type "resource.add" :category "action" :content {:resource {:href id} - :state "to be removed" :linked-identifiers []} :authn-info {} :success true @@ -50,7 +49,6 @@ (is (= {:event-type "resource.add" :category "action" :content {:resource {:href nil} - :state "to be removed" :linked-identifiers []} :authn-info {} :success false From 225e35269dc0607abac9e928db957128fb942d91 Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Mon, 21 Aug 2023 14:58:43 +0200 Subject: [PATCH 06/17] Add check on authn-info --- .../server/resources/lifecycle_test_utils.clj | 14 +++-- .../resources/module_lifecycle_test.clj | 52 +++++++++++++------ .../session_api_key_lifecycle_test.clj | 13 +++-- .../session_password_lifecycle_test.clj | 43 +++++++++------ 4 files changed, 83 insertions(+), 39 deletions(-) diff --git a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj index 49c860001..045b159fa 100644 --- a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj +++ b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj @@ -310,7 +310,7 @@ [client server]))) -(def ^:private zk-client-server-cache (atom nil)) +(defonce ^:private zk-client-server-cache (atom nil)) (defn set-zk-client-server-cache @@ -389,7 +389,7 @@ [node client sniffer])) -(def ^:private es-node-client-cache (atom nil)) +(defonce ^:private es-node-client-cache (atom nil)) (defn es-node [] @@ -607,14 +607,20 @@ ;; (defmacro is-last-event - [resource-id {:keys [event-type linked-identifiers success acl]}] - `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1}))] + [resource-id {:keys [event-type authn-info linked-identifiers success acl]}] + `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1})) + authn-info# (:authn-info event#)] (is (some? event#)) (when ~event-type (is (= ~event-type (:event-type event#)))) + (when ~authn-info + (is (= (:user-id ~authn-info) (:user-id authn-info#))) + (is (= (:active-claim ~authn-info) (:active-claim authn-info#))) + (is (= (set (:claims ~authn-info)) (set (:claims authn-info#))))) (when ~linked-identifiers (is (= (set ~linked-identifiers) (set (get-in event# [:content :linked-identifiers]))))) (when (some? ~success) (is (= ~success (:success event#)))) (when (some? ~acl) (is (= ~acl (:acl event#)))))) + diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj index 2aaaa9538..2f6d5e22a 100644 --- a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj @@ -43,25 +43,32 @@ (defn lifecycle-test-module [subtype valid-content] - (let [session-anon (-> (session (ltu/ring-app)) - (content-type "application/json")) - session-admin (header session-anon authn-info-header - "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") - session-user (header session-anon authn-info-header - "user/jane user/jane group/nuvla-user group/nuvla-anon") + (let [session-anon (-> (session (ltu/ring-app)) + (content-type "application/json")) + session-admin (header session-anon authn-info-header + "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") + session-user (header session-anon authn-info-header + "user/jane user/jane group/nuvla-user group/nuvla-anon") - valid-entry {:parent-path "a/b" - :path "a/b/c" - :subtype subtype + valid-entry {:parent-path "a/b" + :path "a/b/c" + :subtype subtype - :compatibility "docker-compose" + :compatibility "docker-compose" - :logo-url "https://example.org/logo" + :logo-url "https://example.org/logo" - :data-accept-content-types ["application/json" "application/x-something"] - :data-access-protocols ["http+s3" "posix+nfs"] + :data-accept-content-types ["application/json" "application/x-something"] + :data-access-protocols ["http+s3" "posix+nfs"] - :content valid-content}] + :content valid-content} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:claims ["group/nuvla-anon"]}] ;; create: NOK for anon (-> session-anon @@ -75,6 +82,7 @@ {:event-type "module.add" :success false :linked-identifiers [] + :authn-info authn-info-anon :acl {:owners ["group/nuvla-admin"]}}) ;; queries: NOK for anon @@ -105,6 +113,7 @@ {:event-type "module.add" :success false :linked-identifiers [] + :authn-info authn-info-admin :acl {:owners ["group/nuvla-admin"]}}) (when (utils/is-application? valid-entry) @@ -119,8 +128,9 @@ (ltu/message-matches "Application subtype should have compatibility attribute set!")))) ;; adding, retrieving and deleting entry as user should succeed - (doseq [[session event-owners] [[session-admin ["group/nuvla-admin"]] - [session-user ["group/nuvla-admin" "user/jane"]]]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane]]] (let [uri (-> session (request base-uri :request-method :post @@ -135,6 +145,7 @@ {:event-type "module.add" :success true :linked-identifiers [] + :authn-info authn-info :acl {:owners event-owners}}) ;; retrieve: NOK for anon @@ -166,6 +177,7 @@ {:event-type "module.edit" :success false :linked-identifiers [] + :authn-info authn-info-anon :acl {:owners event-owners}}) ;; insert 5 more versions @@ -181,6 +193,7 @@ {:event-type "module.edit" :success true :linked-identifiers [] + :authn-info authn-info-admin :acl {:owners event-owners}})) (let [versions (-> session-admin @@ -226,6 +239,7 @@ {:event-type "module.publish" :success true :linked-identifiers [] + :authn-info authn-info :acl {:owners event-owners}})) (testing "operation urls of specific version" @@ -256,6 +270,7 @@ {:event-type "module.publish" :success true :linked-identifiers [] + :authn-info authn-info :acl {:owners event-owners}})) (let [unpublish-url (-> session @@ -279,6 +294,7 @@ {:event-type "module.unpublish" :success true :linked-identifiers [] + :authn-info authn-info :acl {:owners event-owners}}) ; publish is idempotent @@ -292,6 +308,7 @@ {:event-type "module.publish" :success true :linked-identifiers [] + :authn-info authn-info :acl {:owners event-owners}}) (-> session @@ -314,6 +331,7 @@ {:event-type "module.unpublish" :success true :linked-identifiers [] + :authn-info authn-info :acl {:owners event-owners}}) (-> session @@ -361,6 +379,7 @@ {:event-type "module.delete-version" :success true :linked-identifiers [] + :authn-info authn-info-admin :acl {:owners event-owners}}) @@ -393,6 +412,7 @@ {:event-type "module.delete" :success true :linked-identifiers [] + :authn-info authn-info-admin :acl {:owners event-owners}}) ;; verify that the resource was deleted. diff --git a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj index 8a03e503b..39403d585 100644 --- a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj @@ -123,7 +123,10 @@ :key uuid :secret secret}} unauthorized-create (update-in valid-create [:template :secret] (constantly bad-digest)) - invalid-create (assoc-in valid-create [:template :invalid] "BAD")] + invalid-create (assoc-in valid-create [:template :invalid] "BAD") + event-authn-info {:user-id "user/unknown" + :active-claim "user/unknown" + :claims ["user/unknown" "group/nuvla-anon"]}] ;; anonymous query should succeed but have no entries (-> session-anon @@ -142,7 +145,9 @@ (ltu/is-last-event uuid {:event-type "session.add" :success false - :linked-identifiers [(str "credential/" uuid)]}) + :linked-identifiers [(str "credential/" uuid)] + :authn-info event-authn-info + :acl {:owners ["group/nuvla-admin"]}}) ;; anonymous create must succeed; also with redirect (let [resp (-> session-anon @@ -155,7 +160,9 @@ id (ltu/body-resource-id resp) _ (ltu/is-last-event id {:event-type "session.add" :success true - :linked-identifiers [(str "credential/" uuid)]}) + :linked-identifiers [(str "credential/" uuid)] + :authn-info event-authn-info + :acl {:owners ["group/nuvla-admin" id]}}) token (get-in resp [:response :cookies authn-cookie :value]) cookie-info (if token (sign/unsign-cookie-info token) {}) diff --git a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj index 21fbf5d8e..0f04a843c 100644 --- a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj @@ -151,20 +151,23 @@ :email "jane@example.org")] ; anonymous create must succeed - (let [resp (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str valid-create)) - (ltu/body->edn) - (ltu/is-set-cookie) - (ltu/is-status 201)) - id (ltu/body-resource-id resp) - - token (get-in resp [:response :cookies authn-cookie :value]) - authn-info (if token (sign/unsign-cookie-info token) {}) - - uri (ltu/location resp) - abs-uri (str p/service-context uri)] + (let [resp (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str valid-create)) + (ltu/body->edn) + (ltu/is-set-cookie) + (ltu/is-status 201)) + id (ltu/body-resource-id resp) + + token (get-in resp [:response :cookies authn-cookie :value]) + authn-info (if token (sign/unsign-cookie-info token) {}) + event-authn-info {:user-id "user/user" + :active-claim "group/nuvla-user" + :claims ["group/nuvla-anon" id "user/user"]} + + uri (ltu/location resp) + abs-uri (str p/service-context uri)] ; check claims in cookie (is (= jane-user-id (:user-id authn-info))) @@ -249,6 +252,7 @@ {:event-type "session.delete" :success true :linked-identifiers [] + :authn-info event-authn-info :acl {:owners ["group/nuvla-admin" "user/user"]}}) @@ -329,6 +333,9 @@ {:event-type "session.add" :success true :linked-identifiers [user-id credential-id] + :authn-info {:user-id "user/unknown" + :active-claim "user/unknown" + :claims ["user/unknown" "group/nuvla-anon"]} :acl {:owners ["group/nuvla-admin" user-id]}}) handler (wrap-authn-info identity) authn-session-user (-> session-user @@ -344,7 +351,10 @@ switch-op-url (-> (apply request session-json (concat [sesssion-user-url] authn-session-user)) (ltu/body->edn) (ltu/is-status 200) - (ltu/get-op-url :switch-group))] + (ltu/get-op-url :switch-group)) + event-authn-info {:user-id user-id + :active-claim user-id + :claims ["group/nuvla-anon" "group/nuvla-user" session-user-id user-id]}] (testing "User cannot switch to a group that he is not part of." (-> (apply request session-json @@ -357,7 +367,8 @@ (ltu/is-last-event session-user-id {:event-type "session.switch-group" :success false :linked-identifiers [group-b] - :acl {:owners ["group/nuvla-admin" group-b]}})) + :authn-info event-authn-info + :acl {:owners ["group/nuvla-admin" group-b]}})) (testing "User can switch to a group that he is part of." (-> session-admin From 521f6a857bd4da6ef08d9c7c0105413b7b3ebf92 Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Mon, 21 Aug 2023 16:26:00 +0200 Subject: [PATCH 07/17] Event category --- code/src/sixsq/nuvla/server/app/routes.clj | 4 ++++ .../src/sixsq/nuvla/server/resources/spec/event.cljc | 5 ++--- .../nuvla/server/resources/lifecycle_test_utils.clj | 4 +++- .../nuvla/server/resources/module_lifecycle_test.clj | 12 ++++++++++++ .../resources/session_api_key_lifecycle_test.clj | 2 ++ .../resources/session_password_lifecycle_test.clj | 3 +++ 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/code/src/sixsq/nuvla/server/app/routes.clj b/code/src/sixsq/nuvla/server/app/routes.clj index 27371b417..de363e479 100644 --- a/code/src/sixsq/nuvla/server/app/routes.clj +++ b/code/src/sixsq/nuvla/server/app/routes.clj @@ -14,6 +14,7 @@ (let-routes [uri (str p/service-context ":resource-name")] (POST uri request (ec/add-to-context :params (:params request)) + (ec/add-to-context :category "add") (crud/add request)) (PUT uri request (ec/add-to-context :params (:params request)) @@ -35,9 +36,11 @@ (crud/retrieve request)) (PUT uri request (ec/add-to-context :params (:params request)) + (ec/add-to-context :category "edit") (crud/edit request)) (DELETE uri request (ec/add-to-context :params (:params request)) + (ec/add-to-context :category "delete") (crud/delete request)) (ANY uri request (throw (r/ex-bad-method request))))) @@ -47,6 +50,7 @@ (let-routes [uri (str p/service-context ":resource-name/:uuid/:action")] (ANY uri request (ec/add-to-context :params (:params request)) + (ec/add-to-context :category "action") (crud/do-action request)))) diff --git a/code/src/sixsq/nuvla/server/resources/spec/event.cljc b/code/src/sixsq/nuvla/server/resources/spec/event.cljc index d9bcaf899..964e90b0b 100644 --- a/code/src/sixsq/nuvla/server/resources/spec/event.cljc +++ b/code/src/sixsq/nuvla/server/resources/spec/event.cljc @@ -17,12 +17,11 @@ (s/def ::category - (-> (st/spec #{"state" "alarm" "action" "system" "user" "email"}) + (-> (st/spec #{"add" "edit" "delete" "action" "state" "alarm" "email" "user"}) (assoc :name "category" :json-schema/type "string" :json-schema/description "category of event" - :json-schema/value-scope {:values ["state" "alarm" "action" "system" "user" - "email"]} + :json-schema/value-scope {:values ["add" "edit" "delete" "action" "state" "alarm" "email" "user"]} :json-schema/order 30))) diff --git a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj index 045b159fa..f646495d5 100644 --- a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj +++ b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj @@ -607,12 +607,14 @@ ;; (defmacro is-last-event - [resource-id {:keys [event-type authn-info linked-identifiers success acl]}] + [resource-id {:keys [event-type category authn-info linked-identifiers success acl]}] `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1})) authn-info# (:authn-info event#)] (is (some? event#)) (when ~event-type (is (= ~event-type (:event-type event#)))) + (when ~category + (is (= ~category (:category event#)))) (when ~authn-info (is (= (:user-id ~authn-info) (:user-id authn-info#))) (is (= (:active-claim ~authn-info) (:active-claim authn-info#))) diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj index 2f6d5e22a..f3b565b80 100644 --- a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj @@ -80,6 +80,7 @@ (ltu/is-last-event nil {:event-type "module.add" + :category "add" :success false :linked-identifiers [] :authn-info authn-info-anon @@ -111,6 +112,7 @@ (ltu/is-last-event nil {:event-type "module.add" + :category "add" :success false :linked-identifiers [] :authn-info authn-info-admin @@ -143,6 +145,7 @@ (ltu/is-last-event uri {:event-type "module.add" + :category "add" :success true :linked-identifiers [] :authn-info authn-info @@ -175,6 +178,7 @@ (ltu/is-last-event uri {:event-type "module.edit" + :category "edit" :success false :linked-identifiers [] :authn-info authn-info-anon @@ -191,6 +195,7 @@ (ltu/is-last-event uri {:event-type "module.edit" + :category "edit" :success true :linked-identifiers [] :authn-info authn-info-admin @@ -237,6 +242,7 @@ (ltu/is-last-event uri {:event-type "module.publish" + :category "action" :success true :linked-identifiers [] :authn-info authn-info @@ -268,6 +274,7 @@ (ltu/is-last-event (str uri "_2") {:event-type "module.publish" + :category "action" :success true :linked-identifiers [] :authn-info authn-info @@ -292,6 +299,7 @@ (ltu/is-last-event uri {:event-type "module.unpublish" + :category "action" :success true :linked-identifiers [] :authn-info authn-info @@ -306,6 +314,7 @@ (ltu/is-last-event (str uri "_2") {:event-type "module.publish" + :category "action" :success true :linked-identifiers [] :authn-info authn-info @@ -329,6 +338,7 @@ (ltu/is-last-event (str uri "_2") {:event-type "module.unpublish" + :category "action" :success true :linked-identifiers [] :authn-info authn-info @@ -377,6 +387,7 @@ (ltu/is-last-event uri {:event-type "module.delete-version" + :category "action" :success true :linked-identifiers [] :authn-info authn-info-admin @@ -410,6 +421,7 @@ (ltu/is-last-event uri {:event-type "module.delete" + :category "delete" :success true :linked-identifiers [] :authn-info authn-info-admin diff --git a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj index 39403d585..bcc1a2611 100644 --- a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj @@ -144,6 +144,7 @@ (ltu/is-status 403)) (ltu/is-last-event uuid {:event-type "session.add" + :category "add" :success false :linked-identifiers [(str "credential/" uuid)] :authn-info event-authn-info @@ -159,6 +160,7 @@ (ltu/is-status 201)) id (ltu/body-resource-id resp) _ (ltu/is-last-event id {:event-type "session.add" + :category "add" :success true :linked-identifiers [(str "credential/" uuid)] :authn-info event-authn-info diff --git a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj index 0f04a843c..4781c47e4 100644 --- a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj @@ -250,6 +250,7 @@ (ltu/is-last-event id {:event-type "session.delete" + :category "delete" :success true :linked-identifiers [] :authn-info event-authn-info @@ -331,6 +332,7 @@ credential-id (:credential-password (auth-password/user-id->user user-id)) _ (ltu/is-last-event session-user-id {:event-type "session.add" + :category "add" :success true :linked-identifiers [user-id credential-id] :authn-info {:user-id "user/unknown" @@ -365,6 +367,7 @@ (ltu/message-matches #"Switch group cannot be done to requested group:.*")) (ltu/is-last-event session-user-id {:event-type "session.switch-group" + :category "action" :success false :linked-identifiers [group-b] :authn-info event-authn-info From cca7872be73196f04b122344f908c58a882f0cdd Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Wed, 23 Aug 2023 09:14:55 +0200 Subject: [PATCH 08/17] move field event-type to field name --- .../server/resources/common/event_config.clj | 10 ++++---- .../nuvla/server/resources/event/utils.clj | 18 +++++++------- .../nuvla/server/resources/spec/event.cljc | 17 +------------ .../resources/common/event_config_test.clj | 4 ++-- .../nuvla/server/resources/event_test.clj | 2 +- .../server/resources/event_utils_test.clj | 6 ++--- .../server/resources/lifecycle_test_utils.clj | 6 ++--- .../resources/module_lifecycle_test.clj | 24 +++++++++---------- .../session_api_key_lifecycle_test.clj | 4 ++-- .../session_password_lifecycle_test.clj | 6 ++--- .../server/resources/spec/event_test.cljc | 2 +- 11 files changed, 42 insertions(+), 57 deletions(-) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config.clj b/code/src/sixsq/nuvla/server/resources/common/event_config.clj index 3587acae3..9b961a781 100644 --- a/code/src/sixsq/nuvla/server/resources/common/event_config.clj +++ b/code/src/sixsq/nuvla/server/resources/common/event_config.clj @@ -8,8 +8,8 @@ resource-type) -(defn event-type-dispatch [{:keys [event-type] :as _event} _response] - event-type) +(defn event-name-dispatch [{event-name :name :as _event} _response] + event-name) ;; @@ -32,10 +32,10 @@ (defmulti log-event? "Returns true if the event should be logged, false otherwise." - event-type-dispatch) + event-name-dispatch) (defmethod log-event? :default - [{:keys [event-type] :as _event} {:keys [status] :as _response}] + [{event-name :name :as _event} {:keys [status] :as _response}] (and (not= 405 status) - (some? event-type))) + (some? event-name))) diff --git a/code/src/sixsq/nuvla/server/resources/event/utils.clj b/code/src/sixsq/nuvla/server/resources/event/utils.clj index b9477310f..a8fe39f61 100644 --- a/code/src/sixsq/nuvla/server/resources/event/utils.clj +++ b/code/src/sixsq/nuvla/server/resources/event/utils.clj @@ -10,7 +10,7 @@ [sixsq.nuvla.server.util.time :as time])) -(defn request-event-type +(defn request-event-name "Returns a string of the form ." [{{:keys [resource-name uuid action]} :params :as _context} {:keys [request-method] :as _request}] @@ -33,10 +33,10 @@ (<= 200 status 399)) -(defn get-event-type - [{:keys [event-type] :as context} request] - (or event-type - (request-event-type context request))) +(defn get-event-name + [{:keys [event-name] :as context} request] + (or event-name + (request-event-name context request))) (defn get-category @@ -106,7 +106,7 @@ (defn build-event [context request response] {:resource-type event/resource-type - :event-type (get-event-type context request) + :name (get-event-name context request) :success (get-success response) :category (get-category context) :timestamp (get-timestamp context) @@ -133,7 +133,7 @@ [resource-href state acl & {:keys [severity category timestamp] :or {severity "medium" category "action"}}] - (let [event-map {:event-type "legacy" + (let [event-map {:name "legacy" :success true :resource-type event/resource-type :content {:resource {:href resource-href} @@ -152,7 +152,7 @@ (defn query-events ([resource-href opts] (query-events (assoc opts :resource-href resource-href))) - ([{:keys [resource-href event-type linked-identifier category state start end orderby last] :as opts}] + ([{:keys [resource-href linked-identifier category state start end orderby last] event-name :name :as opts}] (some-> event/resource-type (crud/query-as-admin {:cimi-params @@ -162,7 +162,7 @@ (cond-> [] resource-href (conj (str "content/resource/href='" resource-href "'")) (and (contains? opts :resource-href) (nil? resource-href)) (conj (str "content/resource/href=null")) - event-type (conj (str "event-type='" event-type "'")) + event-name (conj (str "name='" event-name "'")) category (conj (str "category='" category "'")) state (conj (str "content/state='" state "'")) linked-identifier (conj (str "content/linked-identifiers='" linked-identifier "'")) diff --git a/code/src/sixsq/nuvla/server/resources/spec/event.cljc b/code/src/sixsq/nuvla/server/resources/spec/event.cljc index 964e90b0b..f32598d3d 100644 --- a/code/src/sixsq/nuvla/server/resources/spec/event.cljc +++ b/code/src/sixsq/nuvla/server/resources/spec/event.cljc @@ -9,13 +9,6 @@ [spec-tools.core :as st])) -(s/def ::event-type - (-> (st/spec string?) - (assoc :name "event-type" - :json-schema/type "string" - :json-schema/description "type of event"))) - - (s/def ::category (-> (st/spec #{"add" "edit" "delete" "action" "state" "alarm" "email" "user"}) (assoc :name "category" @@ -67,13 +60,6 @@ :json-schema/description "link to associated resource"))) -(s/def ::identifier - (-> (st/spec string?) - (assoc :name "event-type" - :json-schema/type "string" - :json-schema/description "type of event"))) - - (s/def ::identifier (-> (st/spec string?) (assoc :name "identifier" @@ -133,8 +119,7 @@ (s/def ::schema (su/only-keys-maps common/common-attrs - {:req-un [::event-type - ::timestamp + {:req-un [::timestamp ::content ::category ::severity diff --git a/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj index d1b6397ba..9bd615865 100644 --- a/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj +++ b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj @@ -3,13 +3,13 @@ [sixsq.nuvla.server.resources.common.event-config :as t])) -(def logged-event {:event-type "resource.add"}) +(def logged-event {:name "resource.add"}) (def not-logged-event {}) -(def disabled-event {:event-type "resource.validate"}) +(def disabled-event {:name "resource.validate"}) (defmethod t/log-event? diff --git a/code/test/sixsq/nuvla/server/resources/event_test.clj b/code/test/sixsq/nuvla/server/resources/event_test.clj index 0df2336c1..35dc6c4d4 100644 --- a/code/test/sixsq/nuvla/server/resources/event_test.clj +++ b/code/test/sixsq/nuvla/server/resources/event_test.clj @@ -18,7 +18,7 @@ (def valid-event {:acl {:owners ["user/joe"]} :authn-info {} - :event-type "legacy" + :name "legacy" :success true :created "2015-01-16T08:05:00.00Z" :updated "2015-01-16T08:05:00.00Z" diff --git a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj index 81ff54ebd..66dc13c81 100644 --- a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj +++ b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj @@ -33,7 +33,7 @@ (let [uuid (u/random-uuid) id (str "resource/" uuid) event (t/build-event context request {:status 201 :body {:resource-id id}})] - (is (= {:event-type "resource.add" + (is (= {:name "resource.add" :category "action" :content {:resource {:href id} :linked-identifiers []} @@ -46,7 +46,7 @@ event)))) (testing "failure" (let [event (t/build-event context request {:status 400})] - (is (= {:event-type "resource.add" + (is (= {:name "resource.add" :category "action" :content {:resource {:href nil} :linked-identifiers []} @@ -71,7 +71,7 @@ (deftest search-event - (doseq [category ["action" "system"] + (doseq [category ["action" "add"] timestamp ["2015-01-16T08:05:00.000Z" "2015-01-17T08:05:00.000Z" (time/now-str)]] (t/create-event "user/1" "hello" {:owners ["group/nuvla-admin"]} :category category diff --git a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj index f646495d5..1eb783976 100644 --- a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj +++ b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj @@ -607,12 +607,12 @@ ;; (defmacro is-last-event - [resource-id {:keys [event-type category authn-info linked-identifiers success acl]}] + [resource-id {:keys [category authn-info linked-identifiers success acl] event-name :name}] `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1})) authn-info# (:authn-info event#)] (is (some? event#)) - (when ~event-type - (is (= ~event-type (:event-type event#)))) + (when ~event-name + (is (= ~event-name (:name event#)))) (when ~category (is (= ~category (:category event#)))) (when ~authn-info diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj index f3b565b80..b013658bf 100644 --- a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj @@ -79,7 +79,7 @@ (ltu/is-status 403)) (ltu/is-last-event nil - {:event-type "module.add" + {:name "module.add" :category "add" :success false :linked-identifiers [] @@ -111,7 +111,7 @@ (ltu/is-status 400)) (ltu/is-last-event nil - {:event-type "module.add" + {:name "module.add" :category "add" :success false :linked-identifiers [] @@ -144,7 +144,7 @@ abs-uri (str p/service-context uri)] (ltu/is-last-event uri - {:event-type "module.add" + {:name "module.add" :category "add" :success true :linked-identifiers [] @@ -177,7 +177,7 @@ (ltu/is-status 403)) (ltu/is-last-event uri - {:event-type "module.edit" + {:name "module.edit" :category "edit" :success false :linked-identifiers [] @@ -194,7 +194,7 @@ (ltu/is-status 200)) (ltu/is-last-event uri - {:event-type "module.edit" + {:name "module.edit" :category "edit" :success true :linked-identifiers [] @@ -241,7 +241,7 @@ (ltu/message-matches "published successfully")) (ltu/is-last-event uri - {:event-type "module.publish" + {:name "module.publish" :category "action" :success true :linked-identifiers [] @@ -273,7 +273,7 @@ (ltu/message-matches "published successfully")) (ltu/is-last-event (str uri "_2") - {:event-type "module.publish" + {:name "module.publish" :category "action" :success true :linked-identifiers [] @@ -298,7 +298,7 @@ (ltu/message-matches "unpublished successfully"))) (ltu/is-last-event uri - {:event-type "module.unpublish" + {:name "module.unpublish" :category "action" :success true :linked-identifiers [] @@ -313,7 +313,7 @@ (ltu/message-matches "published successfully")) (ltu/is-last-event (str uri "_2") - {:event-type "module.publish" + {:name "module.publish" :category "action" :success true :linked-identifiers [] @@ -337,7 +337,7 @@ (ltu/message-matches "unpublished successfully")) (ltu/is-last-event (str uri "_2") - {:event-type "module.unpublish" + {:name "module.unpublish" :category "action" :success true :linked-identifiers [] @@ -386,7 +386,7 @@ (ltu/is-last-event uri - {:event-type "module.delete-version" + {:name "module.delete-version" :category "action" :success true :linked-identifiers [] @@ -420,7 +420,7 @@ (ltu/is-last-event uri - {:event-type "module.delete" + {:name "module.delete" :category "delete" :success true :linked-identifiers [] diff --git a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj index bcc1a2611..4b8f1da2a 100644 --- a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj @@ -143,7 +143,7 @@ (ltu/body->edn) (ltu/is-status 403)) - (ltu/is-last-event uuid {:event-type "session.add" + (ltu/is-last-event uuid {:name "session.add" :category "add" :success false :linked-identifiers [(str "credential/" uuid)] @@ -159,7 +159,7 @@ (ltu/is-set-cookie) (ltu/is-status 201)) id (ltu/body-resource-id resp) - _ (ltu/is-last-event id {:event-type "session.add" + _ (ltu/is-last-event id {:name "session.add" :category "add" :success true :linked-identifiers [(str "credential/" uuid)] diff --git a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj index 4781c47e4..00de4e104 100644 --- a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj @@ -249,7 +249,7 @@ (ltu/is-status 200)) (ltu/is-last-event id - {:event-type "session.delete" + {:name "session.delete" :category "delete" :success true :linked-identifiers [] @@ -331,7 +331,7 @@ sesssion-user-url (ltu/location-url session-user) credential-id (:credential-password (auth-password/user-id->user user-id)) _ (ltu/is-last-event session-user-id - {:event-type "session.add" + {:name "session.add" :category "add" :success true :linked-identifiers [user-id credential-id] @@ -366,7 +366,7 @@ (ltu/is-status 403) (ltu/message-matches #"Switch group cannot be done to requested group:.*")) - (ltu/is-last-event session-user-id {:event-type "session.switch-group" + (ltu/is-last-event session-user-id {:name "session.switch-group" :category "action" :success false :linked-identifiers [group-b] diff --git a/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc b/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc index 0054aa872..95dd05a4a 100644 --- a/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc +++ b/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc @@ -11,7 +11,7 @@ (def valid-event {:id "event/262626262626262" - :event-type "test" + :name "test" :success true :resource-type t/resource-type :created event-timestamp From ef80949626e439b4e3934b0bb334b946649ab006 Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Wed, 23 Aug 2023 15:47:40 +0200 Subject: [PATCH 09/17] use description field to provide human readable label for events --- .../nuvla/server/resources/common/crud.clj | 10 + .../server/resources/common/event_config.clj | 43 +- .../resources/common/event_config.clj.orig | 89 ++ .../common/event_config_BACKUP_29014.clj | 89 ++ .../common/event_config_BACKUP_29324.clj | 89 ++ .../common/event_config_BASE_29014.clj | 41 + .../common/event_config_BASE_29324.clj | 41 + .../common/event_config_LOCAL_29014.clj | 41 + .../common/event_config_LOCAL_29324.clj | 41 + .../common/event_config_REMOTE_29014.clj | 80 ++ .../common/event_config_REMOTE_29324.clj | 80 ++ .../nuvla/server/resources/event/utils.clj | 43 +- .../server/resources/event/utils.clj.orig | 226 +++++ .../sixsq/nuvla/server/resources/session.clj | 31 + .../resources/common/event_config_test.clj | 29 +- .../common/event_config_test.clj.orig | 57 ++ .../server/resources/event_utils_test.clj | 8 +- .../resources/event_utils_test.clj.orig | 98 +++ .../server/resources/lifecycle_test_utils.clj | 4 +- .../resources/lifecycle_test_utils.clj.orig | 639 ++++++++++++++ .../resources/module_lifecycle_test.clj | 21 +- .../resources/module_lifecycle_test.clj.orig | 741 +++++++++++++++++ .../session_api_key_lifecycle_test.clj | 2 + .../session_api_key_lifecycle_test.clj.orig | 263 ++++++ .../session_password_lifecycle_test.clj | 3 + .../session_password_lifecycle_test.clj.orig | 784 ++++++++++++++++++ 26 files changed, 3573 insertions(+), 20 deletions(-) create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config.clj.orig create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29014.clj create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29324.clj create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29014.clj create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29324.clj create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29014.clj create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29324.clj create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29014.clj create mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29324.clj create mode 100644 code/src/sixsq/nuvla/server/resources/event/utils.clj.orig create mode 100644 code/test/sixsq/nuvla/server/resources/common/event_config_test.clj.orig create mode 100644 code/test/sixsq/nuvla/server/resources/event_utils_test.clj.orig create mode 100644 code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig create mode 100644 code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig create mode 100644 code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj.orig create mode 100644 code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj.orig diff --git a/code/src/sixsq/nuvla/server/resources/common/crud.clj b/code/src/sixsq/nuvla/server/resources/common/crud.clj index 3c8079e32..9c1cefbca 100644 --- a/code/src/sixsq/nuvla/server/resources/common/crud.clj +++ b/code/src/sixsq/nuvla/server/resources/common/crud.clj @@ -92,6 +92,16 @@ [resource-id] (retrieve-by-id resource-id {:nuvla/authn auth/internal-identity})) +(defn retrieve-by-id-as-admin1 + "Same as `retrieve-by-id-as-admin` but if the resource is not found returns nil + instead of throwing an exception." + [resource-id] + (try (retrieve-by-id-as-admin resource-id) + (catch Exception ex + (when-not (= 404 (:status (ex-data ex))) + (throw ex))))) + + (defn id->user-request [id request] {:params (u/id->request-params id) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config.clj b/code/src/sixsq/nuvla/server/resources/common/event_config.clj index 9b961a781..0cd894308 100644 --- a/code/src/sixsq/nuvla/server/resources/common/event_config.clj +++ b/code/src/sixsq/nuvla/server/resources/common/event_config.clj @@ -1,4 +1,6 @@ -(ns sixsq.nuvla.server.resources.common.event-config) +(ns sixsq.nuvla.server.resources.common.event-config + (:require [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.utils :as u])) ;; ;; Dispatch functions @@ -8,7 +10,7 @@ resource-type) -(defn event-name-dispatch [{event-name :name :as _event} _response] +(defn event-name-dispatch [{event-name :name :as _event} & rest] event-name) @@ -39,3 +41,40 @@ [{event-name :name :as _event} {:keys [status] :as _response}] (and (not= 405 status) (some? event-name))) + + +;; +;; Event human readable description +;; + +(defmulti event-description + "Returns a human-readable description of the event" + event-type-dispatch) + + +(defmethod event-description :default + [{:keys [success event-type authn-info category content] :as _event}] + (if success + (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) + (:user-id authn-info)) + resource-id (-> content :resource :href) + resource-type (u/id->resource-type resource-id) + resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) + resource-name-or-id (or resource-name resource-id)] + (case category + ("add" "edit" "delete" "action") + (str (or user-name-or-id "An anonymous user") + (case category + "add" (str " added " resource-type " " resource-name-or-id) + "edit" (str " edited " resource-type " " resource-name-or-id) + "delete" (str " deleted " resource-type " " resource-name-or-id) + "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] + (str " executed action " action " on " resource-type " " resource-name-or-id)) + nil) + ".") + ("state" "alarm" "email" "user") + event-type ;; FIXME: improve description in this case + event-type)) + (str event-type " attempt failed."))) + + diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config.clj.orig b/code/src/sixsq/nuvla/server/resources/common/event_config.clj.orig new file mode 100644 index 000000000..84c6846de --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_config.clj.orig @@ -0,0 +1,89 @@ +(ns sixsq.nuvla.server.resources.common.event-config + (:require [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.utils :as u])) + +;; +;; Dispatch functions +;; + +(defn resource-type-dispatch [resource-type] + resource-type) + + +<<<<<<< HEAD +(defn event-name-dispatch [{event-name :name :as _event} _response] + event-name) +======= +(defn event-type-dispatch [{:keys [event-type] :as _event} & _rest] + event-type) +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + + +;; +;; Enabled/disabled events +;; + +(defmulti events-enabled? + "Returns true if events should be logged for the given resource-type, false otherwise." + resource-type-dispatch) + + +(defmethod events-enabled? :default + [_resource-type] + false) + + +;; +;; Whitelist and blacklist event types per resource type +;; + +(defmulti log-event? + "Returns true if the event should be logged, false otherwise." + event-name-dispatch) + + +(defmethod log-event? :default + [{event-name :name :as _event} {:keys [status] :as _response}] + (and (not= 405 status) +<<<<<<< HEAD + (some? event-name))) +======= + (some? event-type))) + + +;; +;; Event human readable description +;; + +(defmulti event-description + "Returns a human-readable description of the event" + event-type-dispatch) + + +(defmethod event-description :default + [{:keys [success event-type authn-info category content] :as _event}] + (if success + (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) + (:user-id authn-info)) + resource-id (-> content :resource :href) + resource-type (u/id->resource-type resource-id) + resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) + resource-name-or-id (or resource-name resource-id)] + (case category + ("add" "edit" "delete" "action") + (str (or user-name-or-id "An anonymous user") + (case category + "add" (str " added " resource-type " " resource-name-or-id) + "edit" (str " edited " resource-type " " resource-name-or-id) + "delete" (str " deleted " resource-type " " resource-name-or-id) + "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] + (str " executed action " action " on " resource-type " " resource-name-or-id)) + nil) + ".") + ("state" "alarm" "email" "user") + event-type ;; FIXME: improve description in this case + event-type)) + (str event-type " attempt failed."))) + + +>>>>>>> bc60a2ab (use description field to provide human readable label for events) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29014.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29014.clj new file mode 100644 index 000000000..84c6846de --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29014.clj @@ -0,0 +1,89 @@ +(ns sixsq.nuvla.server.resources.common.event-config + (:require [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.utils :as u])) + +;; +;; Dispatch functions +;; + +(defn resource-type-dispatch [resource-type] + resource-type) + + +<<<<<<< HEAD +(defn event-name-dispatch [{event-name :name :as _event} _response] + event-name) +======= +(defn event-type-dispatch [{:keys [event-type] :as _event} & _rest] + event-type) +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + + +;; +;; Enabled/disabled events +;; + +(defmulti events-enabled? + "Returns true if events should be logged for the given resource-type, false otherwise." + resource-type-dispatch) + + +(defmethod events-enabled? :default + [_resource-type] + false) + + +;; +;; Whitelist and blacklist event types per resource type +;; + +(defmulti log-event? + "Returns true if the event should be logged, false otherwise." + event-name-dispatch) + + +(defmethod log-event? :default + [{event-name :name :as _event} {:keys [status] :as _response}] + (and (not= 405 status) +<<<<<<< HEAD + (some? event-name))) +======= + (some? event-type))) + + +;; +;; Event human readable description +;; + +(defmulti event-description + "Returns a human-readable description of the event" + event-type-dispatch) + + +(defmethod event-description :default + [{:keys [success event-type authn-info category content] :as _event}] + (if success + (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) + (:user-id authn-info)) + resource-id (-> content :resource :href) + resource-type (u/id->resource-type resource-id) + resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) + resource-name-or-id (or resource-name resource-id)] + (case category + ("add" "edit" "delete" "action") + (str (or user-name-or-id "An anonymous user") + (case category + "add" (str " added " resource-type " " resource-name-or-id) + "edit" (str " edited " resource-type " " resource-name-or-id) + "delete" (str " deleted " resource-type " " resource-name-or-id) + "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] + (str " executed action " action " on " resource-type " " resource-name-or-id)) + nil) + ".") + ("state" "alarm" "email" "user") + event-type ;; FIXME: improve description in this case + event-type)) + (str event-type " attempt failed."))) + + +>>>>>>> bc60a2ab (use description field to provide human readable label for events) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29324.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29324.clj new file mode 100644 index 000000000..84c6846de --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29324.clj @@ -0,0 +1,89 @@ +(ns sixsq.nuvla.server.resources.common.event-config + (:require [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.utils :as u])) + +;; +;; Dispatch functions +;; + +(defn resource-type-dispatch [resource-type] + resource-type) + + +<<<<<<< HEAD +(defn event-name-dispatch [{event-name :name :as _event} _response] + event-name) +======= +(defn event-type-dispatch [{:keys [event-type] :as _event} & _rest] + event-type) +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + + +;; +;; Enabled/disabled events +;; + +(defmulti events-enabled? + "Returns true if events should be logged for the given resource-type, false otherwise." + resource-type-dispatch) + + +(defmethod events-enabled? :default + [_resource-type] + false) + + +;; +;; Whitelist and blacklist event types per resource type +;; + +(defmulti log-event? + "Returns true if the event should be logged, false otherwise." + event-name-dispatch) + + +(defmethod log-event? :default + [{event-name :name :as _event} {:keys [status] :as _response}] + (and (not= 405 status) +<<<<<<< HEAD + (some? event-name))) +======= + (some? event-type))) + + +;; +;; Event human readable description +;; + +(defmulti event-description + "Returns a human-readable description of the event" + event-type-dispatch) + + +(defmethod event-description :default + [{:keys [success event-type authn-info category content] :as _event}] + (if success + (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) + (:user-id authn-info)) + resource-id (-> content :resource :href) + resource-type (u/id->resource-type resource-id) + resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) + resource-name-or-id (or resource-name resource-id)] + (case category + ("add" "edit" "delete" "action") + (str (or user-name-or-id "An anonymous user") + (case category + "add" (str " added " resource-type " " resource-name-or-id) + "edit" (str " edited " resource-type " " resource-name-or-id) + "delete" (str " deleted " resource-type " " resource-name-or-id) + "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] + (str " executed action " action " on " resource-type " " resource-name-or-id)) + nil) + ".") + ("state" "alarm" "email" "user") + event-type ;; FIXME: improve description in this case + event-type)) + (str event-type " attempt failed."))) + + +>>>>>>> bc60a2ab (use description field to provide human readable label for events) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29014.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29014.clj new file mode 100644 index 000000000..3587acae3 --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29014.clj @@ -0,0 +1,41 @@ +(ns sixsq.nuvla.server.resources.common.event-config) + +;; +;; Dispatch functions +;; + +(defn resource-type-dispatch [resource-type] + resource-type) + + +(defn event-type-dispatch [{:keys [event-type] :as _event} _response] + event-type) + + +;; +;; Enabled/disabled events +;; + +(defmulti events-enabled? + "Returns true if events should be logged for the given resource-type, false otherwise." + resource-type-dispatch) + + +(defmethod events-enabled? :default + [_resource-type] + false) + + +;; +;; Whitelist and blacklist event types per resource type +;; + +(defmulti log-event? + "Returns true if the event should be logged, false otherwise." + event-type-dispatch) + + +(defmethod log-event? :default + [{:keys [event-type] :as _event} {:keys [status] :as _response}] + (and (not= 405 status) + (some? event-type))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29324.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29324.clj new file mode 100644 index 000000000..3587acae3 --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29324.clj @@ -0,0 +1,41 @@ +(ns sixsq.nuvla.server.resources.common.event-config) + +;; +;; Dispatch functions +;; + +(defn resource-type-dispatch [resource-type] + resource-type) + + +(defn event-type-dispatch [{:keys [event-type] :as _event} _response] + event-type) + + +;; +;; Enabled/disabled events +;; + +(defmulti events-enabled? + "Returns true if events should be logged for the given resource-type, false otherwise." + resource-type-dispatch) + + +(defmethod events-enabled? :default + [_resource-type] + false) + + +;; +;; Whitelist and blacklist event types per resource type +;; + +(defmulti log-event? + "Returns true if the event should be logged, false otherwise." + event-type-dispatch) + + +(defmethod log-event? :default + [{:keys [event-type] :as _event} {:keys [status] :as _response}] + (and (not= 405 status) + (some? event-type))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29014.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29014.clj new file mode 100644 index 000000000..9b961a781 --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29014.clj @@ -0,0 +1,41 @@ +(ns sixsq.nuvla.server.resources.common.event-config) + +;; +;; Dispatch functions +;; + +(defn resource-type-dispatch [resource-type] + resource-type) + + +(defn event-name-dispatch [{event-name :name :as _event} _response] + event-name) + + +;; +;; Enabled/disabled events +;; + +(defmulti events-enabled? + "Returns true if events should be logged for the given resource-type, false otherwise." + resource-type-dispatch) + + +(defmethod events-enabled? :default + [_resource-type] + false) + + +;; +;; Whitelist and blacklist event types per resource type +;; + +(defmulti log-event? + "Returns true if the event should be logged, false otherwise." + event-name-dispatch) + + +(defmethod log-event? :default + [{event-name :name :as _event} {:keys [status] :as _response}] + (and (not= 405 status) + (some? event-name))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29324.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29324.clj new file mode 100644 index 000000000..9b961a781 --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29324.clj @@ -0,0 +1,41 @@ +(ns sixsq.nuvla.server.resources.common.event-config) + +;; +;; Dispatch functions +;; + +(defn resource-type-dispatch [resource-type] + resource-type) + + +(defn event-name-dispatch [{event-name :name :as _event} _response] + event-name) + + +;; +;; Enabled/disabled events +;; + +(defmulti events-enabled? + "Returns true if events should be logged for the given resource-type, false otherwise." + resource-type-dispatch) + + +(defmethod events-enabled? :default + [_resource-type] + false) + + +;; +;; Whitelist and blacklist event types per resource type +;; + +(defmulti log-event? + "Returns true if the event should be logged, false otherwise." + event-name-dispatch) + + +(defmethod log-event? :default + [{event-name :name :as _event} {:keys [status] :as _response}] + (and (not= 405 status) + (some? event-name))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29014.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29014.clj new file mode 100644 index 000000000..6896d634d --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29014.clj @@ -0,0 +1,80 @@ +(ns sixsq.nuvla.server.resources.common.event-config + (:require [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.utils :as u])) + +;; +;; Dispatch functions +;; + +(defn resource-type-dispatch [resource-type] + resource-type) + + +(defn event-type-dispatch [{:keys [event-type] :as _event} & _rest] + event-type) + + +;; +;; Enabled/disabled events +;; + +(defmulti events-enabled? + "Returns true if events should be logged for the given resource-type, false otherwise." + resource-type-dispatch) + + +(defmethod events-enabled? :default + [_resource-type] + false) + + +;; +;; Whitelist and blacklist event types per resource type +;; + +(defmulti log-event? + "Returns true if the event should be logged, false otherwise." + event-type-dispatch) + + +(defmethod log-event? :default + [{:keys [event-type] :as _event} {:keys [status] :as _response}] + (and (not= 405 status) + (some? event-type))) + + +;; +;; Event human readable description +;; + +(defmulti event-description + "Returns a human-readable description of the event" + event-type-dispatch) + + +(defmethod event-description :default + [{:keys [success event-type authn-info category content] :as _event}] + (if success + (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) + (:user-id authn-info)) + resource-id (-> content :resource :href) + resource-type (u/id->resource-type resource-id) + resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) + resource-name-or-id (or resource-name resource-id)] + (case category + ("add" "edit" "delete" "action") + (str (or user-name-or-id "An anonymous user") + (case category + "add" (str " added " resource-type " " resource-name-or-id) + "edit" (str " edited " resource-type " " resource-name-or-id) + "delete" (str " deleted " resource-type " " resource-name-or-id) + "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] + (str " executed action " action " on " resource-type " " resource-name-or-id)) + nil) + ".") + ("state" "alarm" "email" "user") + event-type ;; FIXME: improve description in this case + event-type)) + (str event-type " attempt failed."))) + + diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29324.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29324.clj new file mode 100644 index 000000000..6896d634d --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29324.clj @@ -0,0 +1,80 @@ +(ns sixsq.nuvla.server.resources.common.event-config + (:require [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.utils :as u])) + +;; +;; Dispatch functions +;; + +(defn resource-type-dispatch [resource-type] + resource-type) + + +(defn event-type-dispatch [{:keys [event-type] :as _event} & _rest] + event-type) + + +;; +;; Enabled/disabled events +;; + +(defmulti events-enabled? + "Returns true if events should be logged for the given resource-type, false otherwise." + resource-type-dispatch) + + +(defmethod events-enabled? :default + [_resource-type] + false) + + +;; +;; Whitelist and blacklist event types per resource type +;; + +(defmulti log-event? + "Returns true if the event should be logged, false otherwise." + event-type-dispatch) + + +(defmethod log-event? :default + [{:keys [event-type] :as _event} {:keys [status] :as _response}] + (and (not= 405 status) + (some? event-type))) + + +;; +;; Event human readable description +;; + +(defmulti event-description + "Returns a human-readable description of the event" + event-type-dispatch) + + +(defmethod event-description :default + [{:keys [success event-type authn-info category content] :as _event}] + (if success + (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) + (:user-id authn-info)) + resource-id (-> content :resource :href) + resource-type (u/id->resource-type resource-id) + resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) + resource-name-or-id (or resource-name resource-id)] + (case category + ("add" "edit" "delete" "action") + (str (or user-name-or-id "An anonymous user") + (case category + "add" (str " added " resource-type " " resource-name-or-id) + "edit" (str " edited " resource-type " " resource-name-or-id) + "delete" (str " deleted " resource-type " " resource-name-or-id) + "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] + (str " executed action " action " on " resource-type " " resource-name-or-id)) + nil) + ".") + ("state" "alarm" "email" "user") + event-type ;; FIXME: improve description in this case + event-type)) + (str event-type " attempt failed."))) + + diff --git a/code/src/sixsq/nuvla/server/resources/event/utils.clj b/code/src/sixsq/nuvla/server/resources/event/utils.clj index a8fe39f61..9fc6b3893 100644 --- a/code/src/sixsq/nuvla/server/resources/event/utils.clj +++ b/code/src/sixsq/nuvla/server/resources/event/utils.clj @@ -4,6 +4,7 @@ [sixsq.nuvla.auth.utils :as auth] [sixsq.nuvla.db.filter.parser :as parser] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-config :as ec] [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.event :as event] [sixsq.nuvla.server.util.time :as t] @@ -103,18 +104,42 @@ linked-identifiers) +(defn get-linked-resource-ids + [{{:keys [linked-identifiers]} :content :as _event} resource-type] + (->> linked-identifiers + (filter (comp #(= resource-type %) u/id->resource-type)))) + + +(defn get-linked-resources + ([{{:keys [linked-identifiers]} :content :as _event}] + (->> linked-identifiers + (keep crud/retrieve-by-id-as-admin1))) + ([{{:keys [linked-identifiers]} :content :as _event} resource-type] + (->> linked-identifiers + (filter (comp #(= resource-type %) u/id->resource-type)) + (keep crud/retrieve-by-id-as-admin1)))) + + +(defn set-description + [event] + (let [event-description (ec/event-description event)] + (cond-> event + event-description (assoc :description event-description)))) + + (defn build-event [context request response] - {:resource-type event/resource-type + (-> {:resource-type event/resource-type :name (get-event-name context request) - :success (get-success response) - :category (get-category context) - :timestamp (get-timestamp context) - :authn-info (auth/current-authentication request) - :acl (get-acl context response) - :severity (get-severity context) - :content {:resource (get-resource context response) - :linked-identifiers (get-linked-identifiers context)}}) + :success (get-success response) + :category (get-category context) + :timestamp (get-timestamp context) + :authn-info (auth/current-authentication request) + :acl (get-acl context response) + :severity (get-severity context) + :content {:resource (get-resource context response) + :linked-identifiers (get-linked-identifiers context)}} + (set-description))) (defn add-event diff --git a/code/src/sixsq/nuvla/server/resources/event/utils.clj.orig b/code/src/sixsq/nuvla/server/resources/event/utils.clj.orig new file mode 100644 index 000000000..65d4e725e --- /dev/null +++ b/code/src/sixsq/nuvla/server/resources/event/utils.clj.orig @@ -0,0 +1,226 @@ +(ns sixsq.nuvla.server.resources.event.utils + (:require + [clojure.string :as str] + [sixsq.nuvla.auth.utils :as auth] + [sixsq.nuvla.db.filter.parser :as parser] + [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-config :as ec] + [sixsq.nuvla.server.resources.common.utils :as u] + [sixsq.nuvla.server.resources.event :as event] + [sixsq.nuvla.server.util.time :as t] + [sixsq.nuvla.server.util.time :as time])) + + +(defn request-event-name + "Returns a string of the form ." + [{{:keys [resource-name uuid action]} :params :as _context} + {:keys [request-method] :as _request}] + (if uuid + (if action + (some->> action (str resource-name ".")) + (case request-method + :put (str resource-name ".edit") + :delete (str resource-name ".delete") + nil)) + (case request-method + :post (str resource-name ".add") + :delete (str resource-name ".bulk.delete") + :patch (some->> action (str resource-name ".bulk.")) + nil))) + + +(defn get-success + [{:keys [status] :as _response}] + (<= 200 status 399)) + + +(defn get-event-name + [{:keys [event-name] :as context} request] + (or event-name + (request-event-name context request))) + + +(defn get-category + [{:keys [category] :as _context}] + (or category "action")) + + +(defn get-timestamp + [{:keys [timestamp] :as _context}] + (or timestamp (t/now-str))) + + +(defn retrieve-by-id + [id] + (try + (:body (crud/retrieve {:params (u/id->request-params id) + :request-method :get + :nuvla/authn auth/internal-identity})) + (catch Exception _ex + nil))) + + +(defn get-resource-href + [{{:keys [resource-name uuid]} :params :as _context} response] + (or (some->> uuid (str resource-name "/")) + (-> response :body :resource-id))) + + +(defn transform-acl + [acl] + (when acl + {:owners (vec (concat (:edit-data acl) (:owners acl)))})) + +(defn derive-acl-from-resource + [context response] + (when-let [acl (some-> (get-resource-href context response) + retrieve-by-id + :acl)] + (transform-acl acl))) + + +(defn get-acl + [{:keys [visible-to acl] :as context} response] + (let [visible-to (remove nil? visible-to)] + (or (when (seq visible-to) + {:owners (-> visible-to (conj "group/nuvla-admin") distinct vec)}) + (transform-acl acl) + (derive-acl-from-resource context response) + {:owners ["group/nuvla-admin"]}))) + + +(defn get-severity + [{:keys [severity] :as _context}] + (or severity "medium")) + + +(defn get-resource + [context response] + {:href (get-resource-href context response)}) + + +(defn get-linked-identifiers + [{:keys [linked-identifiers] :or {linked-identifiers []} :as _context}] + linked-identifiers) + + +(defn get-linked-resource-ids + [{{:keys [linked-identifiers]} :content :as _event} resource-type] + (->> linked-identifiers + (filter (comp #(= resource-type %) u/id->resource-type)))) + + +(defn get-linked-resources + ([{{:keys [linked-identifiers]} :content :as _event}] + (->> linked-identifiers + (keep crud/retrieve-by-id-as-admin1))) + ([{{:keys [linked-identifiers]} :content :as _event} resource-type] + (->> linked-identifiers + (filter (comp #(= resource-type %) u/id->resource-type)) + (keep crud/retrieve-by-id-as-admin1)))) + + +(defn set-description + [event] + (let [event-description (ec/event-description event)] + (cond-> event + event-description (assoc :description event-description)))) + + +(defn build-event + [context request response] +<<<<<<< HEAD + {:resource-type event/resource-type + :name (get-event-name context request) + :success (get-success response) + :category (get-category context) + :timestamp (get-timestamp context) + :authn-info (auth/current-authentication request) + :acl (get-acl context response) + :severity (get-severity context) + :content {:resource (get-resource context response) + :linked-identifiers (get-linked-identifiers context)}}) +======= + (-> {:resource-type event/resource-type + :event-type (get-event-type context request) + :success (get-success response) + :category (get-category context) + :timestamp (get-timestamp context) + :authn-info (auth/current-authentication request) + :acl (get-acl context response) + :severity (get-severity context) + :content {:resource (get-resource context response) + :linked-identifiers (get-linked-identifiers context)}} + (set-description))) +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + + +(defn add-event + [event] + (let [create-request {:params {:resource-name event/resource-type} + :body event + :nuvla/authn auth/internal-identity}] + (crud/add create-request))) + + +(def topic event/resource-type) + + +;; FIXME: duplicated +(defn create-event + [resource-href state acl & {:keys [severity category timestamp] + :or {severity "medium" + category "action"}}] + (let [event-map {:name "legacy" + :success true + :resource-type event/resource-type + :content {:resource {:href resource-href} + :state state} + :severity severity + :category category + :timestamp (or timestamp (time/now-str)) + :acl acl + :authn-info {}} + create-request {:params {:resource-name event/resource-type} + :body event-map + :nuvla/authn auth/internal-identity}] + (crud/add create-request))) + + +(defn query-events + ([resource-href opts] + (query-events (assoc opts :resource-href resource-href))) + ([{:keys [resource-href linked-identifier category state start end orderby last] event-name :name :as opts}] + (some-> event/resource-type + (crud/query-as-admin + {:cimi-params + (cond-> + {:filter (parser/parse-cimi-filter + (str/join " and " + (cond-> [] + resource-href (conj (str "content/resource/href='" resource-href "'")) + (and (contains? opts :resource-href) (nil? resource-href)) (conj (str "content/resource/href=null")) + event-name (conj (str "name='" event-name "'")) + category (conj (str "category='" category "'")) + state (conj (str "content/state='" state "'")) + linked-identifier (conj (str "content/linked-identifiers='" linked-identifier "'")) + start (conj (str "timestamp>='" start "'")) + end (conj (str "timestamp<'" end "'")))))} + orderby (assoc :orderby orderby) + last (assoc :last last))}) + second))) + +;; FIXME: duplicated +(defn search-event + [resource-href {:keys [category state start end]}] + (some-> event/resource-type + (crud/query-as-admin + {:cimi-params + {:filter (parser/parse-cimi-filter + (str/join " and " + (cond-> [(str "content/resource/href='" resource-href "'")] + category (conj (str "category='" category "'")) + state (conj (str "content/state='" state "'")) + start (conj (str "timestamp>='" start "'")) + end (conj (str "timestamp<'" end "'")))))}}) + second)) diff --git a/code/src/sixsq/nuvla/server/resources/session.clj b/code/src/sixsq/nuvla/server/resources/session.clj index 0aa1a2ea2..5b17e3173 100644 --- a/code/src/sixsq/nuvla/server/resources/session.clj +++ b/code/src/sixsq/nuvla/server/resources/session.clj @@ -95,6 +95,7 @@ status, a 'set-cookie' header, and a 'location' header with the created [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.configuration-nuvla :as config-nuvla] [sixsq.nuvla.server.resources.email :as email] + [sixsq.nuvla.server.resources.event.utils :as eu] [sixsq.nuvla.server.resources.group :as group] [sixsq.nuvla.server.resources.resource-metadata :as md] [sixsq.nuvla.server.resources.spec.session :as session] @@ -535,6 +536,36 @@ status, a 'set-cookie' header, and a 'location' header with the created false) +(defmethod ec/event-description "session.add" + [{:keys [success] :as event}] + (if success + (when-let [user-name-or-credential (or (some-> (eu/get-linked-resources event "user") first :name) + (some-> (eu/get-linked-resource-ids event "user") first) + (some-> (eu/get-linked-resources event "credential") first :id) + (some-> (eu/get-linked-resource-ids event "credential") first))] + (str user-name-or-credential " logged in.")) + "Login attempt failed.")) + + +(defmethod ec/event-description "session.delete" + [{:keys [success] {:keys [user-id]} :authn-info :as _event}] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " logged out.")) + "Logout attempt failed.")) + + +(defmethod ec/event-description "session.switch-group" + [{:keys [success] {:keys [user-id]} :authn-info {:keys [linked-identifiers]} :content :as event}] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " switched to group " + (or (some-> (eu/get-linked-resources event) first :name) + (first linked-identifiers)) + ".")) + "Switch group attempt failed.")) + + ;; ;; initialization: no schema for this parent resource ;; diff --git a/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj index 9bd615865..c4f51b1ba 100644 --- a/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj +++ b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj @@ -1,9 +1,14 @@ (ns sixsq.nuvla.server.resources.common.event-config-test (:require [clojure.test :refer [deftest is]] + [sixsq.nuvla.server.resources.common.crud :as crud] [sixsq.nuvla.server.resources.common.event-config :as t])) -(def logged-event {:name "resource.add"}) +(def logged-event {:name "resource.add" + :category "add" + :success true + :authn-info {:user-id "user/12345"} + :content {:resource {:href "resource/12345"}}}) (def not-logged-event {}) @@ -12,6 +17,18 @@ (def disabled-event {:name "resource.validate"}) +(def anon-event {:event-type "resource.add" + :category "add" + :success true + :authn-info {:claims ["group/nuvla-anon"]} + :content {:resource {:href "resource/12345"}}}) + + +(def failure-event {:event-type "resource.add" + :success false}) + + + (defmethod t/log-event? "resource.validate" [_event _response] @@ -24,3 +41,13 @@ (is (false? (t/log-event? disabled-event {}))) (is (false? (t/log-event? logged-event {:status 405})))) + +(deftest event-description + (with-redefs [crud/retrieve-by-id-as-admin + #(case % + "user/12345" {:name "TestUser"} + "resource/12345" {:resource-type "resource" + :name "TestResource"})] + (is (= "TestUser added resource TestResource." (t/event-description logged-event))) + (is (= "An anonymous user added resource TestResource." (t/event-description anon-event))) + (is (= "resource.add attempt failed." (t/event-description failure-event))))) diff --git a/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj.orig b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj.orig new file mode 100644 index 000000000..27dcdbf17 --- /dev/null +++ b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj.orig @@ -0,0 +1,57 @@ +(ns sixsq.nuvla.server.resources.common.event-config-test + (:require [clojure.test :refer [deftest is]] + [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-config :as t])) + + +<<<<<<< HEAD +(def logged-event {:name "resource.add"}) +======= +(def logged-event {:event-type "resource.add" + :category "add" + :success true + :authn-info {:user-id "user/12345"} + :content {:resource {:href "resource/12345"}}}) +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + + +(def not-logged-event {}) + + +(def disabled-event {:name "resource.validate"}) + + +(def anon-event {:event-type "resource.add" + :category "add" + :success true + :authn-info {:claims ["group/nuvla-anon"]} + :content {:resource {:href "resource/12345"}}}) + + +(def failure-event {:event-type "resource.add" + :success false}) + + + +(defmethod t/log-event? + "resource.validate" + [_event _response] + false) + + +(deftest log-event + (is (true? (t/log-event? logged-event {}))) + (is (false? (t/log-event? not-logged-event {}))) + (is (false? (t/log-event? disabled-event {}))) + (is (false? (t/log-event? logged-event {:status 405})))) + + +(deftest event-description + (with-redefs [crud/retrieve-by-id-as-admin + #(case % + "user/12345" {:name "TestUser"} + "resource/12345" {:resource-type "resource" + :name "TestResource"})] + (is (= "TestUser added resource TestResource." (t/event-description logged-event))) + (is (= "An anonymous user added resource TestResource." (t/event-description anon-event))) + (is (= "resource.add attempt failed." (t/event-description failure-event))))) diff --git a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj index 66dc13c81..1ed29f70d 100644 --- a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj +++ b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj @@ -24,7 +24,7 @@ (deftest build-event (with-redefs [time/now-str (constantly "2023-08-17T07:25:57.259Z")] - (let [context {:category "action" + (let [context {:category "add" :params {:resource-name "resource"}} request (req {:nuvla-authn-info "super super group/nuvla-admin" :method :post @@ -34,7 +34,8 @@ id (str "resource/" uuid) event (t/build-event context request {:status 201 :body {:resource-id id}})] (is (= {:name "resource.add" - :category "action" + :category "add" + :description (str "An anonymous user added resource " id ".") :content {:resource {:href id} :linked-identifiers []} :authn-info {} @@ -47,7 +48,8 @@ (testing "failure" (let [event (t/build-event context request {:status 400})] (is (= {:name "resource.add" - :category "action" + :category "add" + :description "resource.add attempt failed." :content {:resource {:href nil} :linked-identifiers []} :authn-info {} diff --git a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj.orig b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj.orig new file mode 100644 index 000000000..510562023 --- /dev/null +++ b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj.orig @@ -0,0 +1,98 @@ +(ns sixsq.nuvla.server.resources.event-utils-test + (:require + [clojure.test :refer [deftest is testing use-fixtures]] + [sixsq.nuvla.server.resources.common.utils :as u] + [sixsq.nuvla.server.resources.event.utils :as t] + [sixsq.nuvla.server.resources.lifecycle-test-utils :as ltu] + [sixsq.nuvla.server.util.time :as time])) + + +(use-fixtures :each ltu/with-test-server-fixture) + + +(defn req + [{:keys [nuvla-authn-info method body]}] + {:request-method method + :params {:resource-name "resource" + :uuid "12345"} + :headers {"nuvla-authn-info" nuvla-authn-info} + :body body}) + + +;; TODO: test getters in sixsq.nuvla.server.resources.event.utils + + +(deftest build-event + (with-redefs [time/now-str (constantly "2023-08-17T07:25:57.259Z")] + (let [context {:category "add" + :params {:resource-name "resource"}} + request (req {:nuvla-authn-info "super super group/nuvla-admin" + :method :post + :body {:k "v"}})] + (testing "success" + (let [uuid (u/random-uuid) + id (str "resource/" uuid) + event (t/build-event context request {:status 201 :body {:resource-id id}})] +<<<<<<< HEAD + (is (= {:name "resource.add" + :category "action" +======= + (is (= {:event-type "resource.add" + :category "add" + :description (str "An anonymous user added resource " id ".") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :content {:resource {:href id} + :linked-identifiers []} + :authn-info {} + :success true + :severity "medium" + :resource-type "event" + :acl {:owners ["group/nuvla-admin"]} + :timestamp "2023-08-17T07:25:57.259Z"} + event)))) + (testing "failure" + (let [event (t/build-event context request {:status 400})] +<<<<<<< HEAD + (is (= {:name "resource.add" + :category "action" +======= + (is (= {:event-type "resource.add" + :category "add" + :description "resource.add attempt failed." +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :content {:resource {:href nil} + :linked-identifiers []} + :authn-info {} + :success false + :severity "medium" + :resource-type "event" + :acl {:owners ["group/nuvla-admin"]} + :timestamp "2023-08-17T07:25:57.259Z"} + event))))))) + + +(deftest add-event + (let [context {:category "action" + :params {:resource-name "resource"}} + request (req {:nuvla-authn-info "super super group/nuvla-admin" + :method :post + :body {:k "v"}}) + event (t/build-event context request {:status 200})] + (let [{:keys [status]} (t/add-event event)] + (is (= 201 status))))) + + +(deftest search-event + (doseq [category ["action" "add"] + timestamp ["2015-01-16T08:05:00.000Z" "2015-01-17T08:05:00.000Z" (time/now-str)]] + (t/create-event "user/1" "hello" {:owners ["group/nuvla-admin"]} + :category category + :timestamp timestamp)) + (is (= 6 (count (t/search-event "user/1" {})))) + (is (= 0 (count (t/search-event "user/2" {})))) + (is (= 3 (count (t/search-event "user/1" {:category "action"})))) + (is (= 6 (count (t/search-event "user/1" {:start "2015-01-16T08:05:00.000Z"})))) + (is (= 2 (count (t/search-event "user/1" {:end "2015-01-16T08:06:00.000Z"})))) + (is (= 1 (count (t/search-event "user/1" {:category "action" + :start "now/d" :end "now+1d/d"}))))) + diff --git a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj index 1eb783976..284fd06f2 100644 --- a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj +++ b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj @@ -607,12 +607,14 @@ ;; (defmacro is-last-event - [resource-id {:keys [category authn-info linked-identifiers success acl] event-name :name}] + [resource-id {:keys [description category authn-info linked-identifiers success acl] event-name :name}] `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1})) authn-info# (:authn-info event#)] (is (some? event#)) (when ~event-name (is (= ~event-name (:name event#)))) + (when ~description + (is (= ~description (:description event#)))) (when ~category (is (= ~category (:category event#)))) (when ~authn-info diff --git a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig new file mode 100644 index 000000000..590c6d7a2 --- /dev/null +++ b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig @@ -0,0 +1,639 @@ +(ns sixsq.nuvla.server.resources.lifecycle-test-utils + (:require + [clojure.data.json :as json] + [clojure.java.io :as io] + [clojure.pprint :refer [pprint]] + [clojure.string :as str] + [clojure.test :refer [is join-fixtures]] + [clojure.tools.logging :as log] + [compojure.core :as cc] + [kinsky.embedded-kraft :as ke] + [me.raynes.fs :as fs] + [peridot.core :refer [request session]] + [qbits.spandex :as spandex] + [ring.middleware.json :refer [wrap-json-body wrap-json-response]] + [ring.middleware.keyword-params :refer [wrap-keyword-params]] + [ring.middleware.nested-params :refer [wrap-nested-params]] + [ring.middleware.params :refer [wrap-params]] + [ring.util.codec :as codec] + [sixsq.nuvla.db.es.binding :as esb] + [sixsq.nuvla.db.es.utils :as esu] + [sixsq.nuvla.db.impl :as db] + [sixsq.nuvla.server.app.params :as p] + [sixsq.nuvla.server.app.routes :as routes] + [sixsq.nuvla.server.middleware.authn-info :refer [wrap-authn-info]] + [sixsq.nuvla.server.middleware.base-uri :refer [wrap-base-uri]] + [sixsq.nuvla.server.middleware.cimi-params :refer [wrap-cimi-params]] + [sixsq.nuvla.server.middleware.exception-handler :refer [wrap-exceptions]] + [sixsq.nuvla.server.middleware.eventer :refer [wrap-eventer]] + [sixsq.nuvla.server.middleware.logger :refer [wrap-logger]] + [sixsq.nuvla.server.resources.common.dynamic-load :as dyn] + [sixsq.nuvla.server.resources.event.utils :as event-utils] + [sixsq.nuvla.server.util.kafka :as ka] + [sixsq.nuvla.server.util.zookeeper :as uzk] + [zookeeper :as zk]) + (:import + (java.util UUID) + (org.apache.curator.test TestingServer) + (org.elasticsearch.common.logging LogConfigurator) + (org.elasticsearch.common.settings Settings) + (org.elasticsearch.index.reindex ReindexPlugin) + (org.elasticsearch.node MockNode) + (org.elasticsearch.painless PainlessPlugin) + (org.elasticsearch.transport Netty4Plugin))) + + +(defn random-string + "provides a random string with optional prefix" + [& [prefix]] + (apply str prefix (repeatedly 15 #(rand-nth "abcdefghijklmnopqrstuvwxyz")))) + + +(defn serialize-cookie-value + "replaces the map cookie value with a serialized string" + [{:keys [value] :as cookie}] + (if value + (assoc cookie :value (codec/form-encode value)) + cookie)) + + +(defmacro message-matches + [m re] + `((fn [m# re#] + (let [message# (get-in m# [:response :body :message])] + (if (string? re#) + (do + (is (.startsWith (or message# "") re#) (str "Message does not start with string. " (or message# "nil") " " re#)) + m#) + (do + (is (re-matches re# message#) (str "Message does not match pattern. " " " re#)) + m#)))) ~m ~re)) + + +(defmacro is-status + [m status] + `((fn [m# status#] + (let [actual# (get-in m# [:response :status])] + (is (= status# actual#) (str "Expecting status " status# " got " (or actual# "nil") ". Message: " + (get-in m# [:response :body :message]))) + m#)) ~m ~status)) + + +(defmacro is-key-value + ([m f k v] + `((fn [m# f# k# v#] + (let [actual# (-> m# :response :body k# f#)] + (is (= v# actual#) (str "Expecting " v# " got " (or actual# "nil") " for " k#)) + m#)) ~m ~f ~k ~v)) + ([m k v] + `(is-key-value ~m identity ~k ~v))) + + +(defmacro has-key + [m k] + `((fn [m# k#] + (is (get-in m# [:response :body k#]) (str "Map did not contain key " k#)) m#) + ~m ~k)) + + +(defmacro is-resource-uri + [m type-uri] + `(is-key-value ~m :resource-type ~type-uri)) + + +(defn href->url + [href] + (when href + (str p/service-context href))) + + +(defn get-op + [m op] + (->> (get-in m [:response :body :operations]) + (map (juxt :rel :href)) + (filter (fn [[rel _]] (= rel (name op)))) + first + second)) + + +(defn get-op-url + [m op] + (href->url (get-op m op))) + + +(defn select-op + [m op] + (let [op-list (get-in m [:response :body :operations]) + defined-ops (map :rel op-list)] + [(some #(= % (name op)) defined-ops) defined-ops])) + + +(defmacro is-operation-present + [m expected-op] + `((fn [m# expected-op#] + (let [[op# defined-ops#] (select-op m# expected-op#)] + (is op# (str "Missing " (name expected-op#) " in " defined-ops#)) + m#)) + ~m ~expected-op)) + + +(defmacro is-operation-absent [m absent-op] + `((fn [m# absent-op#] + (let [[op# defined-ops#] (select-op m# absent-op#)] + (is (nil? op#) (str "Unexpected op " absent-op# " in " defined-ops#))) + m#) + ~m ~absent-op)) + + +(defmacro is-id + [m id] + `(is-key-value ~m :id ~id)) + + +(defmacro is-count + [m f] + `((fn [m# f#] + (let [count# (get-in m# [:response :body :count])] + (is (number? count#) (str "Count is not a number: " count#)) + (when (number? count#) + (if (fn? f#) + (is (f# count#) "Function of count did not return truthy value") + (is (= f# count#) (str "Count wrong, expecting " f# ", got " (or count# "nil"))))) + m#)) ~m ~f)) + + +(defn does-body-contain + [m v] + `((fn [m# v#] + (let [body# (get-in m# [:response :body])] + (is (= (merge body# v#) body#)))) + ~m ~v)) + + +(defmacro is-set-cookie + [m] + `((fn [m#] + (let [cookies# (get-in m# [:response :cookies]) + n# (count cookies#) + token# (-> (vals cookies#) + first + serialize-cookie-value + :value)] + (is (= 1 n#) "incorrect number of cookies") + (is (not= "INVALID" token#) "expecting valid token but got INVALID") + (is (not (str/blank? token#)) "got blank token") + m#)) ~m)) + + +(defmacro is-unset-cookie + [m] + `((fn [m#] + (let [cookies# (get-in m# [:response :cookies]) + n# (count cookies#) + token# (-> (vals cookies#) + first + serialize-cookie-value + :value)] + (is (= 1 n#) "incorrect number of cookies") + (is (= "INVALID" token#) "expecting INVALID but got different value") + (is (not (str/blank? token#)) "got blank token") + m#)) ~m)) + + +(defmacro is-location + [m] + `((fn [m#] + (let [uri-header# (get-in m# [:response :headers "Location"]) + uri-body# (get-in m# [:response :body :resource-id])] + (is uri-header# "Location header was not set") + (is uri-body# "Location (resource-id) in body was not set") + (is (= uri-header# uri-body#) (str "!!!! Mismatch in locations, header=" uri-header# ", body=" uri-body#)) + m#)) ~m)) + + +(defn location + [m] + (let [uri (get-in m [:response :headers "Location"])] + (is uri "Location header missing from response") + uri)) + + +(defn location-url + [m] + (href->url (location m))) + + +(defmacro is-location-value + [m v] + `((fn [m# v#] + (let [location# (location m#)] + (is (= location# v#)))) + ~m ~v)) + + +(defn operations->map + [m] + (into {} (map (juxt :rel :href) (:operations m)))) + + +(defn body + [m] + (get-in m [:response :body])) + + +(defn body-resource-id + [m] + (get-in m [:response :body :resource-id])) + + +(defn body->edn + [m] + (if-let [body-content (body m)] + (let [updated-body (if (string? body-content) + (json/read-str body-content :key-fn keyword :eof-error? false :eof-value {}) + (json/read (io/reader body-content) :key-fn keyword :eof-error? false :eof-value {}))] + (update-in m [:response :body] (constantly updated-body))) + m)) + + +(defn entries + [m] + (some-> m :response :body :resources)) + + +(defn concat-routes + [rs] + (apply cc/routes rs)) + + +(defn dump + [response] + (pprint response) + response) + + +(defn dump-m + [response message] + (println "-->>" message) + (pprint response) + (println message "<<--") + response) + + +(defn refresh-es-indices + [] + (let [client (spandex/client {:hosts ["localhost:9200"]})] + (spandex/request client {:url [:_refresh], :method :post}) + (spandex/close! client))) + + +(defn strip-unwanted-attrs + "Strips common attributes that are not interesting when comparing + versions of a resource." + [m] + (let [unwanted #{:id :resource-type :acl :operations + :created :updated :name :description :tags}] + (into {} (remove #(unwanted (first %)) m)))) + + +;; +;; Handling of Zookeeper server and client +;; + +(defn create-zk-client-server + [] + (let [port 21810] + (log/info "creating zookeeper server on port" port) + (let [server (TestingServer. port) + client (zk/connect (str "127.0.0.1:" port))] + (uzk/set-client! client) + [client server]))) + + +(defonce ^:private zk-client-server-cache (atom nil)) + + +(defn set-zk-client-server-cache + "Sets the value of the cached Elasticsearch node and client. If the current + value is nil, then a new node and a new client are created and cached. If + the value is not nil, then the cache is set to the same value. This returns + the tuple with the node and client, which should never be nil." + [] + ;; Implementation note: It is unfortunate that the atom will constantly be + ;; reset to the current value because swap! is used. Unfortunately, + ;; compare-and-set! can't be used because we want to avoid unnecessary + ;; creation of ring application instances. + (swap! zk-client-server-cache (fn [current] (or current (create-zk-client-server))))) + + +;(defn clear-zk-client-server-cache +; "Unconditionally clears the cached Elasticsearch node and client. Can be +; used to force the re-initialization of the node and client. If the current +; values are not nil, then the node and client will be closed, with errors +; silently ignored." +; [] +; (let [[[client server] _] (swap-vals! zk-client-server-cache (constantly nil))] +; (when client +; (try +; (.close client) +; (catch Exception _))) +; (when server +; (try +; (.close server) +; (catch Exception _))))) + + +;; +;; Handling of Elasticsearch node and client for tests +;; + + +(defn create-test-node + "Creates a local elasticsearch node that holds data that can be access + through the native or HTTP protocols." + ([] + (create-test-node (str (UUID/randomUUID)))) + ([^String cluster-name] + (let [tempDir (str (fs/temp-dir "es-data-")) + settings (.. (Settings/builder) + (put "cluster.name" cluster-name) + (put "action.auto_create_index" true) + (put "path.home" tempDir) + (put "transport.netty.worker_count" 3) + (put "node.data" true) + (put "logger.level" "ERROR") + (put "cluster.routing.allocation.disk.watermark.low" "1gb") + (put "cluster.routing.allocation.disk.watermark.high" "500mb") + (put "cluster.routing.allocation.disk.watermark.flood_stage" "100mb") + (put "http.type" "netty4") + (put "http.port" "9200") + (put "transport.type" "netty4") + (put "network.host" "127.0.0.1") + (build)) + plugins [Netty4Plugin + ReindexPlugin + PainlessPlugin]] + + (LogConfigurator/configureWithoutConfig settings) + (.. (MockNode. ^Settings settings plugins) + (start))))) + + +(defn create-es-node-client + [] + (log/info "creating elasticsearch node and client") + (let [node (create-test-node) + client (-> (esu/create-es-client) + esu/wait-for-cluster) + sniffer (esu/create-es-sniffer client)] + [node client sniffer])) + + +(defonce ^:private es-node-client-cache (atom nil)) + +(defn es-node + [] + (first @es-node-client-cache)) + +(defn es-client + [] + (second @es-node-client-cache)) + +(defn es-sniffer + [] + (nth @es-node-client-cache 2)) + + +(defn set-es-node-client-cache + "Sets the value of the cached Elasticsearch node and client. If the current + value is nil, then a new node and a new client are created and cached. If + the value is not nil, then the cache is set to the same value. This returns + the tuple with the node and client, which should never be nil." + [] + ;; Implementation note: It is unfortunate that the atom will constantly be + ;; reset to the current value because swap! is used. Unfortunately, + ;; compare-and-set! can't be used because we want to avoid unnecessary + ;; creation of ring application instances. + (swap! es-node-client-cache (fn [current] (or current (create-es-node-client))))) + + +(defn clear-es-node-client-cache + "Unconditionally clears the cached Elasticsearch node and client. Can be + used to force the re-initialization of the node and client. If the current + values are not nil, then the node and client will be closed, with errors + silently ignored." + [] + (let [[[node client sniffer] _] (swap-vals! es-node-client-cache (constantly nil))] + (when client + (try + (.close client) + (catch Exception _))) + (when sniffer + (try + (.close sniffer) + (catch Exception _))) + (when node + (try + (.close node) + (catch Exception _))))) + + +(defn profile + [msg f & rest] + (let [ts (System/currentTimeMillis)] + (log/debug (str "--->: " msg)) + (let [res (if rest + (apply f rest) + (f))] + (log/debug (str "--->: " msg " done in: " (- (System/currentTimeMillis) ts))) + res))) + + +(defmacro with-test-es-client + "Creates an Elasticsearch test client, executes the body with the created + client bound to the Elasticsearch client binding, and then clean up the + allocated resources by closing both the client and the node." + [& body] + `(let [[_# client# sniffer#] + (profile "setting es node client cache" set-es-node-client-cache)] + (db/set-impl! (esb/->ElasticsearchRestBinding client# sniffer#)) + ~@body)) + +;; +;; Ring Application Management +;; + +(defn make-ring-app [resource-routes] + (log/info "creating ring application") + (-> resource-routes + wrap-cimi-params + wrap-keyword-params + wrap-nested-params + wrap-params + wrap-base-uri + wrap-exceptions + (wrap-json-body {:keywords? true}) + wrap-eventer + wrap-authn-info + (wrap-json-response {:pretty true :escape-non-ascii true}) + wrap-logger)) + + +(defonce ^:private ring-app-cache (atom nil)) + +(defn set-ring-app-cache + "Sets the value of the cached ring application. If the current value is nil, + then a new ring application is created and cached. If the value is not nil, + then the cache is set to the same value. This returns the ring application + value, which should never be nil." + [] + ;; Implementation note: It is unfortunate that the atom will constantly be + ;; reset to the current value because swap! is used. Unfortunately, + ;; compare-and-set! can't be used because we want to avoid unnecessary + ;; creation of ring application instances. + (swap! ring-app-cache (fn [current] (or current + (make-ring-app (concat-routes [(routes/get-main-routes)])))))) + + +(defn clear-ring-app-cache + "Unconditionally clears the cached ring application instance. Can be used + to force the re-initialization of the ring application." + [] + (reset! ring-app-cache (constantly nil))) + + +(defn ring-app + "Returns a standard ring application with the CIMI server routes. By + default, only a single instance will be created and cached. The cache can be + cleared with the `clean-ring-app-cache` function." + [] + (set-ring-app-cache)) + + +(def kafka-host "127.0.0.1") +(def kafka-port 9093) + + +(defn with-test-kafka-fixture + [f] + (log/debug "executing with-test-kafka-fixture") + (let [log-dir (ke/create-tmp-dir "kraft-combined-logs")] + (let [kafka (profile "start kafka" + ke/start-embedded-kafka + {::ke/host kafka-host + ::ke/port kafka-port + ::ke/log-dirs (str log-dir) + ::ke/server-config {"auto.create.topics.enable" "true" + "transaction.timeout.ms" "5000"}})] + (try + (when (= 0 (count @ka/producers!)) + (profile "create kafka producers" + ka/create-producers! (format "%s:%s" kafka-host kafka-port))) + (profile "run supplied function" f) + (catch Throwable t + (throw t)) + (finally + (ka/close-producers!) + (log/debug "finalising with-test-kafka-fixture") + ;; FIXME: Closing Kafka server takes ~6 sec. Instead of closing Kafka + ;; server, delete all the topics. In case of the last test, the server + ;; will just go down with the JVM. + (let [ts (System/currentTimeMillis)] + (.close kafka) + (log/debug (str "--->: close kafka done in: " + (- (System/currentTimeMillis) ts)))) + (ke/delete-dir log-dir)))))) + + +(def ^:private resources-initialised (atom false)) + + +(defn initialize-indices + [] + (if @resources-initialised + (dyn/initialize-data) + (do + (dyn/initialize) + (reset! resources-initialised true)))) + + +;; +;; test fixture that starts the following parts of the test server: +;; elasticsearch, zookeeper, ring application +;; + +(defn with-test-server-fixture + "This fixture will ensure that Elasticsearch and zookeeper instances are + running. It will also create a ring application and initialize it. The + servers and application are cached to eliminate unnecessary instance + creation for the subsequent tests." + [f] + (log/debug "executing with-test-server-fixture") + (profile "start zookeeper" set-zk-client-server-cache) + (with-test-es-client + (profile "start ring app" ring-app) + (profile "cleanup indices" esu/cleanup-index (es-client) "nuvla-*") + (profile "initialize indices" initialize-indices) + (profile "run supplied function" f))) + + +;; +;; test fixture that starts all parts of the test server including kafka +;; + +(def with-test-server-kafka-fixture (join-fixtures [with-test-server-fixture + with-test-kafka-fixture])) + +;; +;; miscellaneous utilities +;; + +(defn verify-405-status + "The url-methods parameter must be a list of URL/method tuples. It is + expected that any request with the method to the URL will return a 405 + status." + [url-methods] + (doall + (for [[uri method] url-methods] + (-> (ring-app) + session + (request uri + :request-method method + :body (json/write-str {:dummy "value"})) + (is-status 405))))) + +;; +;; events +;; + +(defmacro is-last-event +<<<<<<< HEAD + [resource-id {:keys [category authn-info linked-identifiers success acl] event-name :name}] + `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1})) + authn-info# (:authn-info event#)] + (is (some? event#)) + (when ~event-name + (is (= ~event-name (:name event#)))) +======= + [resource-id {:keys [event-type description category authn-info linked-identifiers success acl]}] + `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1})) + authn-info# (:authn-info event#)] + (is (some? event#)) + (when ~event-type + (is (= ~event-type (:event-type event#)))) + (when ~description + (is (= ~description (:description event#)))) +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + (when ~category + (is (= ~category (:category event#)))) + (when ~authn-info + (is (= (:user-id ~authn-info) (:user-id authn-info#))) + (is (= (:active-claim ~authn-info) (:active-claim authn-info#))) + (is (= (set (:claims ~authn-info)) (set (:claims authn-info#))))) + (when ~linked-identifiers + (is (= (set ~linked-identifiers) (set (get-in event# [:content :linked-identifiers]))))) + (when (some? ~success) + (is (= ~success (:success event#)))) + (when (some? ~acl) + (is (= ~acl (:acl event#)))))) + diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj index b013658bf..f38211bda 100644 --- a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj @@ -68,7 +68,8 @@ authn-info-jane {:user-id "user/jane" :active-claim "user/jane" :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} - authn-info-anon {:claims ["group/nuvla-anon"]}] + authn-info-anon {:claims ["group/nuvla-anon"]} + admin-group-name "Nuvla Administrator Group"] ;; create: NOK for anon (-> session-anon @@ -80,6 +81,7 @@ (ltu/is-last-event nil {:name "module.add" + :description "module.add attempt failed." :category "add" :success false :linked-identifiers [] @@ -112,6 +114,7 @@ (ltu/is-last-event nil {:name "module.add" + :description "module.add attempt failed." :category "add" :success false :linked-identifiers [] @@ -130,9 +133,9 @@ (ltu/message-matches "Application subtype should have compatibility attribute set!")))) ;; adding, retrieving and deleting entry as user should succeed - (doseq [[session event-owners authn-info] - [[session-admin ["group/nuvla-admin"] authn-info-admin] - [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane]]] + (doseq [[session event-owners authn-info user-name-or-id] + [[session-admin ["group/nuvla-admin"] authn-info-admin admin-group-name] + [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane "user/jane"]]] (let [uri (-> session (request base-uri :request-method :post @@ -145,6 +148,7 @@ (ltu/is-last-event uri {:name "module.add" + :description (str user-name-or-id " added module " uri ".") :category "add" :success true :linked-identifiers [] @@ -178,6 +182,7 @@ (ltu/is-last-event uri {:name "module.edit" + :description "module.edit attempt failed." :category "edit" :success false :linked-identifiers [] @@ -195,6 +200,7 @@ (ltu/is-last-event uri {:name "module.edit" + :description (str admin-group-name " edited module " uri ".") :category "edit" :success true :linked-identifiers [] @@ -242,6 +248,7 @@ (ltu/is-last-event uri {:name "module.publish" + :description (str user-name-or-id " executed action publish on module " uri ".") :category "action" :success true :linked-identifiers [] @@ -274,6 +281,7 @@ (ltu/is-last-event (str uri "_2") {:name "module.publish" + :description (str user-name-or-id " executed action publish on module " uri "_2.") :category "action" :success true :linked-identifiers [] @@ -299,6 +307,7 @@ (ltu/is-last-event uri {:name "module.unpublish" + :description (str user-name-or-id " executed action unpublish on module " uri ".") :category "action" :success true :linked-identifiers [] @@ -314,6 +323,7 @@ (ltu/is-last-event (str uri "_2") {:name "module.publish" + :description (str user-name-or-id " executed action publish on module " uri "_2.") :category "action" :success true :linked-identifiers [] @@ -338,6 +348,7 @@ (ltu/is-last-event (str uri "_2") {:name "module.unpublish" + :description (str user-name-or-id " executed action unpublish on module " uri "_2.") :category "action" :success true :linked-identifiers [] @@ -387,6 +398,7 @@ (ltu/is-last-event uri {:name "module.delete-version" + :description (str admin-group-name " executed action delete-version on module " uri ".") :category "action" :success true :linked-identifiers [] @@ -421,6 +433,7 @@ (ltu/is-last-event uri {:name "module.delete" + :description (str admin-group-name " deleted module " uri ".") :category "delete" :success true :linked-identifiers [] diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig new file mode 100644 index 000000000..3d91e3897 --- /dev/null +++ b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig @@ -0,0 +1,741 @@ +(ns sixsq.nuvla.server.resources.module-lifecycle-test + (:require + [clojure.data.json :as json] + [clojure.string :as str] + [clojure.test :refer [deftest is testing use-fixtures]] + [peridot.core :refer [content-type header request session]] + [sixsq.nuvla.server.app.params :as p] + [sixsq.nuvla.server.middleware.authn-info :refer [authn-info-header]] + [sixsq.nuvla.server.resources.common.utils :as u] + [sixsq.nuvla.server.resources.lifecycle-test-utils :as ltu] + [sixsq.nuvla.server.resources.module :as module] + [sixsq.nuvla.server.resources.module.utils :as utils])) + +(use-fixtures :each ltu/with-test-server-fixture) + +(def base-uri (str p/service-context module/resource-type)) + +(def timestamp "1964-08-25T10:00:00.00Z") + +(defn- get-path-segments + [path] + (reduce + (fn [acu cur] + (conj acu (if (seq acu) + (str (last acu) "/" cur) + cur))) + [] + (str/split path #"/"))) + +(defn create-parent-projects [path user] + (let [paths (get-path-segments (utils/get-parent-path path))] + (run! + (fn [path-segment] + (-> user + (request base-uri + :request-method :post + :body (json/write-str {:subtype utils/subtype-project + :path path-segment + :parent-path (utils/get-parent-path path-segment)})) + ltu/body->edn + (ltu/is-status 201))) + paths))) + +(defn lifecycle-test-module + [subtype valid-content] + (let [session-anon (-> (session (ltu/ring-app)) + (content-type "application/json")) + session-admin (header session-anon authn-info-header + "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") + session-user (header session-anon authn-info-header + "user/jane user/jane group/nuvla-user group/nuvla-anon") + + valid-entry {:parent-path "a/b" + :path "a/b/c" + :subtype subtype + + :compatibility "docker-compose" + + :logo-url "https://example.org/logo" + + :data-accept-content-types ["application/json" "application/x-something"] + :data-access-protocols ["http+s3" "posix+nfs"] + + :content valid-content} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:claims ["group/nuvla-anon"]} + admin-group-name "Nuvla Administrator Group"] + + ;; create: NOK for anon + (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 403)) + + (ltu/is-last-event nil +<<<<<<< HEAD + {:name "module.add" +======= + {:event-type "module.add" + :description "module.add attempt failed." +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners ["group/nuvla-admin"]}}) + + ;; queries: NOK for anon + (-> session-anon + (request base-uri) + (ltu/body->edn) + (ltu/is-status 403)) + + (doseq [session [session-admin session-user]] + (-> session + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count zero?))) + + ;; Creating editable parent project + (create-parent-projects (:path valid-entry) session-user) + + ;; invalid module subtype + (-> session-admin + (request base-uri + :request-method :post + :body (json/write-str (assoc valid-entry :subtype "bad-module-subtype"))) + (ltu/body->edn) + (ltu/is-status 400)) + + (ltu/is-last-event nil +<<<<<<< HEAD + {:name "module.add" +======= + {:event-type "module.add" + :description "module.add attempt failed." +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners ["group/nuvla-admin"]}}) + + (when (utils/is-application? valid-entry) + + (testing "application should have compatibility attribute set" + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (dissoc valid-entry :compatibility))) + (ltu/body->edn) + (ltu/is-status 400) + (ltu/message-matches "Application subtype should have compatibility attribute set!")))) + + ;; adding, retrieving and deleting entry as user should succeed + (doseq [[session event-owners authn-info user-name-or-id] + [[session-admin ["group/nuvla-admin"] authn-info-admin admin-group-name] + [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane "user/jane"]]] + (let [uri (-> session + (request base-uri + :request-method :post + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location)) + + abs-uri (str p/service-context uri)] + + (ltu/is-last-event uri +<<<<<<< HEAD + {:name "module.add" +======= + {:event-type "module.add" + :description (str user-name-or-id " added module " uri ".") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + ;; retrieve: NOK for anon + (-> session-anon + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 403)) + + (let [{:keys [content acl]} (-> session-admin + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-key-value :compatibility "docker-compose") + (as-> m (if (utils/is-application? valid-entry) + (ltu/is-operation-present m :validate-docker-compose) + (ltu/is-operation-absent m :validate-docker-compose))) + (ltu/body))] + (is (= valid-content (select-keys content (keys valid-content))))) + + ;; edit: NOK for anon + (-> session-anon + (request abs-uri + :request-method :put + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 403)) + + (ltu/is-last-event uri +<<<<<<< HEAD + {:name "module.edit" +======= + {:event-type "module.edit" + :description "module.edit attempt failed." +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "edit" + :success false + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners event-owners}}) + + ;; insert 5 more versions + (doseq [_ (range 5)] + (-> session-admin + (request abs-uri + :request-method :put + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 200)) + + (ltu/is-last-event uri +<<<<<<< HEAD + {:name "module.edit" +======= + {:event-type "module.edit" + :description (str admin-group-name " edited module " uri ".") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "edit" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners event-owners}})) + + (let [versions (-> session-admin + (request abs-uri + :request-method :put + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + :versions)] + (is (= 7 (count versions))) + + ;; extract by indexes or last + (doseq [[i n] [["_0" 0] ["_1" 1] ["" 6]]] + (let [content-id (-> session-admin + (request (str abs-uri i)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + :content + :id)] + (is (= (-> versions (nth n) :href) content-id)) + (is (= (-> versions (nth n) :author) "someone")) + (is (= (-> versions (nth n) :commit) "wip"))))) + + ;; publish + (let [publish-url (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/get-op-url :publish))] + + (testing "publish last version" + (-> session + (request publish-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event uri +<<<<<<< HEAD + {:name "module.publish" +======= + {:event-type "module.publish" + :description (str user-name-or-id " executed action publish on module " uri ".") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) + + (testing "operation urls of specific version" + (let [abs-uri-v2 (str abs-uri "_2") + resp (-> session + (request (str abs-uri "_2")) + (ltu/body->edn) + (ltu/is-status 200)) + publish-url (ltu/get-op-url resp :publish) + unpublish-url (ltu/get-op-url resp :unpublish) + edit-url (ltu/get-op-url resp :edit) + delete-url (ltu/get-op-url resp :delete) + delete-version-url (ltu/get-op-url resp :delete-version)] + (is (= publish-url (str abs-uri-v2 "/publish"))) + (is (= unpublish-url (str abs-uri-v2 "/unpublish"))) + (is (= delete-version-url (str abs-uri-v2 "/delete-version"))) + (is (= delete-url abs-uri)) + (is (= edit-url abs-uri)))) + + (testing "publish specific version" + (-> session + (request (str abs-uri "_2/publish")) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event (str uri "_2") +<<<<<<< HEAD + {:name "module.publish" +======= + {:event-type "module.publish" + :description (str user-name-or-id " executed action publish on module " uri "_2.") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) + + (let [unpublish-url (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/is-key-value #(-> % last :published) :versions true) + (ltu/is-key-value #(-> % (nth 2) :published) :versions true) + (ltu/is-key-value :published true) + (ltu/get-op-url :unpublish))] + + (-> session + (request unpublish-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "unpublished successfully"))) + + (ltu/is-last-event uri +<<<<<<< HEAD + {:name "module.unpublish" +======= + {:event-type "module.unpublish" + :description (str user-name-or-id " executed action unpublish on module " uri ".") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + ; publish is idempotent + (-> session + (request (str abs-uri "_2/publish")) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event (str uri "_2") +<<<<<<< HEAD + {:name "module.publish" +======= + {:event-type "module.publish" + :description (str user-name-or-id " executed action publish on module " uri "_2.") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/is-key-value #(-> % last :published) :versions false) + (ltu/is-key-value :published true) + (ltu/get-op-url :unpublish)) + + (-> session + (request (str abs-uri "_2/unpublish")) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "unpublished successfully")) + + (ltu/is-last-event (str uri "_2") +<<<<<<< HEAD + {:name "module.unpublish" +======= + {:event-type "module.unpublish" + :description (str user-name-or-id " executed action unpublish on module " uri "_2.") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/is-key-value #(-> % (nth 2) :published) :versions false) + (ltu/is-key-value :published false) + (ltu/get-op-url :unpublish))) + + (testing "edit module without putting the module-content should not create new version" + (is (= 7 (-> session-admin + (request abs-uri + :request-method :put + :body (json/write-str (dissoc valid-entry :content :path))) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + :versions + count)))) + + (doseq [i ["_0/delete-version" "_1/delete-version"]] + (-> session-admin + (request (str abs-uri i)) + (ltu/body->edn) + (ltu/is-status 200)) + + + (-> session-admin + (request (str abs-uri i)) + (ltu/body->edn) + (ltu/is-status 404))) + + + (testing "delete latest version without specifying version" + (-> session-admin + (request (str abs-uri "/delete-version")) + (ltu/body->edn) + (ltu/is-status 200))) + + + (ltu/is-last-event uri +<<<<<<< HEAD + {:name "module.delete-version" +======= + {:event-type "module.delete-version" + :description (str admin-group-name " executed action delete-version on module " uri ".") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners event-owners}}) + + + (testing "delete out of bound index should return 404" + (-> session-admin + (request (str abs-uri "_50/delete-version")) + (ltu/body->edn) + (ltu/is-status 404))) + + (-> session-admin + (request (str abs-uri "_50")) + (ltu/body->edn) + (ltu/is-status 404)) + + ;; delete: NOK for anon + (-> session-anon + (request abs-uri + :request-method :delete) + (ltu/body->edn) + (ltu/is-status 403)) + + (-> session-admin + (request abs-uri + :request-method :delete) + (ltu/body->edn) + (ltu/is-status 200)) + + + (ltu/is-last-event uri +<<<<<<< HEAD + {:name "module.delete" +======= + {:event-type "module.delete" + :description (str admin-group-name " deleted module " uri ".") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners event-owners}}) + + ;; verify that the resource was deleted. + (-> session-admin + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 404)))))) + +(deftest lifecycle-component + (let [valid-component {:author "someone" + :commit "wip" + + :architectures ["amd64" "arm/v6"] + :image {:image-name "ubuntu" + :tag "16.04"} + :ports [{:protocol "tcp" + :target-port 22 + :published-port 8022}]}] + (lifecycle-test-module utils/subtype-comp valid-component))) + + +(deftest lifecycle-application + (let [valid-application {:author "someone" + :commit "wip" + :docker-compose "version: \"3.6\"\n\nx-common: &common\n stop_grace_period: 4s\n logging:\n options:\n max-size: \"250k\"\n max-file: \"10\"\n labels:\n - \"nuvlabox.component=True\"\n - \"nuvlabox.deployment=production\"\n\nvolumes:\n nuvlabox-db:\n driver: local\n\nnetworks:\n nuvlabox-shared-network:\n driver: overlay\n name: nuvlabox-shared-network\n attachable: true\n\nservices:\n data-gateway:\n <<: *common\n image: traefik:2.1.1\n container_name: datagateway\n restart: on-failure\n command:\n - --entrypoints.mqtt.address=:1883\n - --entrypoints.web.address=:80\n - --providers.docker=true\n - --providers.docker.exposedbydefault=false\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n networks:\n - default\n - nuvlabox-shared-network\n\n nb-mosquitto:\n <<: *common\n image: eclipse-mosquitto:1.6.8\n container_name: nbmosquitto\n restart: on-failure\n labels:\n - \"traefik.enable=true\"\n - \"traefik.tcp.routers.mytcprouter.rule=HostSNI(`*`)\"\n - \"traefik.tcp.routers.mytcprouter.entrypoints=mqtt\"\n - \"traefik.tcp.routers.mytcprouter.service=mosquitto\"\n - \"traefik.tcp.services.mosquitto.loadbalancer.server.port=1883\"\n - \"nuvlabox.component=True\"\n - \"nuvlabox.deployment=production\"\n healthcheck:\n test: [\"CMD-SHELL\", \"timeout -t 5 mosquitto_sub -t '$$SYS/#' -C 1 | grep -v Error || exit 1\"]\n interval: 10s\n timeout: 10s\n start_period: 10s\n\n system-manager:\n <<: *common\n image: nuvlabox/system-manager:1.0.1\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n ports:\n - 127.0.0.1:3636:3636\n healthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:3636\"]\n interval: 30s\n timeout: 10s\n retries: 4\n start_period: 10s\n\n agent:\n <<: *common\n image: nuvlabox/agent:1.3.2\n restart: on-failure\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n - NUVLA_ENDPOINT=${NUVLA_ENDPOINT:-nuvla.io}\n - NUVLA_ENDPOINT_INSECURE=${NUVLA_ENDPOINT_INSECURE:-False}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n - /:/rootfs:ro\n expose:\n - 5000\n depends_on:\n - system-manager\n - compute-api\n\n management-api:\n <<: *common\n image: nuvlabox/management-api:0.1.0\n restart: on-failure\n environment:\n - NUVLA_ENDPOINT=${NUVLA_ENDPOINT:-nuvla.io}\n - NUVLA_ENDPOINT_INSECURE=${NUVLA_ENDPOINT_INSECURE:-False}\n volumes:\n - /proc/sysrq-trigger:/sysrq\n - ${HOME}/.ssh/authorized_keys:/rootfs/.ssh/authorized_keys\n - nuvlabox-db:/srv/nuvlabox/shared\n - /var/run/docker.sock:/var/run/docker.sock\n ports:\n - 5001:5001\n healthcheck:\n test: curl -k https://localhost:5001 2>&1 | grep SSL\n interval: 20s\n timeout: 10s\n start_period: 30s\n\n compute-api:\n <<: *common\n image: nuvlabox/compute-api:0.2.5\n restart: on-failure\n pid: \"host\"\n environment:\n - HOST=${HOSTNAME:-nuvlabox}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n ports:\n - 5000:5000\n depends_on:\n - system-manager\n\n network-manager:\n <<: *common\n image: nuvlabox/network-manager:0.0.4\n restart: on-failure\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n - VPN_INTERFACE_NAME=${NUVLABOX_VPN_IFACE:-vpn}\n volumes:\n - nuvlabox-db:/srv/nuvlabox/shared\n depends_on:\n - system-manager\n\n vpn-client:\n <<: *common\n image: nuvlabox/vpn-client:0.0.4\n container_name: vpn-client\n restart: always\n network_mode: host\n cap_add:\n - NET_ADMIN\n devices:\n - /dev/net/tun\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n volumes:\n - nuvlabox-db:/srv/nuvlabox/shared\n depends_on:\n - network-manager"}] + + (lifecycle-test-module utils/subtype-app valid-application))) + +(deftest lifecycle-creating-applications + (let [session-anon (-> (session (ltu/ring-app)) + (content-type "application/json")) + session-admin (header session-anon authn-info-header + "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") + session-user (header session-anon authn-info-header + "user/jane user/jane group/nuvla-user group/nuvla-anon") + + project {:resource-type module/resource-type + :created timestamp + :updated timestamp + :parent-path "" + :path "example" + :subtype utils/subtype-project} + + valid-app {:parent-path "example" + :path "example/app" + :subtype utils/subtype-app + :compatibility "docker-compose" + :logo-url "https://example.org/logo" + :data-accept-content-types ["application/json" "application/x-something"] + :data-access-protocols ["http+s3" "posix+nfs"] + :content {:author "someone" + :commit "wip" + :docker-compose "version: \"3.6\"\n\nx-common: &common\n stop_grace_period: 4s\n logging:\n options:\n max-size: \"250k\"\n max-file: \"10\"\n labels:\n - \"nuvlabox.component=True\"\n - \"nuvlabox.deployment=production\"\n\nvolumes:\n nuvlabox-db:\n driver: local\n\nnetworks:\n nuvlabox-shared-network:\n driver: overlay\n name: nuvlabox-shared-network\n attachable: true\n\nservices:\n data-gateway:\n <<: *common\n image: traefik:2.1.1\n container_name: datagateway\n restart: on-failure\n command:\n - --entrypoints.mqtt.address=:1883\n - --entrypoints.web.address=:80\n - --providers.docker=true\n - --providers.docker.exposedbydefault=false\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n networks:\n - default\n - nuvlabox-shared-network\n\n nb-mosquitto:\n <<: *common\n image: eclipse-mosquitto:1.6.8\n container_name: nbmosquitto\n restart: on-failure\n labels:\n - \"traefik.enable=true\"\n - \"traefik.tcp.routers.mytcprouter.rule=HostSNI(`*`)\"\n - \"traefik.tcp.routers.mytcprouter.entrypoints=mqtt\"\n - \"traefik.tcp.routers.mytcprouter.service=mosquitto\"\n - \"traefik.tcp.services.mosquitto.loadbalancer.server.port=1883\"\n - \"nuvlabox.component=True\"\n - \"nuvlabox.deployment=production\"\n healthcheck:\n test: [\"CMD-SHELL\", \"timeout -t 5 mosquitto_sub -t '$$SYS/#' -C 1 | grep -v Error || exit 1\"]\n interval: 10s\n timeout: 10s\n start_period: 10s\n\n system-manager:\n <<: *common\n image: nuvlabox/system-manager:1.0.1\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n ports:\n - 127.0.0.1:3636:3636\n healthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:3636\"]\n interval: 30s\n timeout: 10s\n retries: 4\n start_period: 10s\n\n agent:\n <<: *common\n image: nuvlabox/agent:1.3.2\n restart: on-failure\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n - NUVLA_ENDPOINT=${NUVLA_ENDPOINT:-nuvla.io}\n - NUVLA_ENDPOINT_INSECURE=${NUVLA_ENDPOINT_INSECURE:-False}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n - /:/rootfs:ro\n expose:\n - 5000\n depends_on:\n - system-manager\n - compute-api\n\n management-api:\n <<: *common\n image: nuvlabox/management-api:0.1.0\n restart: on-failure\n environment:\n - NUVLA_ENDPOINT=${NUVLA_ENDPOINT:-nuvla.io}\n - NUVLA_ENDPOINT_INSECURE=${NUVLA_ENDPOINT_INSECURE:-False}\n volumes:\n - /proc/sysrq-trigger:/sysrq\n - ${HOME}/.ssh/authorized_keys:/rootfs/.ssh/authorized_keys\n - nuvlabox-db:/srv/nuvlabox/shared\n - /var/run/docker.sock:/var/run/docker.sock\n ports:\n - 5001:5001\n healthcheck:\n test: curl -k https://localhost:5001 2>&1 | grep SSL\n interval: 20s\n timeout: 10s\n start_period: 30s\n\n compute-api:\n <<: *common\n image: nuvlabox/compute-api:0.2.5\n restart: on-failure\n pid: \"host\"\n environment:\n - HOST=${HOSTNAME:-nuvlabox}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n ports:\n - 5000:5000\n depends_on:\n - system-manager\n\n network-manager:\n <<: *common\n image: nuvlabox/network-manager:0.0.4\n restart: on-failure\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n - VPN_INTERFACE_NAME=${NUVLABOX_VPN_IFACE:-vpn}\n volumes:\n - nuvlabox-db:/srv/nuvlabox/shared\n depends_on:\n - system-manager\n\n vpn-client:\n <<: *common\n image: nuvlabox/vpn-client:0.0.4\n container_name: vpn-client\n restart: always\n network_mode: host\n cap_add:\n - NET_ADMIN\n devices:\n - /dev/net/tun\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n volumes:\n - nuvlabox-db:/srv/nuvlabox/shared\n depends_on:\n - network-manager"}}] + + (testing "Failure creating application 1: no parent project is specified" + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (-> valid-app + (assoc :parent-path "") + (assoc :path "app")))) + ltu/body->edn + (ltu/is-status 400) + (ltu/message-matches "Application subtype must have a parent project!"))) + + (testing "Failure creating application 2: specified parent project does not exist" + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (assoc valid-app :path "non-existent-parent/path"))) + ltu/body->edn + (ltu/is-status 400) + (ltu/message-matches "No parent project found for path: non-existent-parent"))) + + (testing "Failure creating application 3: user does not have edit rights in parent project" + ;; Creating a parent project with nuvla-admin as owner + (let [uri (-> session-admin + (request base-uri + :request-method :post + :body (json/write-str project)) + ltu/body->edn + (ltu/is-status 201) + ltu/location-url)] + + ;; If user has no view rights, failure message says that parent project does not exist. + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str valid-app)) + ltu/body->edn + (ltu/is-status 400) + (ltu/message-matches "No parent project found for path: example")) + + ;; Adding view rights for user + (-> session-admin + (request uri + :request-method :put + :body (json/write-str + (assoc project + :acl {:owners ["group/nuvla-admin"] + :view-meta ["user/jane"] + :view-data ["user/jane"] + :view-acl ["user/jane"]}))) + ltu/body->edn + (ltu/is-status 200))) + + ;; If user has view rights, message says user lacks edit rights for parent project. + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str valid-app)) + ltu/body->edn + (ltu/is-status 403) + (ltu/message-matches "You do not have edit rights for:"))) + + ;; Trying to add app to parent app should fail + (testing "Failure creating application 4: Parent is not a project." + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (assoc project :path "example2"))) + ltu/body->edn + (ltu/is-status 201)) + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (assoc valid-app :path "example2/app"))) + ltu/body->edn + (ltu/is-status 201)) + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (assoc valid-app :path "example2/app/not-allowed"))) + ltu/body->edn + (ltu/is-status 403) + (ltu/message-matches "Parent must be a project!"))) + + (testing "new application can be in a project nested inside another project" + ;; Creating a parent project with wrong edit rights + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (assoc project :path "grandparent"))) + ltu/body->edn + (ltu/is-status 201)) + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (assoc project :path "grandparent/parent"))) + ltu/body->edn + (ltu/is-status 201)) + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (assoc valid-app :path "grandparent/parent/app"))) + ltu/body->edn + (ltu/is-status 201))) + + (testing "new application can not be top-level is also applied to admin-users" + (-> session-admin + (request base-uri + :request-method :post + :body (json/write-str (assoc valid-app :path "fails"))) + ltu/body->edn + (ltu/is-status 400) + (ltu/message-matches "Application subtype must have a parent project!"))))) + +(def valid-applications-sets-content + {:author "someone" + :commit "wip" + :applications-sets [{:name "x" + :applications [{:id "module/x" + :version 0}]}]}) + +(deftest lifecycle-applications-sets + (lifecycle-test-module utils/subtype-apps-sets valid-applications-sets-content)) + + +(deftest lifecycle-applications-sets-extended + (let [session-anon (-> (session (ltu/ring-app)) + (content-type "application/json")) + session-user (header session-anon authn-info-header + "user/jane user/jane group/nuvla-user group/nuvla-anon") + + valid-app-1 {:parent-path "a/b" + :path "clara/app-1" + :subtype utils/subtype-app + :compatibility "docker-compose" + :content {:author "someone" + :commit "initial" + :docker-compose "some content"}} + _project (create-parent-projects (:path valid-app-1) session-user) + app-1-create-resp (-> session-user + (request base-uri + :request-method :post + :body (json/write-str valid-app-1)) + (ltu/body->edn) + (ltu/is-status 201)) + app-1-uri (ltu/location-url app-1-create-resp) + app-1-id (ltu/location app-1-create-resp)] + + (let [valid-entry {:parent-path "a/b" + :path "a/b/c" + :subtype utils/subtype-apps-sets + :content (assoc-in valid-applications-sets-content + [:applications-sets 0 + :applications 0 :id] app-1-id)}] + + (-> session-user + (request app-1-uri + :request-method :put + :body (json/write-str + (update valid-app-1 :content assoc + :docker-compose "content changed" + :commit "second commit"))) + (ltu/body->edn) + (ltu/is-status 200)) + + (create-parent-projects (:path valid-entry) session-user) + (let [response (-> session-user + (request base-uri + :request-method :post + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 201)) + uri (ltu/location response) + abs-uri (ltu/location-url response) + deploy-uri (-> session-user + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/get-op-url :deploy))] + (-> session-user + (request deploy-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-key-value :application uri) + (ltu/is-key-value :version 0) + (ltu/is-key-value #(-> % + first + :applications + first + :resolved + :content + :docker-compose) + :applications-sets + "some content")))))) + + +(deftest bad-methods + (let [resource-uri (str p/service-context (u/new-resource-id module/resource-type))] + (ltu/verify-405-status [[base-uri :delete] + [resource-uri :post]]))) diff --git a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj index 4b8f1da2a..282306561 100644 --- a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj @@ -144,6 +144,7 @@ (ltu/is-status 403)) (ltu/is-last-event uuid {:name "session.add" + :description "Login attempt failed." :category "add" :success false :linked-identifiers [(str "credential/" uuid)] @@ -160,6 +161,7 @@ (ltu/is-status 201)) id (ltu/body-resource-id resp) _ (ltu/is-last-event id {:name "session.add" + :description (str (:id valid-api-key) " logged in.") :category "add" :success true :linked-identifiers [(str "credential/" uuid)] diff --git a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj.orig b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj.orig new file mode 100644 index 000000000..269d1a0b1 --- /dev/null +++ b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj.orig @@ -0,0 +1,263 @@ +(ns sixsq.nuvla.server.resources.session-api-key-lifecycle-test + (:require + [clojure.data.json :as json] + [clojure.string :as str] + [clojure.test :refer [are deftest is use-fixtures]] + [peridot.core :refer [content-type header request session]] + [sixsq.nuvla.auth.cookies :as cookies] + [sixsq.nuvla.auth.utils.sign :as sign] + [sixsq.nuvla.server.app.params :as p] + [sixsq.nuvla.server.middleware.authn-info :refer [authn-cookie authn-info-header]] + [sixsq.nuvla.server.resources.common.utils :as u] + [sixsq.nuvla.server.resources.credential-template-api-key :as api-key-tpl] + [sixsq.nuvla.server.resources.credential.key-utils :as key-utils] + [sixsq.nuvla.server.resources.lifecycle-test-utils :as ltu] + [sixsq.nuvla.server.resources.session :as session] + [sixsq.nuvla.server.resources.session-api-key :as t] + [sixsq.nuvla.server.resources.session-template :as st] + [sixsq.nuvla.server.resources.session-template-api-key :as api-key] + [sixsq.nuvla.server.util.time :as time])) + +(use-fixtures :once ltu/with-test-server-fixture) + +(def base-uri (str p/service-context session/resource-type)) + +(def session-template-base-uri (str p/service-context st/resource-type)) + + +(def session-template-api-key {:method api-key/authn-method + :instance api-key/authn-method + :name "API Key" + :description "Authentication with API Key and Secret" + :key "key" + :secret "secret" + :acl st/resource-acl}) + +(deftest check-uuid->id + (let [uuid (u/random-uuid) + correct-id (str "credential/" uuid)] + (is (= correct-id (t/uuid->id uuid))) + (is (= correct-id (t/uuid->id correct-id))))) + +(deftest check-valid-api-key + (let [subtype api-key-tpl/credential-subtype + expired (time/to-str (time/ago 10 :seconds)) + current (time/to-str (time/from-now 1 :hours)) + [secret digest] (key-utils/generate) + [_ bad-digest] (key-utils/generate) + valid-api-key {:subtype subtype + :expiry current + :digest digest}] + (is (true? (t/valid-api-key? valid-api-key secret))) + (are [v] (true? (t/valid-api-key? v secret)) + valid-api-key + (dissoc valid-api-key :expiry)) + (are [v] (false? (t/valid-api-key? v secret)) + {} + (dissoc valid-api-key :subtype) + (assoc valid-api-key :subtype "incorrect-subtype") + (assoc valid-api-key :expiry expired) + (assoc valid-api-key :digest bad-digest)) + (is (false? (t/valid-api-key? valid-api-key "bad-secret"))))) + +(deftest check-create-claims + (let [user-id "user/root" + server "nuvla.io" + headers {:nuvla-ssl-server-name server} + claims #{"user/root" "group/nuvla-user" "group/nuvla-anon"} + session-id "session/72e9f3d8-805a-421b-b3df-86f1af294233" + client-ip "127.0.0.1"] + (is (= {:client-ip "127.0.0.1" + :claims (str "group/nuvla-anon group/nuvla-user user/root " session-id) + :user-id "user/root" + :server "nuvla.io" + :session "session/72e9f3d8-805a-421b-b3df-86f1af294233"} + (cookies/create-cookie-info user-id + :claims claims + :headers headers + :session-id session-id + :client-ip client-ip))))) + + +(deftest lifecycle + + (let [[secret digest] (key-utils/generate) + [_ bad-digest] (key-utils/generate) + uuid (u/random-uuid) + valid-api-key {:id (str "credential/" uuid) + :subtype api-key-tpl/credential-subtype + :method api-key-tpl/method + :expiry (time/to-str (time/from-now 1 :hours)) + :digest digest + :claims {:identity "user/abcdef01-abcd-abcd-abcd-abcdef012345" + :roles ["group/nuvla-user" "group/nuvla-anon"]}} + mock-retrieve-by-id {(:id valid-api-key) valid-api-key + uuid valid-api-key}] + + (with-redefs [t/retrieve-credential-by-id mock-retrieve-by-id] + + ;; check that the mocking is working correctly + (is (= valid-api-key (t/retrieve-credential-by-id (:id valid-api-key)))) + (is (= valid-api-key (t/retrieve-credential-by-id uuid))) + + (let [app (ltu/ring-app) + session-json (content-type (session app) "application/json") + session-anon (header session-json authn-info-header "user/unknown user/unknown group/nuvla-anon") + session-user (header session-json authn-info-header "user/user group/nuvla-user group/nuvla-anon") + session-admin (header session-json authn-info-header + "group/nuvla-admin group/nuvla-user group/nuvla-anon") + + ;; + ;; create the session template to use for these tests + ;; + href (str st/resource-type "/api-key") + + name-attr "name" + description-attr "description" + tags-attr ["one", "two"] + + valid-create {:name name-attr + :description description-attr + :tags tags-attr + :template {:href href + :key uuid + :secret secret}} + unauthorized-create (update-in valid-create [:template :secret] (constantly bad-digest)) + invalid-create (assoc-in valid-create [:template :invalid] "BAD") + event-authn-info {:user-id "user/unknown" + :active-claim "user/unknown" + :claims ["user/unknown" "group/nuvla-anon"]}] + + ;; anonymous query should succeed but have no entries + (-> session-anon + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count zero?)) + + ;; unauthorized create must return a 403 response + (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str unauthorized-create)) + (ltu/body->edn) + (ltu/is-status 403)) + +<<<<<<< HEAD + (ltu/is-last-event uuid {:name "session.add" +======= + (ltu/is-last-event uuid {:event-type "session.add" + :description "Login attempt failed." +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "add" + :success false + :linked-identifiers [(str "credential/" uuid)] + :authn-info event-authn-info + :acl {:owners ["group/nuvla-admin"]}}) + + ;; anonymous create must succeed; also with redirect + (let [resp (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str valid-create)) + (ltu/body->edn) + (ltu/is-set-cookie) + (ltu/is-status 201)) + id (ltu/body-resource-id resp) +<<<<<<< HEAD + _ (ltu/is-last-event id {:name "session.add" +======= + _ (ltu/is-last-event id {:event-type "session.add" + :description (str (:id valid-api-key) " logged in.") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "add" + :success true + :linked-identifiers [(str "credential/" uuid)] + :authn-info event-authn-info + :acl {:owners ["group/nuvla-admin" id]}}) + + token (get-in resp [:response :cookies authn-cookie :value]) + cookie-info (if token (sign/unsign-cookie-info token) {}) + + uri (-> resp + (ltu/location)) + abs-uri (str p/service-context uri)] + + ;; check cookie-info in cookie + (is (= "user/abcdef01-abcd-abcd-abcd-abcdef012345" (:user-id cookie-info))) + (is (= (str/join " " ["group/nuvla-anon" "group/nuvla-user" uri]) (:claims cookie-info))) ;; uri is also session id + (is (= uri (:session cookie-info))) ;; uri is also session id + (is (not (nil? (:exp cookie-info)))) + + ;; user should not be able to see session without session role + (-> session-user + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 403)) + + ;; anonymous query should succeed but still have no entries + (-> session-anon + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count zero?)) + + ;; user query should succeed but have no entries because of missing session role + (-> session-user + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count zero?)) + + ;; admin query should succeed, but see no sessions without the correct session role + (-> session-admin + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count 0)) + + ;; user should be able to see session with session role + (-> (session app) + (header authn-info-header (str "user/user group/nuvla-user " id)) + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-id id) + (ltu/is-operation-present :delete) + (ltu/is-operation-absent :edit)) + + ;; user query with session role should succeed but and have one entry + (-> (session app) + (header authn-info-header (str "user/user group/nuvla-user " id)) + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count 1)) + + ;; check contents of session resource + (let [{:keys [name description tags]} (-> (session app) + (header authn-info-header (str "user/user group/nuvla-user " id)) + (request abs-uri) + (ltu/body->edn) + :response + :body)] + (is (= name name-attr)) + (is (= description description-attr)) + (is (= tags tags-attr))) + + ;; user with session role can delete resource + (-> (session app) + (header authn-info-header (str "user/user group/nuvla-user " id)) + (request abs-uri + :request-method :delete) + (ltu/is-unset-cookie) + (ltu/body->edn) + (ltu/is-status 200)) + + ;; create with invalid template fails + (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str invalid-create)) + (ltu/body->edn) + (ltu/is-status 400))))))) diff --git a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj index 00de4e104..3b21871d6 100644 --- a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj @@ -250,6 +250,7 @@ (ltu/is-last-event id {:name "session.delete" + :description (str "user/user logged out.") :category "delete" :success true :linked-identifiers [] @@ -332,6 +333,7 @@ credential-id (:credential-password (auth-password/user-id->user user-id)) _ (ltu/is-last-event session-user-id {:name "session.add" + :description (str username " logged in.") :category "add" :success true :linked-identifiers [user-id credential-id] @@ -367,6 +369,7 @@ (ltu/message-matches #"Switch group cannot be done to requested group:.*")) (ltu/is-last-event session-user-id {:name "session.switch-group" + :description "Switch group attempt failed." :category "action" :success false :linked-identifiers [group-b] diff --git a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj.orig b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj.orig new file mode 100644 index 000000000..3a9d87b3a --- /dev/null +++ b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj.orig @@ -0,0 +1,784 @@ +(ns sixsq.nuvla.server.resources.session-password-lifecycle-test + (:require + [clojure.data.json :as json] + [clojure.string :as str] + [clojure.test :refer [deftest is testing use-fixtures]] + [peridot.core :refer [content-type header request session]] + [postal.core :as postal] + [sixsq.nuvla.auth.password :as auth-password] + [sixsq.nuvla.auth.utils :as auth] + [sixsq.nuvla.auth.utils.sign :as sign] + [sixsq.nuvla.server.app.params :as p] + [sixsq.nuvla.server.middleware.authn-info + :refer [authn-cookie authn-info-header wrap-authn-info]] + [sixsq.nuvla.server.resources.configuration-nuvla :as config-nuvla] + [sixsq.nuvla.server.resources.email.sending :as email-sending] + [sixsq.nuvla.server.resources.group :as group] + [sixsq.nuvla.server.resources.group-template :as group-tpl] + [sixsq.nuvla.server.resources.lifecycle-test-utils :as ltu] + [sixsq.nuvla.server.resources.nuvlabox :as nuvlabox] + [sixsq.nuvla.server.resources.session :as session] + [sixsq.nuvla.server.resources.session-template :as st] + [sixsq.nuvla.server.resources.user :as user] + [sixsq.nuvla.server.resources.user-template :as user-tpl] + [sixsq.nuvla.server.resources.user-template-email-password :as email-password])) + + +(use-fixtures :once ltu/with-test-server-fixture) + + +(def base-uri (str p/service-context session/resource-type)) +(def grp-base-uri (str p/service-context group/resource-type)) +(def nb-base-uri (str p/service-context nuvlabox/resource-type)) + +(defn create-user + [session-admin & {:keys [username password email activated?]}] + (let [validation-link (atom nil) + href (str user-tpl/resource-type "/" email-password/registration-method) + href-create {:template {:href href + :password password + :username username + :email email}}] + + (with-redefs [email-sending/extract-smtp-cfg + (fn [_] {:host "smtp@example.com" + :port 465 + :ssl true + :user "admin" + :pass "password"}) + + ;; WARNING: This is a fragile! Regex matching to recover callback URL. + postal/send-message (fn [_ {:keys [body]}] + (let [url (->> body second :content + (re-matches #"(?s).*visit:\n\n\s+(.*?)\n.*") + second)] + (reset! validation-link url)) + {:code 0, :error :SUCCESS, :message "OK"})] + + (let [user-id (-> session-admin + (request (str p/service-context user/resource-type) + :request-method :post + :body (json/write-str href-create)) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location))] + + (when activated? + (is (re-matches #"^email.*successfully validated$" + (-> session-admin + (request @validation-link) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + :message)))) + user-id)))) + +(defn valid-create-grp + [group-id] + {:template {:href "group-template/generic" + :group-identifier group-id + :name (str "Group " group-id) + :description (str "Group " group-id " description")}}) + +(deftest lifecycle + + (let [app (ltu/ring-app) + session-json (content-type (session app) "application/json") + session-anon (header session-json authn-info-header "user/unknown user/unknown group/nuvla-anon") + session-user (header session-json authn-info-header "user group/nuvla-user") + session-admin (header session-json authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") + + href (str st/resource-type "/password") + + template-url (str p/service-context href) + + name-attr "name" + description-attr "description" + tags-attr ["one", "two"]] + + ;; password session template should exist + (-> session-anon + (request template-url) + (ltu/body->edn) + (ltu/is-status 200)) + + + ;; anon without valid user can not create session + (let [username "anon" + plaintext-password "anon" + + valid-create {:name name-attr + :description description-attr + :tags tags-attr + :template {:href href + :username username + :password plaintext-password}} + unauthorized-create (update-in valid-create [:template :password] (constantly "BAD"))] + + ; anonymous query should succeed but have no entries + (-> session-anon + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count zero?)) + + ; unauthorized create must return a 403 response + (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str unauthorized-create)) + (ltu/body->edn) + (ltu/is-status 403)) + ) + + + ;; anon with valid activated user can create session + (let [username "user/jane" + plaintext-password "JaneJane-0" + + valid-create {:name name-attr + :description description-attr + :tags tags-attr + :template {:href href + :username username + :password plaintext-password}} + + invalid-create (assoc-in valid-create [:template :invalid] "BAD") + jane-user-id (create-user session-admin + :username username + :password plaintext-password + :activated? true + :email "jane@example.org")] + + ; anonymous create must succeed + (let [resp (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str valid-create)) + (ltu/body->edn) + (ltu/is-set-cookie) + (ltu/is-status 201)) + id (ltu/body-resource-id resp) + + token (get-in resp [:response :cookies authn-cookie :value]) + authn-info (if token (sign/unsign-cookie-info token) {}) + event-authn-info {:user-id "user/user" + :active-claim "group/nuvla-user" + :claims ["group/nuvla-anon" id "user/user"]} + + uri (ltu/location resp) + abs-uri (str p/service-context uri)] + + ; check claims in cookie + (is (= jane-user-id (:user-id authn-info))) + (is (= #{"group/nuvla-user" + "group/nuvla-anon" + uri + jane-user-id} + (some-> authn-info + :claims + (str/split #"\s") + set))) + (is (= uri (:session authn-info))) + (is (not (nil? (:exp authn-info)))) + + ; user should not be able to see session without session role + (-> session-user + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 403)) + + ; anonymous query should succeed but still have no entries + (-> session-anon + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count zero?)) + + ; user query should succeed but have no entries because of missing session role + (-> session-user + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count zero?)) + + ; admin query should succeed, but see no sessions without the correct session role + (-> session-admin + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count 0)) + + ; user should be able to see session with session role + (-> (session app) + (header authn-info-header (str "user/user group/nuvla-user " id)) + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-id id) + (ltu/is-operation-present :delete) + (ltu/is-operation-absent :edit) + (ltu/is-operation-present :switch-group)) + + ; check contents of session + (let [{:keys [name description tags]} (-> session-user + (header authn-info-header (str "user/user group/nuvla-user group/nuvla-anon " id)) + (request abs-uri) + (ltu/body->edn) + :response + :body)] + (is (= name name-attr)) + (is (= description description-attr)) + (is (= tags tags-attr))) + + ; user query with session role should succeed but and have one entry + (-> (session app) + (header authn-info-header (str "user/user group/nuvla-user " id)) + (request base-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count 1)) + + ; user with session role can delete resource + (-> (session app) + (header authn-info-header (str "user/user group/nuvla-user " id)) + (request abs-uri + :request-method :delete) + (ltu/is-unset-cookie) + (ltu/body->edn) + (ltu/is-status 200)) + + (ltu/is-last-event id +<<<<<<< HEAD + {:name "session.delete" +======= + {:event-type "session.delete" + :description (str "user/user logged out.") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "delete" + :success true + :linked-identifiers [] + :authn-info event-authn-info + :acl {:owners ["group/nuvla-admin" + "user/user"]}}) + + + + ; create with invalid template fails + (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str invalid-create)) + (ltu/body->edn) + (ltu/is-status 400))) + + ;; admin create with invalid template fails + (-> session-admin + (request base-uri + :request-method :post + :body (json/write-str invalid-create)) + (ltu/body->edn) + (ltu/is-status 400))) + + ;; anon with valid non activated user cannot create session + (let [username "alex" + plaintext-password "AlexAlex-0" + + valid-create {:name name-attr + :description description-attr + :tags tags-attr + :template {:href href + :username username + :password plaintext-password}}] + + (create-user session-admin + :username username + :password plaintext-password + :activated? false + :email "alex@example.org") + + ; unauthorized create must return a 403 response + (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str valid-create)) + (ltu/body->edn) + (ltu/is-status 403))))) + + +(deftest switch-group-lifecycle-test + (let [app (ltu/ring-app) + session-json (content-type (session app) "application/json") + session-anon (header session-json authn-info-header "user/unknown user/unknown group/nuvla-anon") + session-admin (header session-json authn-info-header "user/super group/nuvla-admin group/nuvla-user group/nuvla-anon group/nuvla-admin") + + href (str st/resource-type "/password") + + username "user/bob" + plaintext-password "BobBob-0" + user-id (create-user session-admin + :username username + :password plaintext-password + :activated? true + :email "bob@example.org") + + valid-create {:template {:href href + :username username + :password plaintext-password}} + session-user (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str valid-create)) + (ltu/body->edn) + (ltu/is-set-cookie) + (ltu/is-status 201)) + session-user-id (ltu/body-resource-id session-user) + sesssion-user-url (ltu/location-url session-user) + credential-id (:credential-password (auth-password/user-id->user user-id)) + _ (ltu/is-last-event session-user-id +<<<<<<< HEAD + {:name "session.add" +======= + {:event-type "session.add" + :description (str username " logged in.") +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "add" + :success true + :linked-identifiers [user-id credential-id] + :authn-info {:user-id "user/unknown" + :active-claim "user/unknown" + :claims ["user/unknown" "group/nuvla-anon"]} + :acl {:owners ["group/nuvla-admin" user-id]}}) + handler (wrap-authn-info identity) + authn-session-user (-> session-user + :response + (select-keys [:cookies]) + handler + seq + flatten) + group-a-identifier "switch-test-a" + group-a (str group/resource-type "/" group-a-identifier) + group-b-identifier "switch-test-b" + group-b (str group/resource-type "/" group-b-identifier) + switch-op-url (-> (apply request session-json (concat [sesssion-user-url] authn-session-user)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/get-op-url :switch-group)) + event-authn-info {:user-id user-id + :active-claim user-id + :claims ["group/nuvla-anon" "group/nuvla-user" session-user-id user-id]}] + + (testing "User cannot switch to a group that he is not part of." + (-> (apply request session-json + (concat [switch-op-url :body (json/write-str {:claim group-b}) + :request-method :post] authn-session-user)) + (ltu/body->edn) + (ltu/is-status 403) + (ltu/message-matches #"Switch group cannot be done to requested group:.*")) + +<<<<<<< HEAD + (ltu/is-last-event session-user-id {:name "session.switch-group" +======= + (ltu/is-last-event session-user-id {:event-type "session.switch-group" + :description "Switch group attempt failed." +>>>>>>> bc60a2ab (use description field to provide human readable label for events) + :category "action" + :success false + :linked-identifiers [group-b] + :authn-info event-authn-info + :acl {:owners ["group/nuvla-admin" group-b]}})) + + (testing "User can switch to a group that he is part of." + (-> session-admin + (request (-> session-admin + (request (str p/service-context group/resource-type) + :request-method :post + :body (json/write-str + {:template + {:href (str group-tpl/resource-type "/generic") + :group-identifier group-a-identifier}})) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location-url)) + :request-method :put + :body (json/write-str {:users [user-id]})) + (ltu/body->edn) + (ltu/is-status 200)) + (let [response (-> (apply request session-json + (concat [switch-op-url :body (json/write-str {:claim group-a}) + :request-method :post] authn-session-user)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-set-cookie) + :response) + authn-session-group-a (-> response + (select-keys [:cookies]) + handler + seq + flatten)] + (testing "Cookie is set and claims correspond to group a" + (is (= {:active-claim group-a + :claims #{"group/nuvla-anon" + "group/nuvla-user" + session-user-id + group-a} + :user-id user-id} + (-> response + handler + auth/current-authentication)))) + + (testing "Nuvlabox owner is set correctly to the active-claim" + (binding [config-nuvla/*stripe-api-key* nil] + (let [nuvlabox-url (-> (apply request session-json + (concat [nb-base-uri + :body (json/write-str {}) + :request-method :post] authn-session-group-a)) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location-url))] + + (-> (apply request session-json (concat [nuvlabox-url] authn-session-group-a)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-key-value :owner group-a))))) + + (testing "switch back to user is possible" + (is (= user-id + (-> (apply request session-json + (concat [switch-op-url :body (json/write-str {:claim user-id}) + :request-method :post] authn-session-group-a)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-set-cookie) + :response + (select-keys [:cookies]) + handler + auth/current-authentication + :active-claim)))) + + (testing "switch to subgroup is possible" + (-> (header session-json authn-info-header (str "user/x " group-a " user/x group/nuvla-user group/nuvla-anon " group-a)) + (request grp-base-uri + :request-method :post + :body (json/write-str (valid-create-grp "switch-test-b"))) + (ltu/body->edn) + (ltu/is-status 201)) + + (let [response (-> (apply request session-json + (concat [switch-op-url :body (json/write-str {:claim "group/switch-test-b"}) + :request-method :post] authn-session-user)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-set-cookie) + :response) + authn-session-group-b (-> response + (select-keys [:cookies]) + handler + seq + flatten)] + (is (= "group/switch-test-b" + (-> response + (select-keys [:cookies]) + handler + auth/current-authentication + :active-claim))) + + (-> (apply request session-json (concat [(str p/service-context nuvlabox/resource-type)] authn-session-group-b)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count 0)) + + (-> (apply request session-json + (concat [nb-base-uri + :body (json/write-str {}) + :request-method :post] authn-session-group-b)) + (ltu/body->edn) + (ltu/is-status 201))))) + (testing "switch to subgroup with extended claims" + (let [response (-> (apply request session-json + (concat [switch-op-url :body (json/write-str {:claim group-a :extended true}) + :request-method :post] authn-session-user)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-set-cookie) + :response) + authn-session-group-a-ext (-> response + (select-keys [:cookies]) + handler + seq + flatten)] + (testing "Cookie is set and claims correspond to group a but claims are extended" + (is (= {:active-claim group-a + :claims #{"group/nuvla-anon" + "group/nuvla-user" + session-user-id + group-a + group-b} + :user-id user-id} + (-> response + handler + auth/current-authentication)))) + + (testing "NuvlaEdge of group b are visible for group a" + (-> (apply request session-json + (concat [nb-base-uri] authn-session-group-a-ext)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count 2)))))))) + + +(deftest get-groups-lifecycle-test + (let [app (ltu/ring-app) + session-json (content-type (session app) "application/json") + session-anon (header session-json authn-info-header "user/unknown user/unknown group/nuvla-anon") + session-admin (header session-json authn-info-header "user/super group/nuvla-admin group/nuvla-user group/nuvla-anon group/nuvla-admin") + user-id (create-user session-admin + :username "tarzan" + :password "TarzanTarzan-0" + :activated? true + :email "tarzan@example.org") + session-user (header session-json authn-info-header (str user-id user-id " group/nuvla-user group/nuvla-anon")) + session-group-a (header session-json authn-info-header "user/x group/a user/x group/nuvla-user group/nuvla-anon group/a") + session-group-b (header session-json authn-info-header "user/x group/b user/x group/nuvla-user group/nuvla-anon group/b") + href (str st/resource-type "/password")] + + (-> session-admin + (request grp-base-uri + :request-method :post + :body (json/write-str (valid-create-grp "a"))) + (ltu/body->edn) + (ltu/is-status 201)) + (-> session-group-a + (request grp-base-uri + :request-method :post + :body (json/write-str (valid-create-grp "b"))) + (ltu/body->edn) + (ltu/is-status 201)) + (-> session-group-a + (request grp-base-uri + :request-method :post + :body (json/write-str (valid-create-grp "b1"))) + (ltu/body->edn) + (ltu/is-status 201)) + (-> session-group-b + (request grp-base-uri + :request-method :post + :body (json/write-str (valid-create-grp "c"))) + (ltu/body->edn) + (ltu/is-status 201)) + + (let [resp (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str {:template {:href href + :username "tarzan" + :password "TarzanTarzan-0"}})) + (ltu/body->edn) + (ltu/is-set-cookie) + (ltu/is-status 201)) + id (ltu/body-resource-id resp) + abs-uri (ltu/location-url resp) + session-with-id (header session-json authn-info-header (str user-id user-id " group/nuvla-user group/nuvla-anon " id))] + (testing "User should be able to see session with session role" + (-> session-with-id + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-id id) + (ltu/is-operation-present :delete) + (ltu/is-operation-absent :edit) + (ltu/is-operation-present :switch-group) + (ltu/is-operation-present :get-peers) + (ltu/is-operation-present :get-groups))) + + (let [get-groups-url (-> session-user + (header authn-info-header (str user-id " " user-id " group/nuvla-user group/nuvla-anon " id)) + (request abs-uri) + (ltu/body->edn) + (ltu/get-op-url :get-groups))] + + (testing "User who is not in any group should get empty list of groups" + (-> session-with-id + (request get-groups-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + (= []) + (is "Get groups body should have no childs"))) + + (testing "When user is part of a group, he should get subgroups" + (-> session-admin + (request (str p/service-context "group/b") + :request-method :put + :body (json/write-str {:users [user-id]})) + (ltu/body->edn) + (ltu/is-status 200)) + (-> session-with-id + (request get-groups-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + (= [{:children [{:description "Group c description" + :id "group/c" + :name "Group c"}] + :description "Group b description" + :id "group/b" + :name "Group b"}]) + (is "User get group/b and subgroup group/c"))) + + (testing "When user is part of a root group he should get + the full group hierarchy and group/b is not duplicated" + (-> session-admin + (request (str p/service-context "group/a") + :request-method :put + :body (json/write-str {:users [user-id]})) + (ltu/body->edn) + (ltu/is-status 200)) + (-> session-admin + (request grp-base-uri + :request-method :post + :body (json/write-str (valid-create-grp "z"))) + (ltu/body->edn) + (ltu/is-status 201)) + (-> session-admin + (request (str p/service-context "group/z") + :request-method :put + :body (json/write-str {:users [user-id]})) + (ltu/body->edn) + (ltu/is-status 200)) + (-> session-with-id + (request get-groups-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + (= [{:children [{:children [{:description "Group c description" + :id "group/c" + :name "Group c"}] + :description "Group b description" + :id "group/b" + :name "Group b"} + {:description "Group b1 description" + :id "group/b1" + :name "Group b1"}] + :description "Group a description" + :id "group/a" + :name "Group a"} + {:description "Group z description" + :id "group/z" + :name "Group z"}]) + (is "Get groups body should contain tree of groups"))))))) + + +(deftest get-peers-lifecycle-test + (let [app (ltu/ring-app) + session-json (content-type (session app) "application/json") + session-anon (header session-json authn-info-header "user/unknown user/unknown group/nuvla-anon") + session-admin (header session-json authn-info-header "user/super group/nuvla-admin group/nuvla-user group/nuvla-anon group/nuvla-admin") + user-id (create-user session-admin + :username "peer0" + :password "Peer0Peer-0" + :activated? true + :email "peer-0@example.org") + peer-1 (create-user session-admin + :username "peer1" + :password "Peer1Peer-1" + :activated? true + :email "peer-1@example.org") + peer-2 (create-user session-admin + :username "peer2" + :password "Peer2Peer-2" + :activated? false + :email "peer-2@example.org") + peer-3 (create-user session-admin + :username "peer3" + :password "Peer3Peer-3" + :activated? true + :email "peer-3@example.org") + session-user (header session-json authn-info-header (str user-id user-id " group/nuvla-user group/nuvla-anon")) + session-group-a (header session-json authn-info-header "user/x group/peers-test-a user/x group/nuvla-user group/nuvla-anon group/peers-test-a") + href (str st/resource-type "/password") + + resp (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str {:template {:href href + :username "peer0" + :password "Peer0Peer-0"}})) + (ltu/body->edn) + (ltu/is-set-cookie) + (ltu/is-status 201)) + id (ltu/body-resource-id resp) + abs-uri (ltu/location-url resp) + session-with-id (header session-json authn-info-header (str user-id user-id " group/nuvla-user group/nuvla-anon " id)) + get-peers-url (-> session-user + (header authn-info-header (str user-id " " user-id " group/nuvla-user group/nuvla-anon " id)) + (request abs-uri) + (ltu/body->edn) + (ltu/get-op-url :get-peers))] + + (testing "admin should get all users with validated emails" + (-> session-admin + (request get-peers-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + vals + set + (= #{"peer-0@example.org" "peer-1@example.org" "peer-3@example.org"}) + (is "Get peers body should contain all users with validated emails"))) + + (testing "user who is not in any group should get empty map of peers" + (-> session-with-id + (request get-peers-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + (= {}) + (is "Get peers body should be empty"))) + + (-> session-admin + (request (-> session-admin + (request grp-base-uri + :request-method :post + :body (json/write-str (valid-create-grp "peers-test-a"))) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location-url)) + :request-method :put + :body (json/write-str {:users [peer-1 user-id peer-2]})) + (ltu/body->edn) + (ltu/is-status 200)) + + (testing "user should get peers of the group when email is validated only" + (-> session-with-id + (request get-peers-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + vals + set + (= #{"peer-0@example.org" "peer-1@example.org"}) + (is "Get peers body should be himself and peer-1"))) + + (testing "user should get peers of subgroup also" + (-> session-admin + (request (-> session-group-a + (request grp-base-uri + :request-method :post + :body (json/write-str (valid-create-grp "peers-test-b"))) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location-url)) + :request-method :put + :body (json/write-str {:users [peer-3 user-id peer-2]})) + (ltu/body->edn) + (ltu/is-status 200)) + (-> session-with-id + (request get-peers-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + vals + set + (= #{"peer-0@example.org" "peer-1@example.org" "peer-3@example.org"}) + (is "Get peers body should contain peer-3"))))) From 8e46eb5147fde2567bcafbb8820829f05132fbfb Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Fri, 25 Aug 2023 15:27:09 +0200 Subject: [PATCH 10/17] Rebase --- .../server/resources/common/event_config.clj | 12 +-- .../common/event_config_BACKUP_29014.clj | 89 ------------------- .../common/event_config_BACKUP_29324.clj | 89 ------------------- .../common/event_config_BASE_29014.clj | 41 --------- .../common/event_config_BASE_29324.clj | 41 --------- .../common/event_config_LOCAL_29014.clj | 41 --------- .../common/event_config_LOCAL_29324.clj | 41 --------- .../common/event_config_REMOTE_29014.clj | 80 ----------------- .../common/event_config_REMOTE_29324.clj | 80 ----------------- .../resources/common/event_config_test.clj | 6 +- 10 files changed, 9 insertions(+), 511 deletions(-) delete mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29014.clj delete mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29324.clj delete mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29014.clj delete mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29324.clj delete mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29014.clj delete mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29324.clj delete mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29014.clj delete mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29324.clj diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config.clj b/code/src/sixsq/nuvla/server/resources/common/event_config.clj index 0cd894308..8d75b6db1 100644 --- a/code/src/sixsq/nuvla/server/resources/common/event_config.clj +++ b/code/src/sixsq/nuvla/server/resources/common/event_config.clj @@ -49,11 +49,11 @@ (defmulti event-description "Returns a human-readable description of the event" - event-type-dispatch) + event-name-dispatch) (defmethod event-description :default - [{:keys [success event-type authn-info category content] :as _event}] + [{:keys [success authn-info category content] event-name :name :as _event}] (if success (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) (:user-id authn-info)) @@ -68,13 +68,13 @@ "add" (str " added " resource-type " " resource-name-or-id) "edit" (str " edited " resource-type " " resource-name-or-id) "delete" (str " deleted " resource-type " " resource-name-or-id) - "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] + "action" (let [action (some->> event-name (re-matches #".*\.(.*)") second)] (str " executed action " action " on " resource-type " " resource-name-or-id)) nil) ".") ("state" "alarm" "email" "user") - event-type ;; FIXME: improve description in this case - event-type)) - (str event-type " attempt failed."))) + event-name ;; FIXME: improve description in this case + event-name)) + (str event-name " attempt failed."))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29014.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29014.clj deleted file mode 100644 index 84c6846de..000000000 --- a/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29014.clj +++ /dev/null @@ -1,89 +0,0 @@ -(ns sixsq.nuvla.server.resources.common.event-config - (:require [sixsq.nuvla.server.resources.common.crud :as crud] - [sixsq.nuvla.server.resources.common.utils :as u])) - -;; -;; Dispatch functions -;; - -(defn resource-type-dispatch [resource-type] - resource-type) - - -<<<<<<< HEAD -(defn event-name-dispatch [{event-name :name :as _event} _response] - event-name) -======= -(defn event-type-dispatch [{:keys [event-type] :as _event} & _rest] - event-type) ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - - -;; -;; Enabled/disabled events -;; - -(defmulti events-enabled? - "Returns true if events should be logged for the given resource-type, false otherwise." - resource-type-dispatch) - - -(defmethod events-enabled? :default - [_resource-type] - false) - - -;; -;; Whitelist and blacklist event types per resource type -;; - -(defmulti log-event? - "Returns true if the event should be logged, false otherwise." - event-name-dispatch) - - -(defmethod log-event? :default - [{event-name :name :as _event} {:keys [status] :as _response}] - (and (not= 405 status) -<<<<<<< HEAD - (some? event-name))) -======= - (some? event-type))) - - -;; -;; Event human readable description -;; - -(defmulti event-description - "Returns a human-readable description of the event" - event-type-dispatch) - - -(defmethod event-description :default - [{:keys [success event-type authn-info category content] :as _event}] - (if success - (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) - (:user-id authn-info)) - resource-id (-> content :resource :href) - resource-type (u/id->resource-type resource-id) - resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) - resource-name-or-id (or resource-name resource-id)] - (case category - ("add" "edit" "delete" "action") - (str (or user-name-or-id "An anonymous user") - (case category - "add" (str " added " resource-type " " resource-name-or-id) - "edit" (str " edited " resource-type " " resource-name-or-id) - "delete" (str " deleted " resource-type " " resource-name-or-id) - "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] - (str " executed action " action " on " resource-type " " resource-name-or-id)) - nil) - ".") - ("state" "alarm" "email" "user") - event-type ;; FIXME: improve description in this case - event-type)) - (str event-type " attempt failed."))) - - ->>>>>>> bc60a2ab (use description field to provide human readable label for events) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29324.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29324.clj deleted file mode 100644 index 84c6846de..000000000 --- a/code/src/sixsq/nuvla/server/resources/common/event_config_BACKUP_29324.clj +++ /dev/null @@ -1,89 +0,0 @@ -(ns sixsq.nuvla.server.resources.common.event-config - (:require [sixsq.nuvla.server.resources.common.crud :as crud] - [sixsq.nuvla.server.resources.common.utils :as u])) - -;; -;; Dispatch functions -;; - -(defn resource-type-dispatch [resource-type] - resource-type) - - -<<<<<<< HEAD -(defn event-name-dispatch [{event-name :name :as _event} _response] - event-name) -======= -(defn event-type-dispatch [{:keys [event-type] :as _event} & _rest] - event-type) ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - - -;; -;; Enabled/disabled events -;; - -(defmulti events-enabled? - "Returns true if events should be logged for the given resource-type, false otherwise." - resource-type-dispatch) - - -(defmethod events-enabled? :default - [_resource-type] - false) - - -;; -;; Whitelist and blacklist event types per resource type -;; - -(defmulti log-event? - "Returns true if the event should be logged, false otherwise." - event-name-dispatch) - - -(defmethod log-event? :default - [{event-name :name :as _event} {:keys [status] :as _response}] - (and (not= 405 status) -<<<<<<< HEAD - (some? event-name))) -======= - (some? event-type))) - - -;; -;; Event human readable description -;; - -(defmulti event-description - "Returns a human-readable description of the event" - event-type-dispatch) - - -(defmethod event-description :default - [{:keys [success event-type authn-info category content] :as _event}] - (if success - (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) - (:user-id authn-info)) - resource-id (-> content :resource :href) - resource-type (u/id->resource-type resource-id) - resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) - resource-name-or-id (or resource-name resource-id)] - (case category - ("add" "edit" "delete" "action") - (str (or user-name-or-id "An anonymous user") - (case category - "add" (str " added " resource-type " " resource-name-or-id) - "edit" (str " edited " resource-type " " resource-name-or-id) - "delete" (str " deleted " resource-type " " resource-name-or-id) - "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] - (str " executed action " action " on " resource-type " " resource-name-or-id)) - nil) - ".") - ("state" "alarm" "email" "user") - event-type ;; FIXME: improve description in this case - event-type)) - (str event-type " attempt failed."))) - - ->>>>>>> bc60a2ab (use description field to provide human readable label for events) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29014.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29014.clj deleted file mode 100644 index 3587acae3..000000000 --- a/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29014.clj +++ /dev/null @@ -1,41 +0,0 @@ -(ns sixsq.nuvla.server.resources.common.event-config) - -;; -;; Dispatch functions -;; - -(defn resource-type-dispatch [resource-type] - resource-type) - - -(defn event-type-dispatch [{:keys [event-type] :as _event} _response] - event-type) - - -;; -;; Enabled/disabled events -;; - -(defmulti events-enabled? - "Returns true if events should be logged for the given resource-type, false otherwise." - resource-type-dispatch) - - -(defmethod events-enabled? :default - [_resource-type] - false) - - -;; -;; Whitelist and blacklist event types per resource type -;; - -(defmulti log-event? - "Returns true if the event should be logged, false otherwise." - event-type-dispatch) - - -(defmethod log-event? :default - [{:keys [event-type] :as _event} {:keys [status] :as _response}] - (and (not= 405 status) - (some? event-type))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29324.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29324.clj deleted file mode 100644 index 3587acae3..000000000 --- a/code/src/sixsq/nuvla/server/resources/common/event_config_BASE_29324.clj +++ /dev/null @@ -1,41 +0,0 @@ -(ns sixsq.nuvla.server.resources.common.event-config) - -;; -;; Dispatch functions -;; - -(defn resource-type-dispatch [resource-type] - resource-type) - - -(defn event-type-dispatch [{:keys [event-type] :as _event} _response] - event-type) - - -;; -;; Enabled/disabled events -;; - -(defmulti events-enabled? - "Returns true if events should be logged for the given resource-type, false otherwise." - resource-type-dispatch) - - -(defmethod events-enabled? :default - [_resource-type] - false) - - -;; -;; Whitelist and blacklist event types per resource type -;; - -(defmulti log-event? - "Returns true if the event should be logged, false otherwise." - event-type-dispatch) - - -(defmethod log-event? :default - [{:keys [event-type] :as _event} {:keys [status] :as _response}] - (and (not= 405 status) - (some? event-type))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29014.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29014.clj deleted file mode 100644 index 9b961a781..000000000 --- a/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29014.clj +++ /dev/null @@ -1,41 +0,0 @@ -(ns sixsq.nuvla.server.resources.common.event-config) - -;; -;; Dispatch functions -;; - -(defn resource-type-dispatch [resource-type] - resource-type) - - -(defn event-name-dispatch [{event-name :name :as _event} _response] - event-name) - - -;; -;; Enabled/disabled events -;; - -(defmulti events-enabled? - "Returns true if events should be logged for the given resource-type, false otherwise." - resource-type-dispatch) - - -(defmethod events-enabled? :default - [_resource-type] - false) - - -;; -;; Whitelist and blacklist event types per resource type -;; - -(defmulti log-event? - "Returns true if the event should be logged, false otherwise." - event-name-dispatch) - - -(defmethod log-event? :default - [{event-name :name :as _event} {:keys [status] :as _response}] - (and (not= 405 status) - (some? event-name))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29324.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29324.clj deleted file mode 100644 index 9b961a781..000000000 --- a/code/src/sixsq/nuvla/server/resources/common/event_config_LOCAL_29324.clj +++ /dev/null @@ -1,41 +0,0 @@ -(ns sixsq.nuvla.server.resources.common.event-config) - -;; -;; Dispatch functions -;; - -(defn resource-type-dispatch [resource-type] - resource-type) - - -(defn event-name-dispatch [{event-name :name :as _event} _response] - event-name) - - -;; -;; Enabled/disabled events -;; - -(defmulti events-enabled? - "Returns true if events should be logged for the given resource-type, false otherwise." - resource-type-dispatch) - - -(defmethod events-enabled? :default - [_resource-type] - false) - - -;; -;; Whitelist and blacklist event types per resource type -;; - -(defmulti log-event? - "Returns true if the event should be logged, false otherwise." - event-name-dispatch) - - -(defmethod log-event? :default - [{event-name :name :as _event} {:keys [status] :as _response}] - (and (not= 405 status) - (some? event-name))) diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29014.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29014.clj deleted file mode 100644 index 6896d634d..000000000 --- a/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29014.clj +++ /dev/null @@ -1,80 +0,0 @@ -(ns sixsq.nuvla.server.resources.common.event-config - (:require [sixsq.nuvla.server.resources.common.crud :as crud] - [sixsq.nuvla.server.resources.common.utils :as u])) - -;; -;; Dispatch functions -;; - -(defn resource-type-dispatch [resource-type] - resource-type) - - -(defn event-type-dispatch [{:keys [event-type] :as _event} & _rest] - event-type) - - -;; -;; Enabled/disabled events -;; - -(defmulti events-enabled? - "Returns true if events should be logged for the given resource-type, false otherwise." - resource-type-dispatch) - - -(defmethod events-enabled? :default - [_resource-type] - false) - - -;; -;; Whitelist and blacklist event types per resource type -;; - -(defmulti log-event? - "Returns true if the event should be logged, false otherwise." - event-type-dispatch) - - -(defmethod log-event? :default - [{:keys [event-type] :as _event} {:keys [status] :as _response}] - (and (not= 405 status) - (some? event-type))) - - -;; -;; Event human readable description -;; - -(defmulti event-description - "Returns a human-readable description of the event" - event-type-dispatch) - - -(defmethod event-description :default - [{:keys [success event-type authn-info category content] :as _event}] - (if success - (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) - (:user-id authn-info)) - resource-id (-> content :resource :href) - resource-type (u/id->resource-type resource-id) - resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) - resource-name-or-id (or resource-name resource-id)] - (case category - ("add" "edit" "delete" "action") - (str (or user-name-or-id "An anonymous user") - (case category - "add" (str " added " resource-type " " resource-name-or-id) - "edit" (str " edited " resource-type " " resource-name-or-id) - "delete" (str " deleted " resource-type " " resource-name-or-id) - "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] - (str " executed action " action " on " resource-type " " resource-name-or-id)) - nil) - ".") - ("state" "alarm" "email" "user") - event-type ;; FIXME: improve description in this case - event-type)) - (str event-type " attempt failed."))) - - diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29324.clj b/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29324.clj deleted file mode 100644 index 6896d634d..000000000 --- a/code/src/sixsq/nuvla/server/resources/common/event_config_REMOTE_29324.clj +++ /dev/null @@ -1,80 +0,0 @@ -(ns sixsq.nuvla.server.resources.common.event-config - (:require [sixsq.nuvla.server.resources.common.crud :as crud] - [sixsq.nuvla.server.resources.common.utils :as u])) - -;; -;; Dispatch functions -;; - -(defn resource-type-dispatch [resource-type] - resource-type) - - -(defn event-type-dispatch [{:keys [event-type] :as _event} & _rest] - event-type) - - -;; -;; Enabled/disabled events -;; - -(defmulti events-enabled? - "Returns true if events should be logged for the given resource-type, false otherwise." - resource-type-dispatch) - - -(defmethod events-enabled? :default - [_resource-type] - false) - - -;; -;; Whitelist and blacklist event types per resource type -;; - -(defmulti log-event? - "Returns true if the event should be logged, false otherwise." - event-type-dispatch) - - -(defmethod log-event? :default - [{:keys [event-type] :as _event} {:keys [status] :as _response}] - (and (not= 405 status) - (some? event-type))) - - -;; -;; Event human readable description -;; - -(defmulti event-description - "Returns a human-readable description of the event" - event-type-dispatch) - - -(defmethod event-description :default - [{:keys [success event-type authn-info category content] :as _event}] - (if success - (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) - (:user-id authn-info)) - resource-id (-> content :resource :href) - resource-type (u/id->resource-type resource-id) - resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) - resource-name-or-id (or resource-name resource-id)] - (case category - ("add" "edit" "delete" "action") - (str (or user-name-or-id "An anonymous user") - (case category - "add" (str " added " resource-type " " resource-name-or-id) - "edit" (str " edited " resource-type " " resource-name-or-id) - "delete" (str " deleted " resource-type " " resource-name-or-id) - "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] - (str " executed action " action " on " resource-type " " resource-name-or-id)) - nil) - ".") - ("state" "alarm" "email" "user") - event-type ;; FIXME: improve description in this case - event-type)) - (str event-type " attempt failed."))) - - diff --git a/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj index c4f51b1ba..719dd41cd 100644 --- a/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj +++ b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj @@ -17,15 +17,15 @@ (def disabled-event {:name "resource.validate"}) -(def anon-event {:event-type "resource.add" +(def anon-event {:name "resource.add" :category "add" :success true :authn-info {:claims ["group/nuvla-anon"]} :content {:resource {:href "resource/12345"}}}) -(def failure-event {:event-type "resource.add" - :success false}) +(def failure-event {:name "resource.add" + :success false}) From dd4f70b6d77f227da87287c0287bc073449b5576 Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Fri, 25 Aug 2023 11:43:08 +0200 Subject: [PATCH 11/17] support for deployment --- .../nuvla/server/resources/deployment.clj | 38 ++++- .../server/resources/deployment/utils.clj | 4 +- .../nuvla/server/resources/spec/event.cljc | 2 +- .../resources/deployment_lifecycle_test.clj | 149 ++++++++++++++---- .../server/resources/lifecycle_test_utils.clj | 86 +++++++--- .../resources/lifecycle_test_utils.clj.orig | 75 +++++++-- 6 files changed, 289 insertions(+), 65 deletions(-) diff --git a/code/src/sixsq/nuvla/server/resources/deployment.clj b/code/src/sixsq/nuvla/server/resources/deployment.clj index d25f92993..c4b06b5b0 100644 --- a/code/src/sixsq/nuvla/server/resources/deployment.clj +++ b/code/src/sixsq/nuvla/server/resources/deployment.clj @@ -9,6 +9,8 @@ a container orchestration engine. [sixsq.nuvla.auth.utils :as auth] [sixsq.nuvla.db.impl :as db] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-config :as ec] + [sixsq.nuvla.server.resources.common.event-context :as ectx] [sixsq.nuvla.server.resources.common.std-crud :as std-crud] [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.deployment.utils :as utils] @@ -154,7 +156,7 @@ a container orchestration engine. msg (get-in create-response [:body :message])] - (event-utils/create-event deployment-id msg (a/default-acl authn-info)) + (event-utils/create-event deployment-id msg (a/default-acl authn-info) :category "state") create-response)) @@ -244,6 +246,7 @@ a container orchestration engine. delete-response (-> deployment (a/throw-cannot-delete request) (db/delete request))] + (ectx/add-to-context :acl (:acl deployment)) (utils/delete-all-child-resources deployment-id) delete-response) (catch Exception e @@ -386,9 +389,11 @@ a container orchestration engine. [request] (try (a/throw-cannot-add collection-acl request) - (-> (crud/get-resource-throw-nok request) - (select-keys [:module :data :name :description :tags]) - (create-deployment request)) + (let [response (-> (crud/get-resource-throw-nok request) + (select-keys [:module :data :name :description :tags]) + (create-deployment request))] + (ectx/add-linked-identifier (get-in response [:body :resource-id])) + response) (catch Exception e (or (ex-data e) (throw e))))) @@ -536,6 +541,31 @@ a container orchestration engine. :body dep-updated :nuvla/authn authn-info})))) + +;; +;; Events +;; + +(defmethod ec/events-enabled? resource-type + [_resource-type] + true) + + +(defmethod ec/log-event? "deployment.create-log" + [_event _response] + false) + + +(defmethod ec/log-event? "deployment.check-dct" + [_event _response] + false) + + +(defmethod ec/log-event? "deployment.fetch-module" + [_event _response] + false) + + ;; ;; initialization ;; diff --git a/code/src/sixsq/nuvla/server/resources/deployment/utils.clj b/code/src/sixsq/nuvla/server/resources/deployment/utils.clj index 06f2b4284..00698b0ad 100644 --- a/code/src/sixsq/nuvla/server/resources/deployment/utils.clj +++ b/code/src/sixsq/nuvla/server/resources/deployment/utils.clj @@ -9,6 +9,7 @@ [sixsq.nuvla.pricing.payment :as payment] [sixsq.nuvla.server.middleware.cimi-params.impl :as cimi-params-impl] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-context :as ec] [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.configuration-nuvla :as config-nuvla] [sixsq.nuvla.server.resources.credential :as credential] @@ -120,7 +121,8 @@ (when (not= job-status 201) (throw (r/ex-response (format "unable to create async job to %s deployment" action) 500 id))) - (event-utils/create-event id job-msg (a/default-acl (auth/current-authentication request))) + (ec/add-linked-identifier job-id) + (event-utils/create-event id job-msg (a/default-acl (auth/current-authentication request)) :category "state") (r/map-response job-msg 202 id job-id))) diff --git a/code/src/sixsq/nuvla/server/resources/spec/event.cljc b/code/src/sixsq/nuvla/server/resources/spec/event.cljc index f32598d3d..79e23fb28 100644 --- a/code/src/sixsq/nuvla/server/resources/spec/event.cljc +++ b/code/src/sixsq/nuvla/server/resources/spec/event.cljc @@ -47,7 +47,7 @@ ;; Events may need to reference resources that do not follow the CIMI. ;; conventions. Allow for a more flexible schema to be used here. (s/def ::href - (-> (st/spec (s/nilable (s/and string? #(re-matches #"^[a-zA-Z0-9]+[a-zA-Z0-9_./-]*$" %)))) + (-> (st/spec (s/nilable ::core/nonblank-string)) (assoc :name "href" :json-schema/type "string" :json-schema/description "reference to associated resource"))) diff --git a/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj index b35ed0fa0..2fbb74c0d 100644 --- a/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj @@ -34,15 +34,15 @@ (defn- setup-module [session-owner module-data] - (let [_ (create-parent-projects (:path module-data) session-owner) - module-id (-> session-owner - (request module-base-uri - :request-method :post - :body (json/write-str - module-data)) - (ltu/body->edn) - (ltu/is-status 201) - (ltu/location))] + (let [_ (create-parent-projects (:path module-data) session-owner) + module-id (-> session-owner + (request module-base-uri + :request-method :post + :body (json/write-str + module-data)) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location))] module-id)) (defn valid-module @@ -113,7 +113,11 @@ ;; setup a module that can be referenced from the deployment module-id (setup-module session-user (valid-module subtype valid-module-content)) valid-deployment {:module {:href module-id}} - invalid-deployment {:module {:href "module/doesnt-exist"}}] + invalid-deployment {:module {:href "module/doesnt-exist"}} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user" session-id]} + event-owners-jane ["group/nuvla-admin" "user/jane"]] ;; admin/user query succeeds but is empty (doseq [session [session-admin session-user]] @@ -160,6 +164,16 @@ deployment-url (str p/service-context deployment-id)] + (ltu/are-last-events deployment-id + [{:event-type "legacy" + :category "state"} + {:event-type "deployment.add" + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners event-owners-jane}}]) + ;; admin/user should see one deployment (doseq [session [session-user session-admin]] (-> session @@ -197,6 +211,14 @@ (ltu/is-key-value :owner "user/jane") (ltu/is-key-value :owners :acl ["user/jane" "user/tarzan"])) + (ltu/is-last-event deployment-id + {:event-type "deployment.edit" + :category "edit" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}) + (testing "user should not be able to change parent credential to something not accessible" (with-redefs [crud/retrieve-by-id-as-admin (fn [id] (if (= id "credential/x") @@ -208,15 +230,35 @@ :request-method :put :body (json/write-str {:parent "credential/x"})) (ltu/body->edn) - (ltu/is-status 403)))) + (ltu/is-status 403)) + + (ltu/is-last-event deployment-id + {:event-type "deployment.edit" + :category "edit" + :success false + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}))) ;; attempt to start the deployment and check the start job was created - (let [job-url (-> session-user + (let [job-id (-> session-user (request start-url :request-method :post) (ltu/body->edn) (ltu/is-status 202) - (ltu/location-url))] + (ltu/location)) + job-url (ltu/href->url job-id)] + + (ltu/are-last-events deployment-id + [{:event-type "legacy" + :category "state"} + {:event-type "deployment.start" + :category "action" + :success true + :linked-identifiers [job-id] + :authn-info authn-info-jane + :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}]) + (-> session-user (request job-url) (ltu/body->edn) @@ -395,12 +437,24 @@ (ltu/is-status 200)) ;; try to stop the deployment and check the stop job was created - (let [job-url (-> session-user + (let [job-id (-> session-user (request stop-url :request-method :post) (ltu/body->edn) (ltu/is-status 202) - (ltu/location-url))] + (ltu/location)) + job-url (ltu/href->url job-id)] + + (ltu/are-last-events deployment-id + [{:event-type "legacy" + :category "state"} + {:event-type "deployment.stop" + :category "action" + :success true + :linked-identifiers [job-id] + :authn-info authn-info-jane + :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}]) + (-> session-user (request job-url :request-method :get) @@ -467,18 +521,39 @@ :acl "user/shared"))) ;; verify user can create another deployment from existing one by using clone action - (let [deployment-url-from-dep (-> session-user + (let [deployment-id-from-dep (-> session-user (request (str deployment-url "/clone") :request-method :post :body (json/write-str {:deployment {:href deployment-id}})) (ltu/body->edn) (ltu/is-status 201) - (ltu/location-url))] + (ltu/location)) + deployment-url-from-dep (ltu/href->url deployment-id-from-dep)] + + (ltu/is-last-event deployment-id + {:event-type "deployment.clone" + :category "action" + :success true + :linked-identifiers [deployment-id-from-dep] + :authn-info authn-info-jane + :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}) + + (ltu/is-last-event deployment-id-from-dep + {:event-type "legacy" + :category "state"}) + (-> session-user (request deployment-url-from-dep :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))) + (ltu/is-status 200)) + + (ltu/is-last-event deployment-id-from-dep + {:event-type "deployment.delete" + :category "delete" + :success true + :authn-info authn-info-jane + :acl {:owners event-owners-jane}})) ;; verify that the user can delete the deployment (-> session-user @@ -487,6 +562,13 @@ (ltu/body->edn) (ltu/is-status 200)) + (ltu/is-last-event deployment-id + {:event-type "deployment.delete" + :category "delete" + :success true + :authn-info authn-info-jane + :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}) + ;; verify that the deployment has disappeared (-> session-user (request deployment-url) @@ -709,7 +791,11 @@ (ltu/is-status 201) (ltu/location)) - deployment-url (str p/service-context deployment-id)] + deployment-url (str p/service-context deployment-id) + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + event-owners-jane ["group/nuvla-admin" "user/jane"]] ;; check deployment creation (-> session-user @@ -816,6 +902,13 @@ (ltu/body->edn) (ltu/is-status 200))) + (ltu/is-last-event deployment-id + {:event-type "deployment.force-delete" + :category "action" + :success true + :authn-info authn-info-jane + :acl {:owners event-owners-jane}}) + (-> session-user (request (str p/service-context module-id) :request-method :delete) @@ -842,7 +935,7 @@ (request deployment-url :request-method :put :body (json/write-str (cond-> - {:name depl-name} + {:name depl-name} depl-tags (assoc :tags depl-tags)))) (ltu/body->edn) (ltu/is-status 200) @@ -875,16 +968,16 @@ (request endpoint :request-method :patch :body (json/write-str (cond-> {:doc {:tags tags}} - filter (assoc :filter filter)))) + filter (assoc :filter filter)))) (ltu/is-status 200)) (run! - (fn [url] - (let [ne (-> session-owner - (request url) - (ltu/body->edn))] - (testing (:name ne) - (ltu/is-key-value ne :tags (expected-fn (-> ne :response :body)))))) - ne-urls)))) + (fn [url] + (let [ne (-> session-owner + (request url) + (ltu/body->edn))] + (testing (:name ne) + (ltu/is-key-value ne :tags (expected-fn (-> ne :response :body)))))) + ne-urls)))) (def endpoint-set-tags (str base-uri "/" "set-tags")) (def endpoint-add-tags (str base-uri "/" "add-tags")) diff --git a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj index 284fd06f2..96ec3cdb3 100644 --- a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj +++ b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj @@ -602,29 +602,73 @@ :body (json/write-str {:dummy "value"})) (is-status 405))))) +;; +;; ACL +;; + +(defmacro is-acl + [expected-acl actual-acl] + `(do + (when (:owners ~expected-acl) + (is (= (set (:owners ~expected-acl)) (set (:owners ~actual-acl))))) + (when (:edit-acl ~expected-acl) + (is (= (set (:edit-acl ~expected-acl)) (set (:edit-acl ~actual-acl))))) + (when (:edit-data ~expected-acl) + (is (= (set (:edit-data ~expected-acl)) (set (:edit-data ~actual-acl))))) + (when (:edit-meta ~expected-acl) + (is (= (set (:edit-meta ~expected-acl)) (set (:edit-meta ~actual-acl))))) + (when (:view-acl ~expected-acl) + (is (= (set (:view-acl ~expected-acl)) (set (:view-acl ~actual-acl))))) + (when (:view-data ~expected-acl) + (is (= (set (:view-data ~expected-acl)) (set (:view-data ~actual-acl))))) + (when (:view-meta ~expected-acl) + (is (= (set (:view-meta ~expected-acl)) (set (:view-meta ~actual-acl))))) + (when (:manage ~expected-acl) + (is (= (set (:manage ~expected-acl)) (set (:manage ~actual-acl))))) + (when (:delete ~expected-acl) + (is (= (set (:delete ~expected-acl)) (set (:delete ~actual-acl))))))) + + ;; ;; events ;; -(defmacro is-last-event - [resource-id {:keys [description category authn-info linked-identifiers success acl] event-name :name}] - `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1})) - authn-info# (:authn-info event#)] - (is (some? event#)) - (when ~event-name - (is (= ~event-name (:name event#)))) - (when ~description - (is (= ~description (:description event#)))) - (when ~category - (is (= ~category (:category event#)))) - (when ~authn-info - (is (= (:user-id ~authn-info) (:user-id authn-info#))) - (is (= (:active-claim ~authn-info) (:active-claim authn-info#))) - (is (= (set (:claims ~authn-info)) (set (:claims authn-info#))))) - (when ~linked-identifiers - (is (= (set ~linked-identifiers) (set (get-in event# [:content :linked-identifiers]))))) - (when (some? ~success) - (is (= ~success (:success event#)))) - (when (some? ~acl) - (is (= ~acl (:acl event#)))))) +(defmacro is-event + [expected-event actual-event] + `(let [expected-authn-info# (:authn-info ~expected-event) + authn-info# (:authn-info ~actual-event)] + (is (some? ~actual-event)) + (when (:name ~expected-event) + (is (= (:name ~expected-event) (:name ~actual-event)))) + (when (:description ~expected-event) + (is (= (:description ~expected-event) (:description ~actual-event)))) + (when (:category ~expected-event) + (is (= (:category ~expected-event) (:category ~actual-event)))) + (when expected-authn-info# + (is (= (:user-id expected-authn-info#) (:user-id authn-info#))) + (is (= (:active-claim expected-authn-info#) (:active-claim authn-info#))) + (is (= (set (:claims expected-authn-info#)) (set (:claims authn-info#))))) + (when (:linked-identifiers ~expected-event) + (is (= (set (:linked-identifiers ~expected-event)) + (set (get-in ~actual-event [:content :linked-identifiers]))))) + (when (some? (:success ~expected-event)) + (is (= (:success ~expected-event) (:success ~actual-event)))) + (when (some? (:acl ~expected-event)) + (is-acl (:acl ~expected-event) (:acl ~actual-event))))) + +(defmacro is-last-event + [resource-id expected-event] + `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1}))] + (is-event ~expected-event event#))) + + +(defmacro are-last-events + [resource-id expected-events] + `(let [events# (take (count ~expected-events) (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] + :last (count ~expected-events)}))] + (is (= (count ~expected-events) (count events#))) + (doall (map (fn [expected-event# actual-event#] + (is-event expected-event# actual-event#)) + ~expected-events + events#)))) diff --git a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig index 590c6d7a2..13e0829b6 100644 --- a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig +++ b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig @@ -602,28 +602,47 @@ :body (json/write-str {:dummy "value"})) (is-status 405))))) +;; +;; ACL +;; + +(defmacro is-acl + [expected-acl actual-acl] + `(do + (when (:owners ~expected-acl) + (is (= (set (:owners ~expected-acl)) (set (:owners ~actual-acl))))) + (when (:edit-acl ~expected-acl) + (is (= (set (:edit-acl ~expected-acl)) (set (:edit-acl ~actual-acl))))) + (when (:edit-data ~expected-acl) + (is (= (set (:edit-data ~expected-acl)) (set (:edit-data ~actual-acl))))) + (when (:edit-meta ~expected-acl) + (is (= (set (:edit-meta ~expected-acl)) (set (:edit-meta ~actual-acl))))) + (when (:view-acl ~expected-acl) + (is (= (set (:view-acl ~expected-acl)) (set (:view-acl ~actual-acl))))) + (when (:view-data ~expected-acl) + (is (= (set (:view-data ~expected-acl)) (set (:view-data ~actual-acl))))) + (when (:view-meta ~expected-acl) + (is (= (set (:view-meta ~expected-acl)) (set (:view-meta ~actual-acl))))) + (when (:manage ~expected-acl) + (is (= (set (:manage ~expected-acl)) (set (:manage ~actual-acl))))) + (when (:delete ~expected-acl) + (is (= (set (:delete ~expected-acl)) (set (:delete ~actual-acl))))))) + + ;; ;; events ;; -(defmacro is-last-event <<<<<<< HEAD - [resource-id {:keys [category authn-info linked-identifiers success acl] event-name :name}] +(defmacro is-last-event + [resource-id {:keys [description category authn-info linked-identifiers success acl] event-name :name}] `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1})) authn-info# (:authn-info event#)] (is (some? event#)) (when ~event-name (is (= ~event-name (:name event#)))) -======= - [resource-id {:keys [event-type description category authn-info linked-identifiers success acl]}] - `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1})) - authn-info# (:authn-info event#)] - (is (some? event#)) - (when ~event-type - (is (= ~event-type (:event-type event#)))) (when ~description (is (= ~description (:description event#)))) ->>>>>>> bc60a2ab (use description field to provide human readable label for events) (when ~category (is (= ~category (:category event#)))) (when ~authn-info @@ -636,4 +655,40 @@ (is (= ~success (:success event#)))) (when (some? ~acl) (is (= ~acl (:acl event#)))))) +======= +(defmacro is-event + [expected-event actual-event] + `(let [expected-authn-info# (:authn-info ~expected-event) + authn-info# (:authn-info ~actual-event)] + (is (some? ~actual-event)) + (when (:event-type ~expected-event) + (is (= (:event-type ~expected-event) (:event-type ~actual-event)))) + (when (:category ~expected-event) + (is (= (:category ~expected-event) (:category ~actual-event)))) + (when expected-authn-info# + (is (= (:user-id expected-authn-info#) (:user-id authn-info#))) + (is (= (:active-claim expected-authn-info#) (:active-claim authn-info#))) + (is (= (set (:claims expected-authn-info#)) (set (:claims authn-info#))))) + (when (:linked-identifiers ~expected-event) + (is (= (set (:linked-identifiers ~expected-event)) + (set (get-in ~actual-event [:content :linked-identifiers]))))) + (when (some? (:success ~expected-event)) + (is (= (:success ~expected-event) (:success ~actual-event)))) + (when (some? (:acl ~expected-event)) + (is-acl (:acl ~expected-event) (:acl ~actual-event))))) +>>>>>>> 2ab3edc8 (support for deployment) +(defmacro is-last-event + [resource-id expected-event] + `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1}))] + (is-event ~expected-event event#))) + +(defmacro are-last-events + [resource-id expected-events] + `(let [events# (take (count ~expected-events) (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] + :last (count ~expected-events)}))] + (is (= (count ~expected-events) (count events#))) + (doall (map (fn [expected-event# actual-event#] + (is-event expected-event# actual-event#)) + ~expected-events + events#)))) From 43abd87f430e68c60b8c19a0e2fab157587144a8 Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Fri, 25 Aug 2023 13:06:38 +0200 Subject: [PATCH 12/17] Rebase --- .../resources/module_lifecycle_test.clj | 654 ++++++++++-------- .../resources/module_lifecycle_test.clj.orig | 441 ++++++++++-- .../server/resources/spec/event_test.cljc | 4 +- 3 files changed, 728 insertions(+), 371 deletions(-) diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj index f38211bda..15f584123 100644 --- a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj @@ -41,34 +41,42 @@ (ltu/is-status 201))) paths))) -(defn lifecycle-test-module +(def session-anon + (-> (session (ltu/ring-app)) + (content-type "application/json"))) +(def session-admin + (header session-anon authn-info-header + "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon")) +(def session-user + (header session-anon authn-info-header + "user/jane user/jane group/nuvla-user group/nuvla-anon")) + +(def authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]}) +(def authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]}) +(def authn-info-anon {:claims ["group/nuvla-anon"]}) + +(defn build-valid-entry [subtype valid-content] - (let [session-anon (-> (session (ltu/ring-app)) - (content-type "application/json")) - session-admin (header session-anon authn-info-header - "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") - session-user (header session-anon authn-info-header - "user/jane user/jane group/nuvla-user group/nuvla-anon") - - valid-entry {:parent-path "a/b" - :path "a/b/c" - :subtype subtype - - :compatibility "docker-compose" - - :logo-url "https://example.org/logo" - - :data-accept-content-types ["application/json" "application/x-something"] - :data-access-protocols ["http+s3" "posix+nfs"] - - :content valid-content} - authn-info-admin {:user-id "group/nuvla-admin" - :active-claim "group/nuvla-admin" - :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} - authn-info-jane {:user-id "user/jane" - :active-claim "user/jane" - :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} - authn-info-anon {:claims ["group/nuvla-anon"]} + {:parent-path "a/b" + :path "a/b/c" + :subtype subtype + + :compatibility "docker-compose" + + :logo-url "https://example.org/logo" + + :data-accept-content-types ["application/json" "application/x-something"] + :data-access-protocols ["http+s3" "posix+nfs"] + + :content valid-content}) + +(defn create-module-nok + [valid-entry] + (let [] admin-group-name "Nuvla Administrator Group"] ;; create: NOK for anon @@ -130,321 +138,359 @@ :body (json/write-str (dissoc valid-entry :compatibility))) (ltu/body->edn) (ltu/is-status 400) - (ltu/message-matches "Application subtype should have compatibility attribute set!")))) + (ltu/message-matches "Application subtype should have compatibility attribute set!")))))) - ;; adding, retrieving and deleting entry as user should succeed - (doseq [[session event-owners authn-info user-name-or-id] - [[session-admin ["group/nuvla-admin"] authn-info-admin admin-group-name] - [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane "user/jane"]]] - (let [uri (-> session - (request base-uri - :request-method :post - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 201) - (ltu/location)) - abs-uri (str p/service-context uri)] +(defn create-module + [session valid-entry event-owners authn-info] + (let [uri (-> session + (request base-uri + :request-method :post + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location)) + + abs-uri (str p/service-context uri)] - (ltu/is-last-event uri - {:name "module.add" + (ltu/is-last-event uri + {:event-type "module.add" :description (str user-name-or-id " added module " uri ".") - :category "add" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - ;; retrieve: NOK for anon - (-> session-anon - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 403)) - - (let [{:keys [content acl]} (-> session-admin - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-key-value :compatibility "docker-compose") - (as-> m (if (utils/is-application? valid-entry) - (ltu/is-operation-present m :validate-docker-compose) - (ltu/is-operation-absent m :validate-docker-compose))) - (ltu/body))] - (is (= valid-content (select-keys content (keys valid-content))))) - - ;; edit: NOK for anon - (-> session-anon - (request abs-uri - :request-method :put - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 403)) + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + ;; retrieve: NOK for anon + (-> session-anon + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 403)) + + uri)) - (ltu/is-last-event uri +(defn retrieve-module + [uri valid-entry valid-content] + (let [abs-uri (str p/service-context uri) + {:keys [content] :as module} (-> session-admin + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-key-value :compatibility "docker-compose") + (as-> m (if (utils/is-application? valid-entry) + (ltu/is-operation-present m :validate-docker-compose) + (ltu/is-operation-absent m :validate-docker-compose))) + (ltu/body))] + (is (= valid-content (select-keys content (keys valid-content)))) + module)) + + +(defn edit-module + [uri valid-entry event-owners] + (let [abs-uri (str p/service-context uri)] + ;; edit: NOK for anon + (-> session-anon + (request abs-uri + :request-method :put + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 403)) + + (ltu/is-last-event uri {:name "module.edit" :description "module.edit attempt failed." - :category "edit" - :success false - :linked-identifiers [] - :authn-info authn-info-anon - :acl {:owners event-owners}}) - - ;; insert 5 more versions - (doseq [_ (range 5)] - (-> session-admin - (request abs-uri - :request-method :put - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 200)) - - (ltu/is-last-event uri + :category "edit" + :success false + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners event-owners}}) + + ;; insert 5 more versions + (doseq [_ (range 5)] + (-> session-admin + (request abs-uri + :request-method :put + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 200)) + + (ltu/is-last-event uri {:name "module.edit" :description (str admin-group-name " edited module " uri ".") - :category "edit" - :success true - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners event-owners}})) - - (let [versions (-> session-admin - (request abs-uri - :request-method :put - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - :versions)] - (is (= 7 (count versions))) - - ;; extract by indexes or last - (doseq [[i n] [["_0" 0] ["_1" 1] ["" 6]]] - (let [content-id (-> session-admin - (request (str abs-uri i)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - :content - :id)] - (is (= (-> versions (nth n) :href) content-id)) - (is (= (-> versions (nth n) :author) "someone")) - (is (= (-> versions (nth n) :commit) "wip"))))) - - ;; publish - (let [publish-url (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/get-op-url :publish))] - - (testing "publish last version" - (-> session - (request publish-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "published successfully")) - - (ltu/is-last-event uri + :category "edit" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners event-owners}})) + + (let [versions (-> session-admin + (request abs-uri + :request-method :put + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + :versions)] + (is (= 7 (count versions))) + + ;; extract by indexes or last + (doseq [[i n] [["_0" 0] ["_1" 1] ["" 6]]] + (let [content-id (-> session-admin + (request (str abs-uri i)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + :content + :id)] + (is (= (-> versions (nth n) :href) content-id)) + (is (= (-> versions (nth n) :author) "someone")) + (is (= (-> versions (nth n) :commit) "wip"))))))) + + +(defn publish-unpublish + [session uri event-owners authn-info] + ;; publish + (let [abs-uri (str p/service-context uri) + publish-url (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/get-op-url :publish))] + + (testing "publish last version" + (-> session + (request publish-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event uri {:name "module.publish" :description (str user-name-or-id " executed action publish on module " uri ".") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}})) - - (testing "operation urls of specific version" - (let [abs-uri-v2 (str abs-uri "_2") - resp (-> session - (request (str abs-uri "_2")) - (ltu/body->edn) - (ltu/is-status 200)) - publish-url (ltu/get-op-url resp :publish) - unpublish-url (ltu/get-op-url resp :unpublish) - edit-url (ltu/get-op-url resp :edit) - delete-url (ltu/get-op-url resp :delete) - delete-version-url (ltu/get-op-url resp :delete-version)] - (is (= publish-url (str abs-uri-v2 "/publish"))) - (is (= unpublish-url (str abs-uri-v2 "/unpublish"))) - (is (= delete-version-url (str abs-uri-v2 "/delete-version"))) - (is (= delete-url abs-uri)) - (is (= edit-url abs-uri)))) - - (testing "publish specific version" - (-> session - (request (str abs-uri "_2/publish")) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "published successfully")) - - (ltu/is-last-event (str uri "_2") + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) + + (testing "operation urls of specific version" + (let [abs-uri-v2 (str abs-uri "_2") + resp (-> session + (request (str abs-uri "_2")) + (ltu/body->edn) + (ltu/is-status 200)) + publish-url (ltu/get-op-url resp :publish) + unpublish-url (ltu/get-op-url resp :unpublish) + edit-url (ltu/get-op-url resp :edit) + delete-url (ltu/get-op-url resp :delete) + delete-version-url (ltu/get-op-url resp :delete-version)] + (is (= publish-url (str abs-uri-v2 "/publish"))) + (is (= unpublish-url (str abs-uri-v2 "/unpublish"))) + (is (= delete-version-url (str abs-uri-v2 "/delete-version"))) + (is (= delete-url abs-uri)) + (is (= edit-url abs-uri)))) + + (testing "publish specific version" + (-> session + (request (str abs-uri "_2/publish")) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event (str uri "_2") {:name "module.publish" :description (str user-name-or-id " executed action publish on module " uri "_2.") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}})) - - (let [unpublish-url (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/is-key-value #(-> % last :published) :versions true) - (ltu/is-key-value #(-> % (nth 2) :published) :versions true) - (ltu/is-key-value :published true) - (ltu/get-op-url :unpublish))] - - (-> session - (request unpublish-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "unpublished successfully"))) - - (ltu/is-last-event uri + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) + + (let [unpublish-url (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/is-key-value #(-> % last :published) :versions true) + (ltu/is-key-value #(-> % (nth 2) :published) :versions true) + (ltu/is-key-value :published true) + (ltu/get-op-url :unpublish))] + + (-> session + (request unpublish-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "unpublished successfully"))) + + (ltu/is-last-event uri {:name "module.unpublish" :description (str user-name-or-id " executed action unpublish on module " uri ".") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - ; publish is idempotent - (-> session - (request (str abs-uri "_2/publish")) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "published successfully")) - - (ltu/is-last-event (str uri "_2") + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + ; publish is idempotent + (-> session + (request (str abs-uri "_2/publish")) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event (str uri "_2") {:name "module.publish" :description (str user-name-or-id " executed action publish on module " uri "_2.") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/is-key-value #(-> % last :published) :versions false) - (ltu/is-key-value :published true) - (ltu/get-op-url :unpublish)) - - (-> session - (request (str abs-uri "_2/unpublish")) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "unpublished successfully")) - - (ltu/is-last-event (str uri "_2") + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/is-key-value #(-> % last :published) :versions false) + (ltu/is-key-value :published true) + (ltu/get-op-url :unpublish)) + + (-> session + (request (str abs-uri "_2/unpublish")) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "unpublished successfully")) + + (ltu/is-last-event (str uri "_2") {:name "module.unpublish" :description (str user-name-or-id " executed action unpublish on module " uri "_2.") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/is-key-value #(-> % (nth 2) :published) :versions false) - (ltu/is-key-value :published false) - (ltu/get-op-url :unpublish))) - - (testing "edit module without putting the module-content should not create new version" - (is (= 7 (-> session-admin - (request abs-uri - :request-method :put - :body (json/write-str (dissoc valid-entry :content :path))) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - :versions - count)))) + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) - (doseq [i ["_0/delete-version" "_1/delete-version"]] - (-> session-admin - (request (str abs-uri i)) - (ltu/body->edn) - (ltu/is-status 200)) + (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/is-key-value #(-> % (nth 2) :published) :versions false) + (ltu/is-key-value :published false) + (ltu/get-op-url :unpublish)))) + + +(defn versions + [uri valid-entry event-owners] + (let [abs-uri (str p/service-context uri)] + (testing "edit module without putting the module-content should not create new version" + (is (= 7 (-> session-admin + (request abs-uri + :request-method :put + :body (json/write-str (dissoc valid-entry :content :path))) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + :versions + count)))) + + (doseq [i ["_0/delete-version" "_1/delete-version"]] + (-> session-admin + (request (str abs-uri i)) + (ltu/body->edn) + (ltu/is-status 200)) - (-> session-admin - (request (str abs-uri i)) - (ltu/body->edn) - (ltu/is-status 404))) + (-> session-admin + (request (str abs-uri i)) + (ltu/body->edn) + (ltu/is-status 404))) - (testing "delete latest version without specifying version" - (-> session-admin - (request (str abs-uri "/delete-version")) - (ltu/body->edn) - (ltu/is-status 200))) + (testing "delete latest version without specifying version" + (-> session-admin + (request (str abs-uri "/delete-version")) + (ltu/body->edn) + (ltu/is-status 200))) - (ltu/is-last-event uri + (ltu/is-last-event uri {:name "module.delete-version" :description (str admin-group-name " executed action delete-version on module " uri ".") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners event-owners}}) + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners event-owners}}) - (testing "delete out of bound index should return 404" - (-> session-admin - (request (str abs-uri "_50/delete-version")) - (ltu/body->edn) - (ltu/is-status 404))) + (testing "delete out of bound index should return 404" + (-> session-admin + (request (str abs-uri "_50/delete-version")) + (ltu/body->edn) + (ltu/is-status 404))) - (-> session-admin - (request (str abs-uri "_50")) - (ltu/body->edn) - (ltu/is-status 404)) + (-> session-admin + (request (str abs-uri "_50")) + (ltu/body->edn) + (ltu/is-status 404)))) - ;; delete: NOK for anon - (-> session-anon - (request abs-uri - :request-method :delete) - (ltu/body->edn) - (ltu/is-status 403)) - (-> session-admin - (request abs-uri - :request-method :delete) - (ltu/body->edn) - (ltu/is-status 200)) +(defn delete-module + [uri event-owners] + (let [abs-uri (str p/service-context uri)] + ;; delete: NOK for anon + (-> session-anon + (request abs-uri + :request-method :delete) + (ltu/body->edn) + (ltu/is-status 403)) + + (-> session-admin + (request abs-uri + :request-method :delete) + (ltu/body->edn) + (ltu/is-status 200)) - (ltu/is-last-event uri + (ltu/is-last-event uri {:name "module.delete" :description (str admin-group-name " deleted module " uri ".") - :category "delete" - :success true - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners event-owners}}) + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners event-owners}}) + + ;; verify that the resource was deleted. + (-> session-admin + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 404)))) + + +(defn lifecycle-test-module + [subtype valid-content] + (let [valid-entry (build-valid-entry subtype valid-content)] + (create-module-nok valid-entry) + ;; adding, retrieving and deleting entry as user should succeed + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane]]] + (let [uri (create-module session valid-entry event-owners authn-info) + module (retrieve-module uri valid-entry valid-content)] + (edit-module uri valid-entry event-owners) + (publish-unpublish session uri event-owners authn-info) + (versions uri valid-entry event-owners) + (delete-module uri event-owners))))) - ;; verify that the resource was deleted. - (-> session-admin - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 404)))))) (deftest lifecycle-component (let [valid-component {:author "someone" diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig index 3d91e3897..aacfd3127 100644 --- a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig +++ b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig @@ -41,26 +41,40 @@ (ltu/is-status 201))) paths))) -(defn lifecycle-test-module +(def session-anon + (-> (session (ltu/ring-app)) + (content-type "application/json"))) +(def session-admin + (header session-anon authn-info-header + "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon")) +(def session-user + (header session-anon authn-info-header + "user/jane user/jane group/nuvla-user group/nuvla-anon")) + +(def authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]}) +(def authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]}) +(def authn-info-anon {:claims ["group/nuvla-anon"]}) + +(defn build-valid-entry [subtype valid-content] - (let [session-anon (-> (session (ltu/ring-app)) - (content-type "application/json")) - session-admin (header session-anon authn-info-header - "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") - session-user (header session-anon authn-info-header - "user/jane user/jane group/nuvla-user group/nuvla-anon") + {:parent-path "a/b" + :path "a/b/c" + :subtype subtype - valid-entry {:parent-path "a/b" - :path "a/b/c" - :subtype subtype + :compatibility "docker-compose" - :compatibility "docker-compose" + :logo-url "https://example.org/logo" - :logo-url "https://example.org/logo" + :data-accept-content-types ["application/json" "application/x-something"] + :data-access-protocols ["http+s3" "posix+nfs"] - :data-accept-content-types ["application/json" "application/x-something"] - :data-access-protocols ["http+s3" "posix+nfs"] + :content valid-content}) +<<<<<<< HEAD :content valid-content} authn-info-admin {:user-id "group/nuvla-admin" :active-claim "group/nuvla-admin" @@ -70,6 +84,11 @@ :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} authn-info-anon {:claims ["group/nuvla-anon"]} admin-group-name "Nuvla Administrator Group"] +======= +(defn create-module-nok + [valid-entry] + (let [] +>>>>>>> b3844f4f (Fix tests) ;; create: NOK for anon (-> session-anon @@ -80,12 +99,8 @@ (ltu/is-status 403)) (ltu/is-last-event nil -<<<<<<< HEAD {:name "module.add" -======= - {:event-type "module.add" :description "module.add attempt failed." ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "add" :success false :linked-identifiers [] @@ -117,12 +132,8 @@ (ltu/is-status 400)) (ltu/is-last-event nil -<<<<<<< HEAD {:name "module.add" -======= - {:event-type "module.add" :description "module.add attempt failed." ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "add" :success false :linked-identifiers [] @@ -130,7 +141,6 @@ :acl {:owners ["group/nuvla-admin"]}}) (when (utils/is-application? valid-entry) - (testing "application should have compatibility attribute set" (-> session-user (request base-uri @@ -138,9 +148,340 @@ :body (json/write-str (dissoc valid-entry :compatibility))) (ltu/body->edn) (ltu/is-status 400) - (ltu/message-matches "Application subtype should have compatibility attribute set!")))) + (ltu/message-matches "Application subtype should have compatibility attribute set!")))))) + + +(defn create-module + [session valid-entry event-owners authn-info] + (let [uri (-> session + (request base-uri + :request-method :post + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location)) + + abs-uri (str p/service-context uri)] + + (ltu/is-last-event uri + {:event-type "module.add" + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + ;; retrieve: NOK for anon + (-> session-anon + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 403)) + + uri)) + +(defn retrieve-module + [uri valid-entry valid-content] + (let [abs-uri (str p/service-context uri) + {:keys [content] :as module} (-> session-admin + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-key-value :compatibility "docker-compose") + (as-> m (if (utils/is-application? valid-entry) + (ltu/is-operation-present m :validate-docker-compose) + (ltu/is-operation-absent m :validate-docker-compose))) + (ltu/body))] + (is (= valid-content (select-keys content (keys valid-content)))) + module)) + + +(defn edit-module + [uri valid-entry event-owners] + (let [abs-uri (str p/service-context uri)] + ;; edit: NOK for anon + (-> session-anon + (request abs-uri + :request-method :put + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 403)) + + (ltu/is-last-event uri + {:event-type "module.edit" + :category "edit" + :success false + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners event-owners}}) + + ;; insert 5 more versions + (doseq [_ (range 5)] + (-> session-admin + (request abs-uri + :request-method :put + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:event-type "module.edit" + :category "edit" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners event-owners}})) + + (let [versions (-> session-admin + (request abs-uri + :request-method :put + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + :versions)] + (is (= 7 (count versions))) + + ;; extract by indexes or last + (doseq [[i n] [["_0" 0] ["_1" 1] ["" 6]]] + (let [content-id (-> session-admin + (request (str abs-uri i)) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + :content + :id)] + (is (= (-> versions (nth n) :href) content-id)) + (is (= (-> versions (nth n) :author) "someone")) + (is (= (-> versions (nth n) :commit) "wip"))))))) + + +(defn publish-unpublish + [session uri event-owners authn-info] + ;; publish + (let [abs-uri (str p/service-context uri) + publish-url (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/get-op-url :publish))] + + (testing "publish last version" + (-> session + (request publish-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event uri + {:event-type "module.publish" + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) + + (testing "operation urls of specific version" + (let [abs-uri-v2 (str abs-uri "_2") + resp (-> session + (request (str abs-uri "_2")) + (ltu/body->edn) + (ltu/is-status 200)) + publish-url (ltu/get-op-url resp :publish) + unpublish-url (ltu/get-op-url resp :unpublish) + edit-url (ltu/get-op-url resp :edit) + delete-url (ltu/get-op-url resp :delete) + delete-version-url (ltu/get-op-url resp :delete-version)] + (is (= publish-url (str abs-uri-v2 "/publish"))) + (is (= unpublish-url (str abs-uri-v2 "/unpublish"))) + (is (= delete-version-url (str abs-uri-v2 "/delete-version"))) + (is (= delete-url abs-uri)) + (is (= edit-url abs-uri)))) + + (testing "publish specific version" + (-> session + (request (str abs-uri "_2/publish")) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event (str uri "_2") + {:event-type "module.publish" + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) + + (let [unpublish-url (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/is-key-value #(-> % last :published) :versions true) + (ltu/is-key-value #(-> % (nth 2) :published) :versions true) + (ltu/is-key-value :published true) + (ltu/get-op-url :unpublish))] + + (-> session + (request unpublish-url) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "unpublished successfully"))) + + (ltu/is-last-event uri + {:event-type "module.unpublish" + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + ; publish is idempotent + (-> session + (request (str abs-uri "_2/publish")) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "published successfully")) + + (ltu/is-last-event (str uri "_2") + {:event-type "module.publish" + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/is-key-value #(-> % last :published) :versions false) + (ltu/is-key-value :published true) + (ltu/get-op-url :unpublish)) + + (-> session + (request (str abs-uri "_2/unpublish")) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/message-matches "unpublished successfully")) + + (ltu/is-last-event (str uri "_2") + {:event-type "module.unpublish" + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + + (-> session + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-operation-present :publish) + (ltu/is-operation-present :unpublish) + (ltu/is-key-value #(-> % (nth 2) :published) :versions false) + (ltu/is-key-value :published false) + (ltu/get-op-url :unpublish)))) + + +(defn versions + [uri valid-entry event-owners] + (let [abs-uri (str p/service-context uri)] + (testing "edit module without putting the module-content should not create new version" + (is (= 7 (-> session-admin + (request abs-uri + :request-method :put + :body (json/write-str (dissoc valid-entry :content :path))) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body) + :versions + count)))) + + (doseq [i ["_0/delete-version" "_1/delete-version"]] + (-> session-admin + (request (str abs-uri i)) + (ltu/body->edn) + (ltu/is-status 200)) + + + (-> session-admin + (request (str abs-uri i)) + (ltu/body->edn) + (ltu/is-status 404))) + + + (testing "delete latest version without specifying version" + (-> session-admin + (request (str abs-uri "/delete-version")) + (ltu/body->edn) + (ltu/is-status 200))) + + (ltu/is-last-event uri + {:event-type "module.delete-version" + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners event-owners}}) + + + (testing "delete out of bound index should return 404" + (-> session-admin + (request (str abs-uri "_50/delete-version")) + (ltu/body->edn) + (ltu/is-status 404))) + + (-> session-admin + (request (str abs-uri "_50")) + (ltu/body->edn) + (ltu/is-status 404)))) + + +(defn delete-module + [uri event-owners] + (let [abs-uri (str p/service-context uri)] + ;; delete: NOK for anon + (-> session-anon + (request abs-uri + :request-method :delete) + (ltu/body->edn) + (ltu/is-status 403)) + + (-> session-admin + (request abs-uri + :request-method :delete) + (ltu/body->edn) + (ltu/is-status 200)) + + + (ltu/is-last-event uri + {:event-type "module.delete" + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners event-owners}}) + + ;; verify that the resource was deleted. + (-> session-admin + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 404)))) + + +(defn lifecycle-test-module + [subtype valid-content] + (let [valid-entry (build-valid-entry subtype valid-content)] + (create-module-nok valid-entry) ;; adding, retrieving and deleting entry as user should succeed +<<<<<<< HEAD (doseq [[session event-owners authn-info user-name-or-id] [[session-admin ["group/nuvla-admin"] authn-info-admin admin-group-name] [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane "user/jane"]]] @@ -155,12 +496,8 @@ abs-uri (str p/service-context uri)] (ltu/is-last-event uri -<<<<<<< HEAD {:name "module.add" -======= - {:event-type "module.add" :description (str user-name-or-id " added module " uri ".") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "add" :success true :linked-identifiers [] @@ -193,12 +530,8 @@ (ltu/is-status 403)) (ltu/is-last-event uri -<<<<<<< HEAD {:name "module.edit" -======= - {:event-type "module.edit" :description "module.edit attempt failed." ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "edit" :success false :linked-identifiers [] @@ -215,12 +548,8 @@ (ltu/is-status 200)) (ltu/is-last-event uri -<<<<<<< HEAD {:name "module.edit" -======= - {:event-type "module.edit" :description (str admin-group-name " edited module " uri ".") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "edit" :success true :linked-identifiers [] @@ -267,12 +596,8 @@ (ltu/message-matches "published successfully")) (ltu/is-last-event uri -<<<<<<< HEAD {:name "module.publish" -======= - {:event-type "module.publish" :description (str user-name-or-id " executed action publish on module " uri ".") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "action" :success true :linked-identifiers [] @@ -304,12 +629,8 @@ (ltu/message-matches "published successfully")) (ltu/is-last-event (str uri "_2") -<<<<<<< HEAD {:name "module.publish" -======= - {:event-type "module.publish" :description (str user-name-or-id " executed action publish on module " uri "_2.") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "action" :success true :linked-identifiers [] @@ -334,12 +655,8 @@ (ltu/message-matches "unpublished successfully"))) (ltu/is-last-event uri -<<<<<<< HEAD {:name "module.unpublish" -======= - {:event-type "module.unpublish" :description (str user-name-or-id " executed action unpublish on module " uri ".") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "action" :success true :linked-identifiers [] @@ -354,12 +671,8 @@ (ltu/message-matches "published successfully")) (ltu/is-last-event (str uri "_2") -<<<<<<< HEAD {:name "module.publish" -======= - {:event-type "module.publish" :description (str user-name-or-id " executed action publish on module " uri "_2.") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "action" :success true :linked-identifiers [] @@ -383,12 +696,8 @@ (ltu/message-matches "unpublished successfully")) (ltu/is-last-event (str uri "_2") -<<<<<<< HEAD {:name "module.unpublish" -======= - {:event-type "module.unpublish" :description (str user-name-or-id " executed action unpublish on module " uri "_2.") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "action" :success true :linked-identifiers [] @@ -437,12 +746,8 @@ (ltu/is-last-event uri -<<<<<<< HEAD {:name "module.delete-version" -======= - {:event-type "module.delete-version" :description (str admin-group-name " executed action delete-version on module " uri ".") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "action" :success true :linked-identifiers [] @@ -476,12 +781,8 @@ (ltu/is-last-event uri -<<<<<<< HEAD {:name "module.delete" -======= - {:event-type "module.delete" :description (str admin-group-name " deleted module " uri ".") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) :category "delete" :success true :linked-identifiers [] @@ -493,6 +794,18 @@ (request abs-uri) (ltu/body->edn) (ltu/is-status 404)))))) +======= + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane]]] + (let [uri (create-module session valid-entry event-owners authn-info) + module (retrieve-module uri valid-entry valid-content)] + (edit-module uri valid-entry event-owners) + (publish-unpublish session uri event-owners authn-info) + (versions uri valid-entry event-owners) + (delete-module uri event-owners))))) + +>>>>>>> b3844f4f (Fix tests) (deftest lifecycle-component (let [valid-component {:author "someone" diff --git a/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc b/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc index 95dd05a4a..5b7e0b001 100644 --- a/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc +++ b/code/test/sixsq/nuvla/server/resources/spec/event_test.cljc @@ -31,9 +31,7 @@ (deftest check-reference (let [updated-event (assoc-in valid-event [:content :resource :href] "another/valid-identifier")] - (stu/is-valid ::event/schema updated-event)) - (let [updated-event (assoc-in valid-event [:content :resource :href] "/not a valid reference/")] - (stu/is-invalid ::event/schema updated-event))) + (stu/is-valid ::event/schema updated-event))) (deftest check-severity From 82f344c96ccc4c14dfbeba569c8dc54b0576c8df Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Fri, 25 Aug 2023 16:24:19 +0200 Subject: [PATCH 13/17] Rebase --- .../resources/module_lifecycle_test.clj | 174 +++++++++--------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj index 15f584123..a979c9d41 100644 --- a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj @@ -59,6 +59,9 @@ :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]}) (def authn-info-anon {:claims ["group/nuvla-anon"]}) +(def admin-group-name "Nuvla Administrator Group") + + (defn build-valid-entry [subtype valid-content] {:parent-path "a/b" @@ -76,73 +79,70 @@ (defn create-module-nok [valid-entry] - (let [] - admin-group-name "Nuvla Administrator Group"] - - ;; create: NOK for anon - (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 403)) - - (ltu/is-last-event nil - {:name "module.add" - :description "module.add attempt failed." - :category "add" - :success false - :linked-identifiers [] - :authn-info authn-info-anon - :acl {:owners ["group/nuvla-admin"]}}) - - ;; queries: NOK for anon - (-> session-anon + ;; create: NOK for anon + (-> session-anon + (request base-uri + :request-method :post + :body (json/write-str valid-entry)) + (ltu/body->edn) + (ltu/is-status 403)) + + (ltu/is-last-event nil + {:name "module.add" + :description "module.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners ["group/nuvla-admin"]}}) + + ;; queries: NOK for anon + (-> session-anon + (request base-uri) + (ltu/body->edn) + (ltu/is-status 403)) + + (doseq [session [session-admin session-user]] + (-> session (request base-uri) (ltu/body->edn) - (ltu/is-status 403)) - - (doseq [session [session-admin session-user]] - (-> session - (request base-uri) + (ltu/is-status 200) + (ltu/is-count zero?))) + + ;; Creating editable parent project + (create-parent-projects (:path valid-entry) session-user) + + ;; invalid module subtype + (-> session-admin + (request base-uri + :request-method :post + :body (json/write-str (assoc valid-entry :subtype "bad-module-subtype"))) + (ltu/body->edn) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "module.add" + :description "module.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners ["group/nuvla-admin"]}}) + + (when (utils/is-application? valid-entry) + + (testing "application should have compatibility attribute set" + (-> session-user + (request base-uri + :request-method :post + :body (json/write-str (dissoc valid-entry :compatibility))) (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count zero?))) - - ;; Creating editable parent project - (create-parent-projects (:path valid-entry) session-user) - - ;; invalid module subtype - (-> session-admin - (request base-uri - :request-method :post - :body (json/write-str (assoc valid-entry :subtype "bad-module-subtype"))) - (ltu/body->edn) - (ltu/is-status 400)) - - (ltu/is-last-event nil - {:name "module.add" - :description "module.add attempt failed." - :category "add" - :success false - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners ["group/nuvla-admin"]}}) - - (when (utils/is-application? valid-entry) - - (testing "application should have compatibility attribute set" - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (dissoc valid-entry :compatibility))) - (ltu/body->edn) - (ltu/is-status 400) - (ltu/message-matches "Application subtype should have compatibility attribute set!")))))) + (ltu/is-status 400) + (ltu/message-matches "Application subtype should have compatibility attribute set!"))))) (defn create-module - [session valid-entry event-owners authn-info] + [session valid-entry event-owners authn-info user-name-or-id] (let [uri (-> session (request base-uri :request-method :post @@ -155,7 +155,7 @@ (ltu/is-last-event uri {:event-type "module.add" - :description (str user-name-or-id " added module " uri ".") + :description (str user-name-or-id " added module " uri ".") :category "add" :success true :linked-identifiers [] @@ -198,8 +198,8 @@ (ltu/is-status 403)) (ltu/is-last-event uri - {:name "module.edit" - :description "module.edit attempt failed." + {:name "module.edit" + :description "module.edit attempt failed." :category "edit" :success false :linked-identifiers [] @@ -216,8 +216,8 @@ (ltu/is-status 200)) (ltu/is-last-event uri - {:name "module.edit" - :description (str admin-group-name " edited module " uri ".") + {:name "module.edit" + :description (str admin-group-name " edited module " uri ".") :category "edit" :success true :linked-identifiers [] @@ -249,7 +249,7 @@ (defn publish-unpublish - [session uri event-owners authn-info] + [session uri event-owners authn-info user-name-or-id] ;; publish (let [abs-uri (str p/service-context uri) publish-url (-> session @@ -268,8 +268,8 @@ (ltu/message-matches "published successfully")) (ltu/is-last-event uri - {:name "module.publish" - :description (str user-name-or-id " executed action publish on module " uri ".") + {:name "module.publish" + :description (str user-name-or-id " executed action publish on module " uri ".") :category "action" :success true :linked-identifiers [] @@ -301,8 +301,8 @@ (ltu/message-matches "published successfully")) (ltu/is-last-event (str uri "_2") - {:name "module.publish" - :description (str user-name-or-id " executed action publish on module " uri "_2.") + {:name "module.publish" + :description (str user-name-or-id " executed action publish on module " uri "_2.") :category "action" :success true :linked-identifiers [] @@ -327,8 +327,8 @@ (ltu/message-matches "unpublished successfully"))) (ltu/is-last-event uri - {:name "module.unpublish" - :description (str user-name-or-id " executed action unpublish on module " uri ".") + {:name "module.unpublish" + :description (str user-name-or-id " executed action unpublish on module " uri ".") :category "action" :success true :linked-identifiers [] @@ -343,8 +343,8 @@ (ltu/message-matches "published successfully")) (ltu/is-last-event (str uri "_2") - {:name "module.publish" - :description (str user-name-or-id " executed action publish on module " uri "_2.") + {:name "module.publish" + :description (str user-name-or-id " executed action publish on module " uri "_2.") :category "action" :success true :linked-identifiers [] @@ -368,8 +368,8 @@ (ltu/message-matches "unpublished successfully")) (ltu/is-last-event (str uri "_2") - {:name "module.unpublish" - :description (str user-name-or-id " executed action unpublish on module " uri "_2.") + {:name "module.unpublish" + :description (str user-name-or-id " executed action unpublish on module " uri "_2.") :category "action" :success true :linked-identifiers [] @@ -422,8 +422,8 @@ (ltu/is-last-event uri - {:name "module.delete-version" - :description (str admin-group-name " executed action delete-version on module " uri ".") + {:name "module.delete-version" + :description (str admin-group-name " executed action delete-version on module " uri ".") :category "action" :success true :linked-identifiers [] @@ -461,8 +461,8 @@ (ltu/is-last-event uri - {:name "module.delete" - :description (str admin-group-name " deleted module " uri ".") + {:name "module.delete" + :description (str admin-group-name " deleted module " uri ".") :category "delete" :success true :linked-identifiers [] @@ -481,13 +481,13 @@ (let [valid-entry (build-valid-entry subtype valid-content)] (create-module-nok valid-entry) ;; adding, retrieving and deleting entry as user should succeed - (doseq [[session event-owners authn-info] - [[session-admin ["group/nuvla-admin"] authn-info-admin] - [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane]]] - (let [uri (create-module session valid-entry event-owners authn-info) - module (retrieve-module uri valid-entry valid-content)] + (doseq [[session event-owners authn-info user-name-or-id] + [[session-admin ["group/nuvla-admin"] authn-info-admin admin-group-name] + [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane "user/jane"]]] + (let [uri (create-module session valid-entry event-owners authn-info user-name-or-id) + _module (retrieve-module uri valid-entry valid-content)] (edit-module uri valid-entry event-owners) - (publish-unpublish session uri event-owners authn-info) + (publish-unpublish session uri event-owners authn-info user-name-or-id) (versions uri valid-entry event-owners) (delete-module uri event-owners))))) From eb2d6138aa08f3901bde6369d391fdf92de4fb78 Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Mon, 28 Aug 2023 10:18:31 +0200 Subject: [PATCH 14/17] Legacy events commented out. Added checks on event description. --- .../nuvla/server/resources/deployment.clj | 40 ++++-- .../server/resources/deployment/utils.clj | 6 +- .../resources/deployment_lifecycle_test.clj | 134 +++++++++--------- .../resources/module_lifecycle_test.clj | 4 +- 4 files changed, 103 insertions(+), 81 deletions(-) diff --git a/code/src/sixsq/nuvla/server/resources/deployment.clj b/code/src/sixsq/nuvla/server/resources/deployment.clj index c4b06b5b0..d6a5a3000 100644 --- a/code/src/sixsq/nuvla/server/resources/deployment.clj +++ b/code/src/sixsq/nuvla/server/resources/deployment.clj @@ -14,7 +14,6 @@ a container orchestration engine. [sixsq.nuvla.server.resources.common.std-crud :as std-crud] [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.deployment.utils :as utils] - [sixsq.nuvla.server.resources.event.utils :as event-utils] [sixsq.nuvla.server.resources.job.interface :as job-interface] [sixsq.nuvla.server.resources.module.utils :as module-utils] [sixsq.nuvla.server.resources.resource-metadata :as md] @@ -138,8 +137,7 @@ a container orchestration engine. (defn create-deployment [{:keys [parent deployment-set] :as deployment} {:keys [base-uri] :as request}] (some-> parent (crud/get-resource-throw-nok request)) - (let [authn-info (auth/current-authentication request) - deployment-set-name (some-> deployment-set + (let [deployment-set-name (some-> deployment-set crud/retrieve-by-id-as-admin :name) ;; FIXME: Correct the value passed to the python API. @@ -150,13 +148,13 @@ a container orchestration engine. :owner (auth/current-active-claim request)) (cond-> deployment-set-name (assoc :deployment-set-name deployment-set-name)) (utils/throw-when-payment-required request)) - create-response (add-impl (assoc request :body deployment)) + create-response (add-impl (assoc request :body deployment))] - deployment-id (get-in create-response [:body :resource-id]) - - msg (get-in create-response [:body :message])] - - (event-utils/create-event deployment-id msg (a/default-acl authn-info) :category "state") + ;; legacy event logging + #_(let [authn-info (auth/current-authentication request) + deployment-id (get-in create-response [:body :resource-id]) + msg (get-in create-response [:body :message])] + (event-utils/create-event deployment-id msg (a/default-acl authn-info) :category "state")) create-response)) @@ -566,6 +564,30 @@ a container orchestration engine. false) +(defmethod ec/event-description "deployment.start" + [{:keys [success] {:keys [user-id]} :authn-info :as _event}] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " started deployment.")) + "Deployment start attempt failed.")) + + +(defmethod ec/event-description "deployment.stop" + [{:keys [success] {:keys [user-id]} :authn-info :as _event}] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " stopped deployment.")) + "Deployment stop attempt failed.")) + + +(defmethod ec/event-description "deployment.clone" + [{:keys [success] {:keys [user-id]} :authn-info :as _event}] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " cloned deployment.")) + "Deployment clone attempt failed.")) + + ;; ;; initialization ;; diff --git a/code/src/sixsq/nuvla/server/resources/deployment/utils.clj b/code/src/sixsq/nuvla/server/resources/deployment/utils.clj index 00698b0ad..114a091fe 100644 --- a/code/src/sixsq/nuvla/server/resources/deployment/utils.clj +++ b/code/src/sixsq/nuvla/server/resources/deployment/utils.clj @@ -14,7 +14,6 @@ [sixsq.nuvla.server.resources.configuration-nuvla :as config-nuvla] [sixsq.nuvla.server.resources.credential :as credential] [sixsq.nuvla.server.resources.credential-template-api-key :as cred-api-key] - [sixsq.nuvla.server.resources.event.utils :as event-utils] [sixsq.nuvla.server.resources.job :as job] [sixsq.nuvla.server.resources.job.interface :as job-interface] [sixsq.nuvla.server.resources.resource-log :as resource-log] @@ -122,7 +121,10 @@ (throw (r/ex-response (format "unable to create async job to %s deployment" action) 500 id))) (ec/add-linked-identifier job-id) - (event-utils/create-event id job-msg (a/default-acl (auth/current-authentication request)) :category "state") + + ;; Legacy event logging + #_(event-utils/create-event id job-msg (a/default-acl (auth/current-authentication request)) :category "state") + (r/map-response job-msg 202 id job-id))) diff --git a/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj index 2fbb74c0d..ff87e4536 100644 --- a/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj @@ -164,15 +164,14 @@ deployment-url (str p/service-context deployment-id)] - (ltu/are-last-events deployment-id - [{:event-type "legacy" - :category "state"} - {:event-type "deployment.add" - :category "add" - :success true - :linked-identifiers [] - :authn-info authn-info-jane - :acl {:owners event-owners-jane}}]) + (ltu/is-last-event deployment-id + {:name "deployment.add" + :description (str "user/jane added deployment " deployment-id ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners event-owners-jane}}) ;; admin/user should see one deployment (doseq [session [session-user session-admin]] @@ -212,7 +211,8 @@ (ltu/is-key-value :owners :acl ["user/jane" "user/tarzan"])) (ltu/is-last-event deployment-id - {:event-type "deployment.edit" + {:name "deployment.edit" + :description (str "user/jane edited deployment " deployment-id ".") :category "edit" :success true :linked-identifiers [] @@ -233,7 +233,8 @@ (ltu/is-status 403)) (ltu/is-last-event deployment-id - {:event-type "deployment.edit" + {:name "deployment.edit" + :description "deployment.edit attempt failed." :category "edit" :success false :linked-identifiers [] @@ -249,15 +250,14 @@ (ltu/location)) job-url (ltu/href->url job-id)] - (ltu/are-last-events deployment-id - [{:event-type "legacy" - :category "state"} - {:event-type "deployment.start" - :category "action" - :success true - :linked-identifiers [job-id] - :authn-info authn-info-jane - :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}]) + (ltu/is-last-event deployment-id + {:name "deployment.start" + :description "user/jane started deployment." + :category "action" + :success true + :linked-identifiers [job-id] + :authn-info authn-info-jane + :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}) (-> session-user (request job-url) @@ -445,15 +445,14 @@ (ltu/location)) job-url (ltu/href->url job-id)] - (ltu/are-last-events deployment-id - [{:event-type "legacy" - :category "state"} - {:event-type "deployment.stop" - :category "action" - :success true - :linked-identifiers [job-id] - :authn-info authn-info-jane - :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}]) + (ltu/is-last-event deployment-id + {:name "deployment.stop" + :description "user/jane stopped deployment." + :category "action" + :success true + :linked-identifiers [job-id] + :authn-info authn-info-jane + :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}) (-> session-user (request job-url @@ -531,17 +530,14 @@ deployment-url-from-dep (ltu/href->url deployment-id-from-dep)] (ltu/is-last-event deployment-id - {:event-type "deployment.clone" + {:name "deployment.clone" + :description "user/jane cloned deployment." :category "action" :success true :linked-identifiers [deployment-id-from-dep] :authn-info authn-info-jane :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}) - (ltu/is-last-event deployment-id-from-dep - {:event-type "legacy" - :category "state"}) - (-> session-user (request deployment-url-from-dep :request-method :delete) @@ -549,11 +545,12 @@ (ltu/is-status 200)) (ltu/is-last-event deployment-id-from-dep - {:event-type "deployment.delete" - :category "delete" - :success true - :authn-info authn-info-jane - :acl {:owners event-owners-jane}})) + {:name "deployment.delete" + :description (str "user/jane deleted deployment " deployment-id-from-dep ".") + :category "delete" + :success true + :authn-info authn-info-jane + :acl {:owners event-owners-jane}})) ;; verify that the user can delete the deployment (-> session-user @@ -563,11 +560,12 @@ (ltu/is-status 200)) (ltu/is-last-event deployment-id - {:event-type "deployment.delete" - :category "delete" - :success true - :authn-info authn-info-jane - :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}) + {:name "deployment.delete" + :description (str "user/jane deleted deployment " deployment-id ".") + :category "delete" + :success true + :authn-info authn-info-jane + :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}) ;; verify that the deployment has disappeared (-> session-user @@ -772,30 +770,30 @@ (deftest lifecycle-bulk-update-force-delete (binding [config-nuvla/*stripe-api-key* nil] - (let [session-anon (-> (ltu/ring-app) - session - (content-type "application/json")) - session-user (header session-anon authn-info-header - "user/jane user/jane group/nuvla-user group/nuvla-anon") + (let [session-anon (-> (ltu/ring-app) + session + (content-type "application/json")) + session-user (header session-anon authn-info-header + "user/jane user/jane group/nuvla-user group/nuvla-anon") ;; setup a module that can be referenced from the deployment - module-id (setup-module session-user (valid-module "component" valid-component)) + module-id (setup-module session-user (valid-module "component" valid-component)) - valid-deployment {:module {:href module-id}} - deployment-id (-> session-user - (request base-uri - :request-method :post - :body (json/write-str valid-deployment)) - (ltu/body->edn) - (ltu/is-status 201) - (ltu/location)) + valid-deployment {:module {:href module-id}} + deployment-id (-> session-user + (request base-uri + :request-method :post + :body (json/write-str valid-deployment)) + (ltu/body->edn) + (ltu/is-status 201) + (ltu/location)) - deployment-url (str p/service-context deployment-id) - authn-info-jane {:user-id "user/jane" - :active-claim "user/jane" - :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} - event-owners-jane ["group/nuvla-admin" "user/jane"]] + deployment-url (str p/service-context deployment-id) + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + event-owners-jane ["group/nuvla-admin" "user/jane"]] ;; check deployment creation (-> session-user @@ -903,11 +901,11 @@ (ltu/is-status 200))) (ltu/is-last-event deployment-id - {:event-type "deployment.force-delete" - :category "action" - :success true - :authn-info authn-info-jane - :acl {:owners event-owners-jane}}) + {:name "deployment.force-delete" + :category "action" + :success true + :authn-info authn-info-jane + :acl {:owners event-owners-jane}}) (-> session-user (request (str p/service-context module-id) diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj index a979c9d41..c6c00d7f0 100644 --- a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj @@ -154,7 +154,7 @@ abs-uri (str p/service-context uri)] (ltu/is-last-event uri - {:event-type "module.add" + {:name "module.add" :description (str user-name-or-id " added module " uri ".") :category "add" :success true @@ -484,7 +484,7 @@ (doseq [[session event-owners authn-info user-name-or-id] [[session-admin ["group/nuvla-admin"] authn-info-admin admin-group-name] [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane "user/jane"]]] - (let [uri (create-module session valid-entry event-owners authn-info user-name-or-id) + (let [uri (create-module session valid-entry event-owners authn-info user-name-or-id) _module (retrieve-module uri valid-entry valid-content)] (edit-module uri valid-entry event-owners) (publish-unpublish session uri event-owners authn-info user-name-or-id) From 24fc0c3b2875fd129f2c56bc2b5ea975e81e00b6 Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Mon, 28 Aug 2023 16:02:48 +0200 Subject: [PATCH 15/17] basic support for infrastructure services, and comment out legacy events on infrastructure services --- .../server/resources/common/event_config.clj | 8 +- .../resources/common/event_config.clj.orig | 89 -- .../nuvla/server/resources/deployment.clj | 7 +- .../nuvla/server/resources/event/utils.clj | 8 +- .../server/resources/event/utils.clj.orig | 226 ---- .../resources/infrastructure_service.clj | 68 +- .../resources/infrastructure_service_coe.clj | 15 +- .../infrastructure_service_generic.clj | 5 +- .../sixsq/nuvla/server/resources/session.clj | 6 +- .../common/event_config_test.clj.orig | 57 - .../resources/event_utils_test.clj.orig | 98 -- ...rastructure_service_coe_lifecycle_test.clj | 133 ++- ...ructure_service_generic_lifecycle_test.clj | 33 +- ...rastructure_service_vpn_lifecycle_test.clj | 36 +- .../resources/lifecycle_test_utils.clj.orig | 694 ----------- .../resources/module_lifecycle_test.clj.orig | 1054 ----------------- .../session_api_key_lifecycle_test.clj.orig | 263 ---- .../session_password_lifecycle_test.clj.orig | 784 ------------ 18 files changed, 241 insertions(+), 3343 deletions(-) delete mode 100644 code/src/sixsq/nuvla/server/resources/common/event_config.clj.orig delete mode 100644 code/src/sixsq/nuvla/server/resources/event/utils.clj.orig delete mode 100644 code/test/sixsq/nuvla/server/resources/common/event_config_test.clj.orig delete mode 100644 code/test/sixsq/nuvla/server/resources/event_utils_test.clj.orig delete mode 100644 code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig delete mode 100644 code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig delete mode 100644 code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj.orig delete mode 100644 code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj.orig diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config.clj b/code/src/sixsq/nuvla/server/resources/common/event_config.clj index 8d75b6db1..e8fba5470 100644 --- a/code/src/sixsq/nuvla/server/resources/common/event_config.clj +++ b/code/src/sixsq/nuvla/server/resources/common/event_config.clj @@ -53,13 +53,16 @@ (defmethod event-description :default - [{:keys [success authn-info category content] event-name :name :as _event}] + [{:keys [success authn-info category content] event-name :name :as _event} + & [{:keys [resource] :as _context}]] (if success (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) (:user-id authn-info)) resource-id (-> content :resource :href) resource-type (u/id->resource-type resource-id) - resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) + resource (or resource + (crud/retrieve-by-id-as-admin1 resource-id)) + resource-name (:name resource) resource-name-or-id (or resource-name resource-id)] (case category ("add" "edit" "delete" "action") @@ -77,4 +80,3 @@ event-name)) (str event-name " attempt failed."))) - diff --git a/code/src/sixsq/nuvla/server/resources/common/event_config.clj.orig b/code/src/sixsq/nuvla/server/resources/common/event_config.clj.orig deleted file mode 100644 index 84c6846de..000000000 --- a/code/src/sixsq/nuvla/server/resources/common/event_config.clj.orig +++ /dev/null @@ -1,89 +0,0 @@ -(ns sixsq.nuvla.server.resources.common.event-config - (:require [sixsq.nuvla.server.resources.common.crud :as crud] - [sixsq.nuvla.server.resources.common.utils :as u])) - -;; -;; Dispatch functions -;; - -(defn resource-type-dispatch [resource-type] - resource-type) - - -<<<<<<< HEAD -(defn event-name-dispatch [{event-name :name :as _event} _response] - event-name) -======= -(defn event-type-dispatch [{:keys [event-type] :as _event} & _rest] - event-type) ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - - -;; -;; Enabled/disabled events -;; - -(defmulti events-enabled? - "Returns true if events should be logged for the given resource-type, false otherwise." - resource-type-dispatch) - - -(defmethod events-enabled? :default - [_resource-type] - false) - - -;; -;; Whitelist and blacklist event types per resource type -;; - -(defmulti log-event? - "Returns true if the event should be logged, false otherwise." - event-name-dispatch) - - -(defmethod log-event? :default - [{event-name :name :as _event} {:keys [status] :as _response}] - (and (not= 405 status) -<<<<<<< HEAD - (some? event-name))) -======= - (some? event-type))) - - -;; -;; Event human readable description -;; - -(defmulti event-description - "Returns a human-readable description of the event" - event-type-dispatch) - - -(defmethod event-description :default - [{:keys [success event-type authn-info category content] :as _event}] - (if success - (let [user-name-or-id (or (some-> authn-info :user-id crud/retrieve-by-id-as-admin1 :name) - (:user-id authn-info)) - resource-id (-> content :resource :href) - resource-type (u/id->resource-type resource-id) - resource-name (:name (crud/retrieve-by-id-as-admin1 resource-id)) - resource-name-or-id (or resource-name resource-id)] - (case category - ("add" "edit" "delete" "action") - (str (or user-name-or-id "An anonymous user") - (case category - "add" (str " added " resource-type " " resource-name-or-id) - "edit" (str " edited " resource-type " " resource-name-or-id) - "delete" (str " deleted " resource-type " " resource-name-or-id) - "action" (let [action (some->> event-type (re-matches #".*\.(.*)") second)] - (str " executed action " action " on " resource-type " " resource-name-or-id)) - nil) - ".") - ("state" "alarm" "email" "user") - event-type ;; FIXME: improve description in this case - event-type)) - (str event-type " attempt failed."))) - - ->>>>>>> bc60a2ab (use description field to provide human readable label for events) diff --git a/code/src/sixsq/nuvla/server/resources/deployment.clj b/code/src/sixsq/nuvla/server/resources/deployment.clj index d6a5a3000..168b2f044 100644 --- a/code/src/sixsq/nuvla/server/resources/deployment.clj +++ b/code/src/sixsq/nuvla/server/resources/deployment.clj @@ -245,6 +245,7 @@ a container orchestration engine. (a/throw-cannot-delete request) (db/delete request))] (ectx/add-to-context :acl (:acl deployment)) + (ectx/add-to-context :resource deployment) (utils/delete-all-child-resources deployment-id) delete-response) (catch Exception e @@ -565,7 +566,7 @@ a container orchestration engine. (defmethod ec/event-description "deployment.start" - [{:keys [success] {:keys [user-id]} :authn-info :as _event}] + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] (if success (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] (str user-name " started deployment.")) @@ -573,7 +574,7 @@ a container orchestration engine. (defmethod ec/event-description "deployment.stop" - [{:keys [success] {:keys [user-id]} :authn-info :as _event}] + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] (if success (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] (str user-name " stopped deployment.")) @@ -581,7 +582,7 @@ a container orchestration engine. (defmethod ec/event-description "deployment.clone" - [{:keys [success] {:keys [user-id]} :authn-info :as _event}] + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] (if success (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] (str user-name " cloned deployment.")) diff --git a/code/src/sixsq/nuvla/server/resources/event/utils.clj b/code/src/sixsq/nuvla/server/resources/event/utils.clj index 9fc6b3893..24c19c975 100644 --- a/code/src/sixsq/nuvla/server/resources/event/utils.clj +++ b/code/src/sixsq/nuvla/server/resources/event/utils.clj @@ -121,8 +121,8 @@ (defn set-description - [event] - (let [event-description (ec/event-description event)] + [event context] + (let [event-description (ec/event-description event context)] (cond-> event event-description (assoc :description event-description)))) @@ -130,7 +130,7 @@ (defn build-event [context request response] (-> {:resource-type event/resource-type - :name (get-event-name context request) + :name (get-event-name context request) :success (get-success response) :category (get-category context) :timestamp (get-timestamp context) @@ -139,7 +139,7 @@ :severity (get-severity context) :content {:resource (get-resource context response) :linked-identifiers (get-linked-identifiers context)}} - (set-description))) + (set-description context))) (defn add-event diff --git a/code/src/sixsq/nuvla/server/resources/event/utils.clj.orig b/code/src/sixsq/nuvla/server/resources/event/utils.clj.orig deleted file mode 100644 index 65d4e725e..000000000 --- a/code/src/sixsq/nuvla/server/resources/event/utils.clj.orig +++ /dev/null @@ -1,226 +0,0 @@ -(ns sixsq.nuvla.server.resources.event.utils - (:require - [clojure.string :as str] - [sixsq.nuvla.auth.utils :as auth] - [sixsq.nuvla.db.filter.parser :as parser] - [sixsq.nuvla.server.resources.common.crud :as crud] - [sixsq.nuvla.server.resources.common.event-config :as ec] - [sixsq.nuvla.server.resources.common.utils :as u] - [sixsq.nuvla.server.resources.event :as event] - [sixsq.nuvla.server.util.time :as t] - [sixsq.nuvla.server.util.time :as time])) - - -(defn request-event-name - "Returns a string of the form ." - [{{:keys [resource-name uuid action]} :params :as _context} - {:keys [request-method] :as _request}] - (if uuid - (if action - (some->> action (str resource-name ".")) - (case request-method - :put (str resource-name ".edit") - :delete (str resource-name ".delete") - nil)) - (case request-method - :post (str resource-name ".add") - :delete (str resource-name ".bulk.delete") - :patch (some->> action (str resource-name ".bulk.")) - nil))) - - -(defn get-success - [{:keys [status] :as _response}] - (<= 200 status 399)) - - -(defn get-event-name - [{:keys [event-name] :as context} request] - (or event-name - (request-event-name context request))) - - -(defn get-category - [{:keys [category] :as _context}] - (or category "action")) - - -(defn get-timestamp - [{:keys [timestamp] :as _context}] - (or timestamp (t/now-str))) - - -(defn retrieve-by-id - [id] - (try - (:body (crud/retrieve {:params (u/id->request-params id) - :request-method :get - :nuvla/authn auth/internal-identity})) - (catch Exception _ex - nil))) - - -(defn get-resource-href - [{{:keys [resource-name uuid]} :params :as _context} response] - (or (some->> uuid (str resource-name "/")) - (-> response :body :resource-id))) - - -(defn transform-acl - [acl] - (when acl - {:owners (vec (concat (:edit-data acl) (:owners acl)))})) - -(defn derive-acl-from-resource - [context response] - (when-let [acl (some-> (get-resource-href context response) - retrieve-by-id - :acl)] - (transform-acl acl))) - - -(defn get-acl - [{:keys [visible-to acl] :as context} response] - (let [visible-to (remove nil? visible-to)] - (or (when (seq visible-to) - {:owners (-> visible-to (conj "group/nuvla-admin") distinct vec)}) - (transform-acl acl) - (derive-acl-from-resource context response) - {:owners ["group/nuvla-admin"]}))) - - -(defn get-severity - [{:keys [severity] :as _context}] - (or severity "medium")) - - -(defn get-resource - [context response] - {:href (get-resource-href context response)}) - - -(defn get-linked-identifiers - [{:keys [linked-identifiers] :or {linked-identifiers []} :as _context}] - linked-identifiers) - - -(defn get-linked-resource-ids - [{{:keys [linked-identifiers]} :content :as _event} resource-type] - (->> linked-identifiers - (filter (comp #(= resource-type %) u/id->resource-type)))) - - -(defn get-linked-resources - ([{{:keys [linked-identifiers]} :content :as _event}] - (->> linked-identifiers - (keep crud/retrieve-by-id-as-admin1))) - ([{{:keys [linked-identifiers]} :content :as _event} resource-type] - (->> linked-identifiers - (filter (comp #(= resource-type %) u/id->resource-type)) - (keep crud/retrieve-by-id-as-admin1)))) - - -(defn set-description - [event] - (let [event-description (ec/event-description event)] - (cond-> event - event-description (assoc :description event-description)))) - - -(defn build-event - [context request response] -<<<<<<< HEAD - {:resource-type event/resource-type - :name (get-event-name context request) - :success (get-success response) - :category (get-category context) - :timestamp (get-timestamp context) - :authn-info (auth/current-authentication request) - :acl (get-acl context response) - :severity (get-severity context) - :content {:resource (get-resource context response) - :linked-identifiers (get-linked-identifiers context)}}) -======= - (-> {:resource-type event/resource-type - :event-type (get-event-type context request) - :success (get-success response) - :category (get-category context) - :timestamp (get-timestamp context) - :authn-info (auth/current-authentication request) - :acl (get-acl context response) - :severity (get-severity context) - :content {:resource (get-resource context response) - :linked-identifiers (get-linked-identifiers context)}} - (set-description))) ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - - -(defn add-event - [event] - (let [create-request {:params {:resource-name event/resource-type} - :body event - :nuvla/authn auth/internal-identity}] - (crud/add create-request))) - - -(def topic event/resource-type) - - -;; FIXME: duplicated -(defn create-event - [resource-href state acl & {:keys [severity category timestamp] - :or {severity "medium" - category "action"}}] - (let [event-map {:name "legacy" - :success true - :resource-type event/resource-type - :content {:resource {:href resource-href} - :state state} - :severity severity - :category category - :timestamp (or timestamp (time/now-str)) - :acl acl - :authn-info {}} - create-request {:params {:resource-name event/resource-type} - :body event-map - :nuvla/authn auth/internal-identity}] - (crud/add create-request))) - - -(defn query-events - ([resource-href opts] - (query-events (assoc opts :resource-href resource-href))) - ([{:keys [resource-href linked-identifier category state start end orderby last] event-name :name :as opts}] - (some-> event/resource-type - (crud/query-as-admin - {:cimi-params - (cond-> - {:filter (parser/parse-cimi-filter - (str/join " and " - (cond-> [] - resource-href (conj (str "content/resource/href='" resource-href "'")) - (and (contains? opts :resource-href) (nil? resource-href)) (conj (str "content/resource/href=null")) - event-name (conj (str "name='" event-name "'")) - category (conj (str "category='" category "'")) - state (conj (str "content/state='" state "'")) - linked-identifier (conj (str "content/linked-identifiers='" linked-identifier "'")) - start (conj (str "timestamp>='" start "'")) - end (conj (str "timestamp<'" end "'")))))} - orderby (assoc :orderby orderby) - last (assoc :last last))}) - second))) - -;; FIXME: duplicated -(defn search-event - [resource-href {:keys [category state start end]}] - (some-> event/resource-type - (crud/query-as-admin - {:cimi-params - {:filter (parser/parse-cimi-filter - (str/join " and " - (cond-> [(str "content/resource/href='" resource-href "'")] - category (conj (str "category='" category "'")) - state (conj (str "content/state='" state "'")) - start (conj (str "timestamp>='" start "'")) - end (conj (str "timestamp<'" end "'")))))}}) - second)) diff --git a/code/src/sixsq/nuvla/server/resources/infrastructure_service.clj b/code/src/sixsq/nuvla/server/resources/infrastructure_service.clj index 8779eb0e9..6fd96ba91 100644 --- a/code/src/sixsq/nuvla/server/resources/infrastructure_service.clj +++ b/code/src/sixsq/nuvla/server/resources/infrastructure_service.clj @@ -15,9 +15,10 @@ existing `infrastructure-service-template` resource. [sixsq.nuvla.auth.utils :as auth] [sixsq.nuvla.db.impl :as db] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-config :as ec] + [sixsq.nuvla.server.resources.common.event-context :as ectx] [sixsq.nuvla.server.resources.common.std-crud :as std-crud] [sixsq.nuvla.server.resources.common.utils :as u] - [sixsq.nuvla.server.resources.event.utils :as event-utils] [sixsq.nuvla.server.resources.resource-metadata :as md] [sixsq.nuvla.server.resources.spec.infrastructure-service :as infra-service] [sixsq.nuvla.server.resources.spec.infrastructure-service-template-generic :as infra-srvc-gen] @@ -36,6 +37,41 @@ existing `infrastructure-service-template` resource. (def collection-acl {:query ["group/nuvla-user"] :add ["group/nuvla-user"]}) + +;; +;; Events +;; + + +(defmethod ec/events-enabled? resource-type + [_resource-type] + true) + + +(defmethod ec/event-description "infrastructure-service.start" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " started infrastructure service.")) + "Infrastructure service start attempt failed.")) + + +(defmethod ec/event-description "infrastructure-service.stop" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " stopped infrastructure service.")) + "Infrastructure service stop attempt failed.")) + + +(defmethod ec/event-description "infrastructure-service.terminate" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " terminated infrastructure service.")) + "Infrastructure service terminate attempt failed.")) + + ;; ;; initialization ;; @@ -237,12 +273,13 @@ existing `infrastructure-service-template` resource. (defn event-state-change - [{current-state :state id :id} {{new-state :state} :body :as request}] - (when (and new-state (not (= current-state new-state))) - (event-utils/create-event id new-state - (a/default-acl (auth/current-authentication request)) - :severity "low" - :category "state"))) + [{_current-state :state _id :id} {{_new-state :state} :body :as _request}] + ;; legacy events + #_(when (and new-state (not (= current-state new-state))) + (event-utils/create-event id new-state + (a/default-acl (auth/current-authentication request)) + :severity "low" + :category "state"))) (def edit-impl (std-crud/edit-fn resource-type)) @@ -275,18 +312,21 @@ existing `infrastructure-service-template` resource. (defn post-delete-hooks - [{{uuid :uuid} :params :as request} delete-resp] - (let [id (str resource-type "/" uuid)] - (when (= 200 (:status delete-resp)) - (event-utils/create-event id "DELETED" - (a/default-acl (auth/current-authentication request)) - :severity "low" - :category "state")))) + [{{_uuid :uuid} :params :as _request} _delete-resp] + ;; legacy events + #_(let [id (str resource-type "/" uuid)] + (when (= 200 (:status delete-resp)) + (event-utils/create-event id "DELETED" + (a/default-acl (auth/current-authentication request)) + :severity "low" + :category "state")))) (defmethod crud/delete resource-type [{{uuid :uuid} :params :as request}] (let [resource (db/retrieve (str resource-type "/" uuid) request) delete-resp (delete resource request)] + (ectx/add-to-context :resource resource) + (ectx/add-to-context :acl (:acl resource)) (post-delete-hooks request delete-resp) delete-resp)) diff --git a/code/src/sixsq/nuvla/server/resources/infrastructure_service_coe.clj b/code/src/sixsq/nuvla/server/resources/infrastructure_service_coe.clj index f6fcb83ea..f63f95f4a 100644 --- a/code/src/sixsq/nuvla/server/resources/infrastructure_service_coe.clj +++ b/code/src/sixsq/nuvla/server/resources/infrastructure_service_coe.clj @@ -8,8 +8,8 @@ manage it. [sixsq.nuvla.auth.utils :as auth] [sixsq.nuvla.db.impl :as db] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-context :as ec] [sixsq.nuvla.server.resources.common.utils :as u] - [sixsq.nuvla.server.resources.event.utils :as event-utils] [sixsq.nuvla.server.resources.infrastructure-service :as infra-service] [sixsq.nuvla.server.resources.job :as job] [sixsq.nuvla.server.resources.spec.infrastructure-service-coe :as infra-service-coe] @@ -81,8 +81,10 @@ manage it. (if (= 201 status) (let [job-msg (format "created job %s with id %s" job-name job-id)] (edit-infra-service resource request #(assoc % :state new-state)) - (infra-service/event-state-change resource (assoc-in request [:body :state] new-state)) - (event-utils/create-event resource-id job-msg (a/default-acl (auth/current-authentication request))) + (ec/add-linked-identifier job-id) + ;; legacy events + #_(infra-service/event-state-change resource (assoc-in request [:body :state] new-state)) + #_(event-utils/create-event resource-id job-msg (a/default-acl (auth/current-authentication request))) (r/map-response job-msg 202 resource-id job-id)) (throw (r/ex-response (format "unable to create job %s" job-name) 500 resource-id)))) (catch Exception e @@ -180,9 +182,10 @@ manage it. (u/update-timestamps) (u/set-updated-by request) (db/edit request)) - (event-utils/create-event id "STARTING" (a/default-acl (auth/current-authentication request)) - :severity "low" - :category "state") + ;; Legacy events + #_(event-utils/create-event id "STARTING" (a/default-acl (auth/current-authentication request)) + :severity "low" + :category "state") (r/map-response job-msg 202 id job-id)) (catch Exception e (or (ex-data e) (throw e))))) diff --git a/code/src/sixsq/nuvla/server/resources/infrastructure_service_generic.clj b/code/src/sixsq/nuvla/server/resources/infrastructure_service_generic.clj index d22b2574b..929f9abc5 100644 --- a/code/src/sixsq/nuvla/server/resources/infrastructure_service_generic.clj +++ b/code/src/sixsq/nuvla/server/resources/infrastructure_service_generic.clj @@ -38,8 +38,9 @@ an endpoint. (defmethod infra-service/post-add-hook method - [service request] - (try + [_service _request] + ;; legacy events + #_(try (let [id (:id service) category "state"] (event-utils/create-event id diff --git a/code/src/sixsq/nuvla/server/resources/session.clj b/code/src/sixsq/nuvla/server/resources/session.clj index 5b17e3173..f2c5c3cc4 100644 --- a/code/src/sixsq/nuvla/server/resources/session.clj +++ b/code/src/sixsq/nuvla/server/resources/session.clj @@ -537,7 +537,7 @@ status, a 'set-cookie' header, and a 'location' header with the created (defmethod ec/event-description "session.add" - [{:keys [success] :as event}] + [{:keys [success] :as event} & _] (if success (when-let [user-name-or-credential (or (some-> (eu/get-linked-resources event "user") first :name) (some-> (eu/get-linked-resource-ids event "user") first) @@ -548,7 +548,7 @@ status, a 'set-cookie' header, and a 'location' header with the created (defmethod ec/event-description "session.delete" - [{:keys [success] {:keys [user-id]} :authn-info :as _event}] + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] (if success (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] (str user-name " logged out.")) @@ -556,7 +556,7 @@ status, a 'set-cookie' header, and a 'location' header with the created (defmethod ec/event-description "session.switch-group" - [{:keys [success] {:keys [user-id]} :authn-info {:keys [linked-identifiers]} :content :as event}] + [{:keys [success] {:keys [user-id]} :authn-info {:keys [linked-identifiers]} :content :as event} & _] (if success (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] (str user-name " switched to group " diff --git a/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj.orig b/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj.orig deleted file mode 100644 index 27dcdbf17..000000000 --- a/code/test/sixsq/nuvla/server/resources/common/event_config_test.clj.orig +++ /dev/null @@ -1,57 +0,0 @@ -(ns sixsq.nuvla.server.resources.common.event-config-test - (:require [clojure.test :refer [deftest is]] - [sixsq.nuvla.server.resources.common.crud :as crud] - [sixsq.nuvla.server.resources.common.event-config :as t])) - - -<<<<<<< HEAD -(def logged-event {:name "resource.add"}) -======= -(def logged-event {:event-type "resource.add" - :category "add" - :success true - :authn-info {:user-id "user/12345"} - :content {:resource {:href "resource/12345"}}}) ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - - -(def not-logged-event {}) - - -(def disabled-event {:name "resource.validate"}) - - -(def anon-event {:event-type "resource.add" - :category "add" - :success true - :authn-info {:claims ["group/nuvla-anon"]} - :content {:resource {:href "resource/12345"}}}) - - -(def failure-event {:event-type "resource.add" - :success false}) - - - -(defmethod t/log-event? - "resource.validate" - [_event _response] - false) - - -(deftest log-event - (is (true? (t/log-event? logged-event {}))) - (is (false? (t/log-event? not-logged-event {}))) - (is (false? (t/log-event? disabled-event {}))) - (is (false? (t/log-event? logged-event {:status 405})))) - - -(deftest event-description - (with-redefs [crud/retrieve-by-id-as-admin - #(case % - "user/12345" {:name "TestUser"} - "resource/12345" {:resource-type "resource" - :name "TestResource"})] - (is (= "TestUser added resource TestResource." (t/event-description logged-event))) - (is (= "An anonymous user added resource TestResource." (t/event-description anon-event))) - (is (= "resource.add attempt failed." (t/event-description failure-event))))) diff --git a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj.orig b/code/test/sixsq/nuvla/server/resources/event_utils_test.clj.orig deleted file mode 100644 index 510562023..000000000 --- a/code/test/sixsq/nuvla/server/resources/event_utils_test.clj.orig +++ /dev/null @@ -1,98 +0,0 @@ -(ns sixsq.nuvla.server.resources.event-utils-test - (:require - [clojure.test :refer [deftest is testing use-fixtures]] - [sixsq.nuvla.server.resources.common.utils :as u] - [sixsq.nuvla.server.resources.event.utils :as t] - [sixsq.nuvla.server.resources.lifecycle-test-utils :as ltu] - [sixsq.nuvla.server.util.time :as time])) - - -(use-fixtures :each ltu/with-test-server-fixture) - - -(defn req - [{:keys [nuvla-authn-info method body]}] - {:request-method method - :params {:resource-name "resource" - :uuid "12345"} - :headers {"nuvla-authn-info" nuvla-authn-info} - :body body}) - - -;; TODO: test getters in sixsq.nuvla.server.resources.event.utils - - -(deftest build-event - (with-redefs [time/now-str (constantly "2023-08-17T07:25:57.259Z")] - (let [context {:category "add" - :params {:resource-name "resource"}} - request (req {:nuvla-authn-info "super super group/nuvla-admin" - :method :post - :body {:k "v"}})] - (testing "success" - (let [uuid (u/random-uuid) - id (str "resource/" uuid) - event (t/build-event context request {:status 201 :body {:resource-id id}})] -<<<<<<< HEAD - (is (= {:name "resource.add" - :category "action" -======= - (is (= {:event-type "resource.add" - :category "add" - :description (str "An anonymous user added resource " id ".") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - :content {:resource {:href id} - :linked-identifiers []} - :authn-info {} - :success true - :severity "medium" - :resource-type "event" - :acl {:owners ["group/nuvla-admin"]} - :timestamp "2023-08-17T07:25:57.259Z"} - event)))) - (testing "failure" - (let [event (t/build-event context request {:status 400})] -<<<<<<< HEAD - (is (= {:name "resource.add" - :category "action" -======= - (is (= {:event-type "resource.add" - :category "add" - :description "resource.add attempt failed." ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - :content {:resource {:href nil} - :linked-identifiers []} - :authn-info {} - :success false - :severity "medium" - :resource-type "event" - :acl {:owners ["group/nuvla-admin"]} - :timestamp "2023-08-17T07:25:57.259Z"} - event))))))) - - -(deftest add-event - (let [context {:category "action" - :params {:resource-name "resource"}} - request (req {:nuvla-authn-info "super super group/nuvla-admin" - :method :post - :body {:k "v"}}) - event (t/build-event context request {:status 200})] - (let [{:keys [status]} (t/add-event event)] - (is (= 201 status))))) - - -(deftest search-event - (doseq [category ["action" "add"] - timestamp ["2015-01-16T08:05:00.000Z" "2015-01-17T08:05:00.000Z" (time/now-str)]] - (t/create-event "user/1" "hello" {:owners ["group/nuvla-admin"]} - :category category - :timestamp timestamp)) - (is (= 6 (count (t/search-event "user/1" {})))) - (is (= 0 (count (t/search-event "user/2" {})))) - (is (= 3 (count (t/search-event "user/1" {:category "action"})))) - (is (= 6 (count (t/search-event "user/1" {:start "2015-01-16T08:05:00.000Z"})))) - (is (= 2 (count (t/search-event "user/1" {:end "2015-01-16T08:06:00.000Z"})))) - (is (= 1 (count (t/search-event "user/1" {:category "action" - :start "now/d" :end "now+1d/d"}))))) - diff --git a/code/test/sixsq/nuvla/server/resources/infrastructure_service_coe_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/infrastructure_service_coe_lifecycle_test.clj index 69ede182d..89e2b0c49 100644 --- a/code/test/sixsq/nuvla/server/resources/infrastructure_service_coe_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/infrastructure_service_coe_lifecycle_test.clj @@ -56,7 +56,7 @@ session (content-type "application/json")) session-admin (header session-anon authn-info-header - "group/nuvla-admin group/nuvla-user group/nuvla-anon") + "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") session-user (header session-anon authn-info-header "user/jane user/jane group/nuvla-user group/nuvla-anon") ;; setup a service-group to act as parent for service @@ -101,7 +101,14 @@ :method infra-service-tpl-coe/method :parent service-group-id :subtype subtype - :management-credential credential-id}}] + :management-credential credential-id}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + admin-group-name "Nuvla Administrator Group"] ;; anon create must fail (-> session-anon @@ -112,7 +119,9 @@ (ltu/is-status 400)) ;; check creation - (doseq [session [session-admin session-user]] + (doseq [[session event-owners authn-info user-name-or-id] + [[session-admin ["group/nuvla-admin"] authn-info-admin admin-group-name] + [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane "user/jane"]]] (let [uri (-> session (request base-uri :request-method :post @@ -141,12 +150,30 @@ (is (= "STARTING" (:state service))) (is (= credential-id (:management-credential service)))) + (ltu/is-last-event uri + {:name "infrastructure-service.add" + :description (str user-name-or-id " added infrastructure-service " service-name ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + ;; can NOT delete resource in STARTING state (-> session (request abs-uri :request-method :delete) (ltu/body->edn) (ltu/is-status 409)) + (ltu/is-last-event uri + {:name "infrastructure-service.delete" + :description "infrastructure-service.delete attempt failed." + :category "delete" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + ;; set TERMINATED state (set-state-on-is abs-uri session "TERMINATED") @@ -154,7 +181,16 @@ (-> session (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "infrastructure-service.delete" + :description (str user-name-or-id " deleted infrastructure-service " service-name ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})))))) ;; Validate right CRUD operations and actions are available on resource in @@ -218,22 +254,27 @@ (ltu/is-status 201) (ltu/location)) abs-uri (str p/service-context uri) + event-owners ["group/nuvla-admin" "user/jane"] + authn-info {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} check-event (fn [exp-state] - (let [filter (format "category='state' and content/resource/href='%s' and content/state='%s'" uri exp-state) - state (-> session-user - (content-type "application/x-www-form-urlencoded") - (request "/api/event" - :request-method :put - :body (rc/form-encode {:filter filter})) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count 1) - (ltu/body) - :resources - first - :content - :state)] - (is (= state exp-state))))] + ;; legacy events + #_(let [filter (format "category='state' and content/resource/href='%s' and content/state='%s'" uri exp-state) + state (-> session-user + (content-type "application/x-www-form-urlencoded") + (request "/api/event" + :request-method :put + :body (rc/form-encode {:filter filter})) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/is-count 1) + (ltu/body) + :resources + first + :content + :state)] + (is (= state exp-state))))] ;; STARTING: edit (let [response (-> session-user @@ -271,15 +312,25 @@ (request abs-uri) (ltu/body->edn) (ltu/is-status 200) - (ltu/get-op-url "stop"))] - (-> session-user - (request op-uri - :request-method :post) - (ltu/is-status 202) - (ltu/body->edn))) + (ltu/get-op-url "stop")) + job-id (-> session-user + (request op-uri + :request-method :post) + (ltu/is-status 202) + (ltu/body->edn) + (ltu/location))] + + ;; check event for STOPPING was created + (check-event "STOPPING") - ;; check event for STOPPING was created - (check-event "STOPPING") + (ltu/is-last-event uri + {:name "infrastructure-service.stop" + :description (str "user/jane stopped infrastructure service.") + :category "action" + :success true + :linked-identifiers [job-id] + :authn-info authn-info + :acl {:owners event-owners}})) ;; STOPPING: edit (let [response (-> session-user @@ -314,15 +365,25 @@ (request abs-uri) (ltu/body->edn) (ltu/is-status 200) - (ltu/get-op-url "terminate"))] - (-> session-user - (request op-uri - :request-method :post) - (ltu/is-status 202) - (ltu/body->edn))) - - ;; check event for TERMINATING was created - (check-event "TERMINATING") + (ltu/get-op-url "terminate")) + job-id (-> session-user + (request op-uri + :request-method :post) + (ltu/is-status 202) + (ltu/body->edn) + (ltu/location))] + + ;; check event for TERMINATING was created + (check-event "TERMINATING") + + (ltu/is-last-event uri + {:name "infrastructure-service.terminate" + :description (str "user/jane terminated infrastructure service.") + :category "action" + :success true + :linked-identifiers [job-id] + :authn-info authn-info + :acl {:owners event-owners}})) ;; TERMINATING: edit (let [response (-> session-user diff --git a/code/test/sixsq/nuvla/server/resources/infrastructure_service_generic_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/infrastructure_service_generic_lifecycle_test.clj index d0f939ae3..a71f24039 100644 --- a/code/test/sixsq/nuvla/server/resources/infrastructure_service_generic_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/infrastructure_service_generic_lifecycle_test.clj @@ -70,7 +70,14 @@ :acl valid-acl :template (merge {:href (str infra-service-tpl/resource-type "/" infra-service-tpl-generic/method)} - valid-service)}] + valid-service)} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + admin-group-name "Nuvla Administrator Group"] ;; admin query succeeds but is empty (-> session-admin @@ -107,7 +114,9 @@ (ltu/is-status 400)) ;; check creation - (doseq [session [session-admin session-user]] + (doseq [[session event-owners authn-info user-name-or-id] + [[session-admin ["group/nuvla-admin" "user/jane"] authn-info-admin admin-group-name] + [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane "user/jane"]]] (let [uri (-> session (request base-uri :request-method :post @@ -136,8 +145,26 @@ (is (:endpoint service)) (is (= "STARTED" (:state service)))) + (ltu/is-last-event uri + {:name "infrastructure-service.add" + :description (str user-name-or-id " added infrastructure-service " service-name ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + ;; can delete resource (-> session (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200)))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "infrastructure-service.delete" + :description (str user-name-or-id " deleted infrastructure-service " service-name ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}))))) diff --git a/code/test/sixsq/nuvla/server/resources/infrastructure_service_vpn_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/infrastructure_service_vpn_lifecycle_test.clj index e5e074e9c..49e4a6df7 100644 --- a/code/test/sixsq/nuvla/server/resources/infrastructure_service_vpn_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/infrastructure_service_vpn_lifecycle_test.clj @@ -40,7 +40,7 @@ session (content-type "application/json")) session-admin (header session-anon authn-info-header - "group/nuvla-admin group/nuvla-user group/nuvla-anon") + "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") session-user (header session-anon authn-info-header "user/jane user/jane group/nuvla-user group/nuvla-anon") ;; setup a service-group to act as parent for service @@ -65,7 +65,14 @@ :tags service-tags :template {:href (str infra-service-tpl/resource-type "/" infra-service-tpl-vpn/method) - :parent service-group-id}}] + :parent service-group-id}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + admin-group-name "Nuvla Administrator Group"] ;; anon create must fail (-> session-anon @@ -76,7 +83,9 @@ (ltu/is-status 400)) ;; check creation - (doseq [session [session-admin session-user]] + (doseq [[session event-owners authn-info user-name-or-id] + [[session-admin ["group/nuvla-admin"] authn-info-admin admin-group-name] + [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane "user/jane"]]] (let [uri (-> session (request base-uri :request-method :post @@ -101,8 +110,27 @@ (is (:subtype service)) (is (nil? (:endpoint service)))) + (ltu/is-last-event uri + {:name "infrastructure-service.add" + :description (str user-name-or-id " added infrastructure-service " service-name ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}) + ;; can delete resource (-> session (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200)))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "infrastructure-service.delete" + :description (str user-name-or-id " deleted infrastructure-service " service-name ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}))))) + diff --git a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig b/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig deleted file mode 100644 index 13e0829b6..000000000 --- a/code/test/sixsq/nuvla/server/resources/lifecycle_test_utils.clj.orig +++ /dev/null @@ -1,694 +0,0 @@ -(ns sixsq.nuvla.server.resources.lifecycle-test-utils - (:require - [clojure.data.json :as json] - [clojure.java.io :as io] - [clojure.pprint :refer [pprint]] - [clojure.string :as str] - [clojure.test :refer [is join-fixtures]] - [clojure.tools.logging :as log] - [compojure.core :as cc] - [kinsky.embedded-kraft :as ke] - [me.raynes.fs :as fs] - [peridot.core :refer [request session]] - [qbits.spandex :as spandex] - [ring.middleware.json :refer [wrap-json-body wrap-json-response]] - [ring.middleware.keyword-params :refer [wrap-keyword-params]] - [ring.middleware.nested-params :refer [wrap-nested-params]] - [ring.middleware.params :refer [wrap-params]] - [ring.util.codec :as codec] - [sixsq.nuvla.db.es.binding :as esb] - [sixsq.nuvla.db.es.utils :as esu] - [sixsq.nuvla.db.impl :as db] - [sixsq.nuvla.server.app.params :as p] - [sixsq.nuvla.server.app.routes :as routes] - [sixsq.nuvla.server.middleware.authn-info :refer [wrap-authn-info]] - [sixsq.nuvla.server.middleware.base-uri :refer [wrap-base-uri]] - [sixsq.nuvla.server.middleware.cimi-params :refer [wrap-cimi-params]] - [sixsq.nuvla.server.middleware.exception-handler :refer [wrap-exceptions]] - [sixsq.nuvla.server.middleware.eventer :refer [wrap-eventer]] - [sixsq.nuvla.server.middleware.logger :refer [wrap-logger]] - [sixsq.nuvla.server.resources.common.dynamic-load :as dyn] - [sixsq.nuvla.server.resources.event.utils :as event-utils] - [sixsq.nuvla.server.util.kafka :as ka] - [sixsq.nuvla.server.util.zookeeper :as uzk] - [zookeeper :as zk]) - (:import - (java.util UUID) - (org.apache.curator.test TestingServer) - (org.elasticsearch.common.logging LogConfigurator) - (org.elasticsearch.common.settings Settings) - (org.elasticsearch.index.reindex ReindexPlugin) - (org.elasticsearch.node MockNode) - (org.elasticsearch.painless PainlessPlugin) - (org.elasticsearch.transport Netty4Plugin))) - - -(defn random-string - "provides a random string with optional prefix" - [& [prefix]] - (apply str prefix (repeatedly 15 #(rand-nth "abcdefghijklmnopqrstuvwxyz")))) - - -(defn serialize-cookie-value - "replaces the map cookie value with a serialized string" - [{:keys [value] :as cookie}] - (if value - (assoc cookie :value (codec/form-encode value)) - cookie)) - - -(defmacro message-matches - [m re] - `((fn [m# re#] - (let [message# (get-in m# [:response :body :message])] - (if (string? re#) - (do - (is (.startsWith (or message# "") re#) (str "Message does not start with string. " (or message# "nil") " " re#)) - m#) - (do - (is (re-matches re# message#) (str "Message does not match pattern. " " " re#)) - m#)))) ~m ~re)) - - -(defmacro is-status - [m status] - `((fn [m# status#] - (let [actual# (get-in m# [:response :status])] - (is (= status# actual#) (str "Expecting status " status# " got " (or actual# "nil") ". Message: " - (get-in m# [:response :body :message]))) - m#)) ~m ~status)) - - -(defmacro is-key-value - ([m f k v] - `((fn [m# f# k# v#] - (let [actual# (-> m# :response :body k# f#)] - (is (= v# actual#) (str "Expecting " v# " got " (or actual# "nil") " for " k#)) - m#)) ~m ~f ~k ~v)) - ([m k v] - `(is-key-value ~m identity ~k ~v))) - - -(defmacro has-key - [m k] - `((fn [m# k#] - (is (get-in m# [:response :body k#]) (str "Map did not contain key " k#)) m#) - ~m ~k)) - - -(defmacro is-resource-uri - [m type-uri] - `(is-key-value ~m :resource-type ~type-uri)) - - -(defn href->url - [href] - (when href - (str p/service-context href))) - - -(defn get-op - [m op] - (->> (get-in m [:response :body :operations]) - (map (juxt :rel :href)) - (filter (fn [[rel _]] (= rel (name op)))) - first - second)) - - -(defn get-op-url - [m op] - (href->url (get-op m op))) - - -(defn select-op - [m op] - (let [op-list (get-in m [:response :body :operations]) - defined-ops (map :rel op-list)] - [(some #(= % (name op)) defined-ops) defined-ops])) - - -(defmacro is-operation-present - [m expected-op] - `((fn [m# expected-op#] - (let [[op# defined-ops#] (select-op m# expected-op#)] - (is op# (str "Missing " (name expected-op#) " in " defined-ops#)) - m#)) - ~m ~expected-op)) - - -(defmacro is-operation-absent [m absent-op] - `((fn [m# absent-op#] - (let [[op# defined-ops#] (select-op m# absent-op#)] - (is (nil? op#) (str "Unexpected op " absent-op# " in " defined-ops#))) - m#) - ~m ~absent-op)) - - -(defmacro is-id - [m id] - `(is-key-value ~m :id ~id)) - - -(defmacro is-count - [m f] - `((fn [m# f#] - (let [count# (get-in m# [:response :body :count])] - (is (number? count#) (str "Count is not a number: " count#)) - (when (number? count#) - (if (fn? f#) - (is (f# count#) "Function of count did not return truthy value") - (is (= f# count#) (str "Count wrong, expecting " f# ", got " (or count# "nil"))))) - m#)) ~m ~f)) - - -(defn does-body-contain - [m v] - `((fn [m# v#] - (let [body# (get-in m# [:response :body])] - (is (= (merge body# v#) body#)))) - ~m ~v)) - - -(defmacro is-set-cookie - [m] - `((fn [m#] - (let [cookies# (get-in m# [:response :cookies]) - n# (count cookies#) - token# (-> (vals cookies#) - first - serialize-cookie-value - :value)] - (is (= 1 n#) "incorrect number of cookies") - (is (not= "INVALID" token#) "expecting valid token but got INVALID") - (is (not (str/blank? token#)) "got blank token") - m#)) ~m)) - - -(defmacro is-unset-cookie - [m] - `((fn [m#] - (let [cookies# (get-in m# [:response :cookies]) - n# (count cookies#) - token# (-> (vals cookies#) - first - serialize-cookie-value - :value)] - (is (= 1 n#) "incorrect number of cookies") - (is (= "INVALID" token#) "expecting INVALID but got different value") - (is (not (str/blank? token#)) "got blank token") - m#)) ~m)) - - -(defmacro is-location - [m] - `((fn [m#] - (let [uri-header# (get-in m# [:response :headers "Location"]) - uri-body# (get-in m# [:response :body :resource-id])] - (is uri-header# "Location header was not set") - (is uri-body# "Location (resource-id) in body was not set") - (is (= uri-header# uri-body#) (str "!!!! Mismatch in locations, header=" uri-header# ", body=" uri-body#)) - m#)) ~m)) - - -(defn location - [m] - (let [uri (get-in m [:response :headers "Location"])] - (is uri "Location header missing from response") - uri)) - - -(defn location-url - [m] - (href->url (location m))) - - -(defmacro is-location-value - [m v] - `((fn [m# v#] - (let [location# (location m#)] - (is (= location# v#)))) - ~m ~v)) - - -(defn operations->map - [m] - (into {} (map (juxt :rel :href) (:operations m)))) - - -(defn body - [m] - (get-in m [:response :body])) - - -(defn body-resource-id - [m] - (get-in m [:response :body :resource-id])) - - -(defn body->edn - [m] - (if-let [body-content (body m)] - (let [updated-body (if (string? body-content) - (json/read-str body-content :key-fn keyword :eof-error? false :eof-value {}) - (json/read (io/reader body-content) :key-fn keyword :eof-error? false :eof-value {}))] - (update-in m [:response :body] (constantly updated-body))) - m)) - - -(defn entries - [m] - (some-> m :response :body :resources)) - - -(defn concat-routes - [rs] - (apply cc/routes rs)) - - -(defn dump - [response] - (pprint response) - response) - - -(defn dump-m - [response message] - (println "-->>" message) - (pprint response) - (println message "<<--") - response) - - -(defn refresh-es-indices - [] - (let [client (spandex/client {:hosts ["localhost:9200"]})] - (spandex/request client {:url [:_refresh], :method :post}) - (spandex/close! client))) - - -(defn strip-unwanted-attrs - "Strips common attributes that are not interesting when comparing - versions of a resource." - [m] - (let [unwanted #{:id :resource-type :acl :operations - :created :updated :name :description :tags}] - (into {} (remove #(unwanted (first %)) m)))) - - -;; -;; Handling of Zookeeper server and client -;; - -(defn create-zk-client-server - [] - (let [port 21810] - (log/info "creating zookeeper server on port" port) - (let [server (TestingServer. port) - client (zk/connect (str "127.0.0.1:" port))] - (uzk/set-client! client) - [client server]))) - - -(defonce ^:private zk-client-server-cache (atom nil)) - - -(defn set-zk-client-server-cache - "Sets the value of the cached Elasticsearch node and client. If the current - value is nil, then a new node and a new client are created and cached. If - the value is not nil, then the cache is set to the same value. This returns - the tuple with the node and client, which should never be nil." - [] - ;; Implementation note: It is unfortunate that the atom will constantly be - ;; reset to the current value because swap! is used. Unfortunately, - ;; compare-and-set! can't be used because we want to avoid unnecessary - ;; creation of ring application instances. - (swap! zk-client-server-cache (fn [current] (or current (create-zk-client-server))))) - - -;(defn clear-zk-client-server-cache -; "Unconditionally clears the cached Elasticsearch node and client. Can be -; used to force the re-initialization of the node and client. If the current -; values are not nil, then the node and client will be closed, with errors -; silently ignored." -; [] -; (let [[[client server] _] (swap-vals! zk-client-server-cache (constantly nil))] -; (when client -; (try -; (.close client) -; (catch Exception _))) -; (when server -; (try -; (.close server) -; (catch Exception _))))) - - -;; -;; Handling of Elasticsearch node and client for tests -;; - - -(defn create-test-node - "Creates a local elasticsearch node that holds data that can be access - through the native or HTTP protocols." - ([] - (create-test-node (str (UUID/randomUUID)))) - ([^String cluster-name] - (let [tempDir (str (fs/temp-dir "es-data-")) - settings (.. (Settings/builder) - (put "cluster.name" cluster-name) - (put "action.auto_create_index" true) - (put "path.home" tempDir) - (put "transport.netty.worker_count" 3) - (put "node.data" true) - (put "logger.level" "ERROR") - (put "cluster.routing.allocation.disk.watermark.low" "1gb") - (put "cluster.routing.allocation.disk.watermark.high" "500mb") - (put "cluster.routing.allocation.disk.watermark.flood_stage" "100mb") - (put "http.type" "netty4") - (put "http.port" "9200") - (put "transport.type" "netty4") - (put "network.host" "127.0.0.1") - (build)) - plugins [Netty4Plugin - ReindexPlugin - PainlessPlugin]] - - (LogConfigurator/configureWithoutConfig settings) - (.. (MockNode. ^Settings settings plugins) - (start))))) - - -(defn create-es-node-client - [] - (log/info "creating elasticsearch node and client") - (let [node (create-test-node) - client (-> (esu/create-es-client) - esu/wait-for-cluster) - sniffer (esu/create-es-sniffer client)] - [node client sniffer])) - - -(defonce ^:private es-node-client-cache (atom nil)) - -(defn es-node - [] - (first @es-node-client-cache)) - -(defn es-client - [] - (second @es-node-client-cache)) - -(defn es-sniffer - [] - (nth @es-node-client-cache 2)) - - -(defn set-es-node-client-cache - "Sets the value of the cached Elasticsearch node and client. If the current - value is nil, then a new node and a new client are created and cached. If - the value is not nil, then the cache is set to the same value. This returns - the tuple with the node and client, which should never be nil." - [] - ;; Implementation note: It is unfortunate that the atom will constantly be - ;; reset to the current value because swap! is used. Unfortunately, - ;; compare-and-set! can't be used because we want to avoid unnecessary - ;; creation of ring application instances. - (swap! es-node-client-cache (fn [current] (or current (create-es-node-client))))) - - -(defn clear-es-node-client-cache - "Unconditionally clears the cached Elasticsearch node and client. Can be - used to force the re-initialization of the node and client. If the current - values are not nil, then the node and client will be closed, with errors - silently ignored." - [] - (let [[[node client sniffer] _] (swap-vals! es-node-client-cache (constantly nil))] - (when client - (try - (.close client) - (catch Exception _))) - (when sniffer - (try - (.close sniffer) - (catch Exception _))) - (when node - (try - (.close node) - (catch Exception _))))) - - -(defn profile - [msg f & rest] - (let [ts (System/currentTimeMillis)] - (log/debug (str "--->: " msg)) - (let [res (if rest - (apply f rest) - (f))] - (log/debug (str "--->: " msg " done in: " (- (System/currentTimeMillis) ts))) - res))) - - -(defmacro with-test-es-client - "Creates an Elasticsearch test client, executes the body with the created - client bound to the Elasticsearch client binding, and then clean up the - allocated resources by closing both the client and the node." - [& body] - `(let [[_# client# sniffer#] - (profile "setting es node client cache" set-es-node-client-cache)] - (db/set-impl! (esb/->ElasticsearchRestBinding client# sniffer#)) - ~@body)) - -;; -;; Ring Application Management -;; - -(defn make-ring-app [resource-routes] - (log/info "creating ring application") - (-> resource-routes - wrap-cimi-params - wrap-keyword-params - wrap-nested-params - wrap-params - wrap-base-uri - wrap-exceptions - (wrap-json-body {:keywords? true}) - wrap-eventer - wrap-authn-info - (wrap-json-response {:pretty true :escape-non-ascii true}) - wrap-logger)) - - -(defonce ^:private ring-app-cache (atom nil)) - -(defn set-ring-app-cache - "Sets the value of the cached ring application. If the current value is nil, - then a new ring application is created and cached. If the value is not nil, - then the cache is set to the same value. This returns the ring application - value, which should never be nil." - [] - ;; Implementation note: It is unfortunate that the atom will constantly be - ;; reset to the current value because swap! is used. Unfortunately, - ;; compare-and-set! can't be used because we want to avoid unnecessary - ;; creation of ring application instances. - (swap! ring-app-cache (fn [current] (or current - (make-ring-app (concat-routes [(routes/get-main-routes)])))))) - - -(defn clear-ring-app-cache - "Unconditionally clears the cached ring application instance. Can be used - to force the re-initialization of the ring application." - [] - (reset! ring-app-cache (constantly nil))) - - -(defn ring-app - "Returns a standard ring application with the CIMI server routes. By - default, only a single instance will be created and cached. The cache can be - cleared with the `clean-ring-app-cache` function." - [] - (set-ring-app-cache)) - - -(def kafka-host "127.0.0.1") -(def kafka-port 9093) - - -(defn with-test-kafka-fixture - [f] - (log/debug "executing with-test-kafka-fixture") - (let [log-dir (ke/create-tmp-dir "kraft-combined-logs")] - (let [kafka (profile "start kafka" - ke/start-embedded-kafka - {::ke/host kafka-host - ::ke/port kafka-port - ::ke/log-dirs (str log-dir) - ::ke/server-config {"auto.create.topics.enable" "true" - "transaction.timeout.ms" "5000"}})] - (try - (when (= 0 (count @ka/producers!)) - (profile "create kafka producers" - ka/create-producers! (format "%s:%s" kafka-host kafka-port))) - (profile "run supplied function" f) - (catch Throwable t - (throw t)) - (finally - (ka/close-producers!) - (log/debug "finalising with-test-kafka-fixture") - ;; FIXME: Closing Kafka server takes ~6 sec. Instead of closing Kafka - ;; server, delete all the topics. In case of the last test, the server - ;; will just go down with the JVM. - (let [ts (System/currentTimeMillis)] - (.close kafka) - (log/debug (str "--->: close kafka done in: " - (- (System/currentTimeMillis) ts)))) - (ke/delete-dir log-dir)))))) - - -(def ^:private resources-initialised (atom false)) - - -(defn initialize-indices - [] - (if @resources-initialised - (dyn/initialize-data) - (do - (dyn/initialize) - (reset! resources-initialised true)))) - - -;; -;; test fixture that starts the following parts of the test server: -;; elasticsearch, zookeeper, ring application -;; - -(defn with-test-server-fixture - "This fixture will ensure that Elasticsearch and zookeeper instances are - running. It will also create a ring application and initialize it. The - servers and application are cached to eliminate unnecessary instance - creation for the subsequent tests." - [f] - (log/debug "executing with-test-server-fixture") - (profile "start zookeeper" set-zk-client-server-cache) - (with-test-es-client - (profile "start ring app" ring-app) - (profile "cleanup indices" esu/cleanup-index (es-client) "nuvla-*") - (profile "initialize indices" initialize-indices) - (profile "run supplied function" f))) - - -;; -;; test fixture that starts all parts of the test server including kafka -;; - -(def with-test-server-kafka-fixture (join-fixtures [with-test-server-fixture - with-test-kafka-fixture])) - -;; -;; miscellaneous utilities -;; - -(defn verify-405-status - "The url-methods parameter must be a list of URL/method tuples. It is - expected that any request with the method to the URL will return a 405 - status." - [url-methods] - (doall - (for [[uri method] url-methods] - (-> (ring-app) - session - (request uri - :request-method method - :body (json/write-str {:dummy "value"})) - (is-status 405))))) - -;; -;; ACL -;; - -(defmacro is-acl - [expected-acl actual-acl] - `(do - (when (:owners ~expected-acl) - (is (= (set (:owners ~expected-acl)) (set (:owners ~actual-acl))))) - (when (:edit-acl ~expected-acl) - (is (= (set (:edit-acl ~expected-acl)) (set (:edit-acl ~actual-acl))))) - (when (:edit-data ~expected-acl) - (is (= (set (:edit-data ~expected-acl)) (set (:edit-data ~actual-acl))))) - (when (:edit-meta ~expected-acl) - (is (= (set (:edit-meta ~expected-acl)) (set (:edit-meta ~actual-acl))))) - (when (:view-acl ~expected-acl) - (is (= (set (:view-acl ~expected-acl)) (set (:view-acl ~actual-acl))))) - (when (:view-data ~expected-acl) - (is (= (set (:view-data ~expected-acl)) (set (:view-data ~actual-acl))))) - (when (:view-meta ~expected-acl) - (is (= (set (:view-meta ~expected-acl)) (set (:view-meta ~actual-acl))))) - (when (:manage ~expected-acl) - (is (= (set (:manage ~expected-acl)) (set (:manage ~actual-acl))))) - (when (:delete ~expected-acl) - (is (= (set (:delete ~expected-acl)) (set (:delete ~actual-acl))))))) - - -;; -;; events -;; - -<<<<<<< HEAD -(defmacro is-last-event - [resource-id {:keys [description category authn-info linked-identifiers success acl] event-name :name}] - `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1})) - authn-info# (:authn-info event#)] - (is (some? event#)) - (when ~event-name - (is (= ~event-name (:name event#)))) - (when ~description - (is (= ~description (:description event#)))) - (when ~category - (is (= ~category (:category event#)))) - (when ~authn-info - (is (= (:user-id ~authn-info) (:user-id authn-info#))) - (is (= (:active-claim ~authn-info) (:active-claim authn-info#))) - (is (= (set (:claims ~authn-info)) (set (:claims authn-info#))))) - (when ~linked-identifiers - (is (= (set ~linked-identifiers) (set (get-in event# [:content :linked-identifiers]))))) - (when (some? ~success) - (is (= ~success (:success event#)))) - (when (some? ~acl) - (is (= ~acl (:acl event#)))))) -======= -(defmacro is-event - [expected-event actual-event] - `(let [expected-authn-info# (:authn-info ~expected-event) - authn-info# (:authn-info ~actual-event)] - (is (some? ~actual-event)) - (when (:event-type ~expected-event) - (is (= (:event-type ~expected-event) (:event-type ~actual-event)))) - (when (:category ~expected-event) - (is (= (:category ~expected-event) (:category ~actual-event)))) - (when expected-authn-info# - (is (= (:user-id expected-authn-info#) (:user-id authn-info#))) - (is (= (:active-claim expected-authn-info#) (:active-claim authn-info#))) - (is (= (set (:claims expected-authn-info#)) (set (:claims authn-info#))))) - (when (:linked-identifiers ~expected-event) - (is (= (set (:linked-identifiers ~expected-event)) - (set (get-in ~actual-event [:content :linked-identifiers]))))) - (when (some? (:success ~expected-event)) - (is (= (:success ~expected-event) (:success ~actual-event)))) - (when (some? (:acl ~expected-event)) - (is-acl (:acl ~expected-event) (:acl ~actual-event))))) ->>>>>>> 2ab3edc8 (support for deployment) - -(defmacro is-last-event - [resource-id expected-event] - `(let [event# (last (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] :last 1}))] - (is-event ~expected-event event#))) - -(defmacro are-last-events - [resource-id expected-events] - `(let [events# (take (count ~expected-events) (event-utils/query-events ~resource-id {:orderby [["timestamp" :desc]] - :last (count ~expected-events)}))] - (is (= (count ~expected-events) (count events#))) - (doall (map (fn [expected-event# actual-event#] - (is-event expected-event# actual-event#)) - ~expected-events - events#)))) diff --git a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig b/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig deleted file mode 100644 index aacfd3127..000000000 --- a/code/test/sixsq/nuvla/server/resources/module_lifecycle_test.clj.orig +++ /dev/null @@ -1,1054 +0,0 @@ -(ns sixsq.nuvla.server.resources.module-lifecycle-test - (:require - [clojure.data.json :as json] - [clojure.string :as str] - [clojure.test :refer [deftest is testing use-fixtures]] - [peridot.core :refer [content-type header request session]] - [sixsq.nuvla.server.app.params :as p] - [sixsq.nuvla.server.middleware.authn-info :refer [authn-info-header]] - [sixsq.nuvla.server.resources.common.utils :as u] - [sixsq.nuvla.server.resources.lifecycle-test-utils :as ltu] - [sixsq.nuvla.server.resources.module :as module] - [sixsq.nuvla.server.resources.module.utils :as utils])) - -(use-fixtures :each ltu/with-test-server-fixture) - -(def base-uri (str p/service-context module/resource-type)) - -(def timestamp "1964-08-25T10:00:00.00Z") - -(defn- get-path-segments - [path] - (reduce - (fn [acu cur] - (conj acu (if (seq acu) - (str (last acu) "/" cur) - cur))) - [] - (str/split path #"/"))) - -(defn create-parent-projects [path user] - (let [paths (get-path-segments (utils/get-parent-path path))] - (run! - (fn [path-segment] - (-> user - (request base-uri - :request-method :post - :body (json/write-str {:subtype utils/subtype-project - :path path-segment - :parent-path (utils/get-parent-path path-segment)})) - ltu/body->edn - (ltu/is-status 201))) - paths))) - -(def session-anon - (-> (session (ltu/ring-app)) - (content-type "application/json"))) -(def session-admin - (header session-anon authn-info-header - "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon")) -(def session-user - (header session-anon authn-info-header - "user/jane user/jane group/nuvla-user group/nuvla-anon")) - -(def authn-info-admin {:user-id "group/nuvla-admin" - :active-claim "group/nuvla-admin" - :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]}) -(def authn-info-jane {:user-id "user/jane" - :active-claim "user/jane" - :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]}) -(def authn-info-anon {:claims ["group/nuvla-anon"]}) - -(defn build-valid-entry - [subtype valid-content] - {:parent-path "a/b" - :path "a/b/c" - :subtype subtype - - :compatibility "docker-compose" - - :logo-url "https://example.org/logo" - - :data-accept-content-types ["application/json" "application/x-something"] - :data-access-protocols ["http+s3" "posix+nfs"] - - :content valid-content}) - -<<<<<<< HEAD - :content valid-content} - authn-info-admin {:user-id "group/nuvla-admin" - :active-claim "group/nuvla-admin" - :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} - authn-info-jane {:user-id "user/jane" - :active-claim "user/jane" - :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} - authn-info-anon {:claims ["group/nuvla-anon"]} - admin-group-name "Nuvla Administrator Group"] -======= -(defn create-module-nok - [valid-entry] - (let [] ->>>>>>> b3844f4f (Fix tests) - - ;; create: NOK for anon - (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 403)) - - (ltu/is-last-event nil - {:name "module.add" - :description "module.add attempt failed." - :category "add" - :success false - :linked-identifiers [] - :authn-info authn-info-anon - :acl {:owners ["group/nuvla-admin"]}}) - - ;; queries: NOK for anon - (-> session-anon - (request base-uri) - (ltu/body->edn) - (ltu/is-status 403)) - - (doseq [session [session-admin session-user]] - (-> session - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count zero?))) - - ;; Creating editable parent project - (create-parent-projects (:path valid-entry) session-user) - - ;; invalid module subtype - (-> session-admin - (request base-uri - :request-method :post - :body (json/write-str (assoc valid-entry :subtype "bad-module-subtype"))) - (ltu/body->edn) - (ltu/is-status 400)) - - (ltu/is-last-event nil - {:name "module.add" - :description "module.add attempt failed." - :category "add" - :success false - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners ["group/nuvla-admin"]}}) - - (when (utils/is-application? valid-entry) - (testing "application should have compatibility attribute set" - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (dissoc valid-entry :compatibility))) - (ltu/body->edn) - (ltu/is-status 400) - (ltu/message-matches "Application subtype should have compatibility attribute set!")))))) - - -(defn create-module - [session valid-entry event-owners authn-info] - (let [uri (-> session - (request base-uri - :request-method :post - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 201) - (ltu/location)) - - abs-uri (str p/service-context uri)] - - (ltu/is-last-event uri - {:event-type "module.add" - :category "add" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - ;; retrieve: NOK for anon - (-> session-anon - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 403)) - - uri)) - -(defn retrieve-module - [uri valid-entry valid-content] - (let [abs-uri (str p/service-context uri) - {:keys [content] :as module} (-> session-admin - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-key-value :compatibility "docker-compose") - (as-> m (if (utils/is-application? valid-entry) - (ltu/is-operation-present m :validate-docker-compose) - (ltu/is-operation-absent m :validate-docker-compose))) - (ltu/body))] - (is (= valid-content (select-keys content (keys valid-content)))) - module)) - - -(defn edit-module - [uri valid-entry event-owners] - (let [abs-uri (str p/service-context uri)] - ;; edit: NOK for anon - (-> session-anon - (request abs-uri - :request-method :put - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 403)) - - (ltu/is-last-event uri - {:event-type "module.edit" - :category "edit" - :success false - :linked-identifiers [] - :authn-info authn-info-anon - :acl {:owners event-owners}}) - - ;; insert 5 more versions - (doseq [_ (range 5)] - (-> session-admin - (request abs-uri - :request-method :put - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 200)) - - (ltu/is-last-event uri - {:event-type "module.edit" - :category "edit" - :success true - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners event-owners}})) - - (let [versions (-> session-admin - (request abs-uri - :request-method :put - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - :versions)] - (is (= 7 (count versions))) - - ;; extract by indexes or last - (doseq [[i n] [["_0" 0] ["_1" 1] ["" 6]]] - (let [content-id (-> session-admin - (request (str abs-uri i)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - :content - :id)] - (is (= (-> versions (nth n) :href) content-id)) - (is (= (-> versions (nth n) :author) "someone")) - (is (= (-> versions (nth n) :commit) "wip"))))))) - - -(defn publish-unpublish - [session uri event-owners authn-info] - ;; publish - (let [abs-uri (str p/service-context uri) - publish-url (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/get-op-url :publish))] - - (testing "publish last version" - (-> session - (request publish-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "published successfully")) - - (ltu/is-last-event uri - {:event-type "module.publish" - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}})) - - (testing "operation urls of specific version" - (let [abs-uri-v2 (str abs-uri "_2") - resp (-> session - (request (str abs-uri "_2")) - (ltu/body->edn) - (ltu/is-status 200)) - publish-url (ltu/get-op-url resp :publish) - unpublish-url (ltu/get-op-url resp :unpublish) - edit-url (ltu/get-op-url resp :edit) - delete-url (ltu/get-op-url resp :delete) - delete-version-url (ltu/get-op-url resp :delete-version)] - (is (= publish-url (str abs-uri-v2 "/publish"))) - (is (= unpublish-url (str abs-uri-v2 "/unpublish"))) - (is (= delete-version-url (str abs-uri-v2 "/delete-version"))) - (is (= delete-url abs-uri)) - (is (= edit-url abs-uri)))) - - (testing "publish specific version" - (-> session - (request (str abs-uri "_2/publish")) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "published successfully")) - - (ltu/is-last-event (str uri "_2") - {:event-type "module.publish" - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}})) - - (let [unpublish-url (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/is-key-value #(-> % last :published) :versions true) - (ltu/is-key-value #(-> % (nth 2) :published) :versions true) - (ltu/is-key-value :published true) - (ltu/get-op-url :unpublish))] - - (-> session - (request unpublish-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "unpublished successfully"))) - - (ltu/is-last-event uri - {:event-type "module.unpublish" - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - ; publish is idempotent - (-> session - (request (str abs-uri "_2/publish")) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "published successfully")) - - (ltu/is-last-event (str uri "_2") - {:event-type "module.publish" - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/is-key-value #(-> % last :published) :versions false) - (ltu/is-key-value :published true) - (ltu/get-op-url :unpublish)) - - (-> session - (request (str abs-uri "_2/unpublish")) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "unpublished successfully")) - - (ltu/is-last-event (str uri "_2") - {:event-type "module.unpublish" - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/is-key-value #(-> % (nth 2) :published) :versions false) - (ltu/is-key-value :published false) - (ltu/get-op-url :unpublish)))) - - -(defn versions - [uri valid-entry event-owners] - (let [abs-uri (str p/service-context uri)] - (testing "edit module without putting the module-content should not create new version" - (is (= 7 (-> session-admin - (request abs-uri - :request-method :put - :body (json/write-str (dissoc valid-entry :content :path))) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - :versions - count)))) - - (doseq [i ["_0/delete-version" "_1/delete-version"]] - (-> session-admin - (request (str abs-uri i)) - (ltu/body->edn) - (ltu/is-status 200)) - - - (-> session-admin - (request (str abs-uri i)) - (ltu/body->edn) - (ltu/is-status 404))) - - - (testing "delete latest version without specifying version" - (-> session-admin - (request (str abs-uri "/delete-version")) - (ltu/body->edn) - (ltu/is-status 200))) - - - (ltu/is-last-event uri - {:event-type "module.delete-version" - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners event-owners}}) - - - (testing "delete out of bound index should return 404" - (-> session-admin - (request (str abs-uri "_50/delete-version")) - (ltu/body->edn) - (ltu/is-status 404))) - - (-> session-admin - (request (str abs-uri "_50")) - (ltu/body->edn) - (ltu/is-status 404)))) - - -(defn delete-module - [uri event-owners] - (let [abs-uri (str p/service-context uri)] - ;; delete: NOK for anon - (-> session-anon - (request abs-uri - :request-method :delete) - (ltu/body->edn) - (ltu/is-status 403)) - - (-> session-admin - (request abs-uri - :request-method :delete) - (ltu/body->edn) - (ltu/is-status 200)) - - - (ltu/is-last-event uri - {:event-type "module.delete" - :category "delete" - :success true - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners event-owners}}) - - ;; verify that the resource was deleted. - (-> session-admin - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 404)))) - - -(defn lifecycle-test-module - [subtype valid-content] - (let [valid-entry (build-valid-entry subtype valid-content)] - (create-module-nok valid-entry) - ;; adding, retrieving and deleting entry as user should succeed -<<<<<<< HEAD - (doseq [[session event-owners authn-info user-name-or-id] - [[session-admin ["group/nuvla-admin"] authn-info-admin admin-group-name] - [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane "user/jane"]]] - (let [uri (-> session - (request base-uri - :request-method :post - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 201) - (ltu/location)) - - abs-uri (str p/service-context uri)] - - (ltu/is-last-event uri - {:name "module.add" - :description (str user-name-or-id " added module " uri ".") - :category "add" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - ;; retrieve: NOK for anon - (-> session-anon - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 403)) - - (let [{:keys [content acl]} (-> session-admin - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-key-value :compatibility "docker-compose") - (as-> m (if (utils/is-application? valid-entry) - (ltu/is-operation-present m :validate-docker-compose) - (ltu/is-operation-absent m :validate-docker-compose))) - (ltu/body))] - (is (= valid-content (select-keys content (keys valid-content))))) - - ;; edit: NOK for anon - (-> session-anon - (request abs-uri - :request-method :put - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 403)) - - (ltu/is-last-event uri - {:name "module.edit" - :description "module.edit attempt failed." - :category "edit" - :success false - :linked-identifiers [] - :authn-info authn-info-anon - :acl {:owners event-owners}}) - - ;; insert 5 more versions - (doseq [_ (range 5)] - (-> session-admin - (request abs-uri - :request-method :put - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 200)) - - (ltu/is-last-event uri - {:name "module.edit" - :description (str admin-group-name " edited module " uri ".") - :category "edit" - :success true - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners event-owners}})) - - (let [versions (-> session-admin - (request abs-uri - :request-method :put - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - :versions)] - (is (= 7 (count versions))) - - ;; extract by indexes or last - (doseq [[i n] [["_0" 0] ["_1" 1] ["" 6]]] - (let [content-id (-> session-admin - (request (str abs-uri i)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - :content - :id)] - (is (= (-> versions (nth n) :href) content-id)) - (is (= (-> versions (nth n) :author) "someone")) - (is (= (-> versions (nth n) :commit) "wip"))))) - - ;; publish - (let [publish-url (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/get-op-url :publish))] - - (testing "publish last version" - (-> session - (request publish-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "published successfully")) - - (ltu/is-last-event uri - {:name "module.publish" - :description (str user-name-or-id " executed action publish on module " uri ".") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}})) - - (testing "operation urls of specific version" - (let [abs-uri-v2 (str abs-uri "_2") - resp (-> session - (request (str abs-uri "_2")) - (ltu/body->edn) - (ltu/is-status 200)) - publish-url (ltu/get-op-url resp :publish) - unpublish-url (ltu/get-op-url resp :unpublish) - edit-url (ltu/get-op-url resp :edit) - delete-url (ltu/get-op-url resp :delete) - delete-version-url (ltu/get-op-url resp :delete-version)] - (is (= publish-url (str abs-uri-v2 "/publish"))) - (is (= unpublish-url (str abs-uri-v2 "/unpublish"))) - (is (= delete-version-url (str abs-uri-v2 "/delete-version"))) - (is (= delete-url abs-uri)) - (is (= edit-url abs-uri)))) - - (testing "publish specific version" - (-> session - (request (str abs-uri "_2/publish")) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "published successfully")) - - (ltu/is-last-event (str uri "_2") - {:name "module.publish" - :description (str user-name-or-id " executed action publish on module " uri "_2.") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}})) - - (let [unpublish-url (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/is-key-value #(-> % last :published) :versions true) - (ltu/is-key-value #(-> % (nth 2) :published) :versions true) - (ltu/is-key-value :published true) - (ltu/get-op-url :unpublish))] - - (-> session - (request unpublish-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "unpublished successfully"))) - - (ltu/is-last-event uri - {:name "module.unpublish" - :description (str user-name-or-id " executed action unpublish on module " uri ".") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - ; publish is idempotent - (-> session - (request (str abs-uri "_2/publish")) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "published successfully")) - - (ltu/is-last-event (str uri "_2") - {:name "module.publish" - :description (str user-name-or-id " executed action publish on module " uri "_2.") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/is-key-value #(-> % last :published) :versions false) - (ltu/is-key-value :published true) - (ltu/get-op-url :unpublish)) - - (-> session - (request (str abs-uri "_2/unpublish")) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/message-matches "unpublished successfully")) - - (ltu/is-last-event (str uri "_2") - {:name "module.unpublish" - :description (str user-name-or-id " executed action unpublish on module " uri "_2.") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info - :acl {:owners event-owners}}) - - (-> session - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-operation-present :publish) - (ltu/is-operation-present :unpublish) - (ltu/is-key-value #(-> % (nth 2) :published) :versions false) - (ltu/is-key-value :published false) - (ltu/get-op-url :unpublish))) - - (testing "edit module without putting the module-content should not create new version" - (is (= 7 (-> session-admin - (request abs-uri - :request-method :put - :body (json/write-str (dissoc valid-entry :content :path))) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - :versions - count)))) - - (doseq [i ["_0/delete-version" "_1/delete-version"]] - (-> session-admin - (request (str abs-uri i)) - (ltu/body->edn) - (ltu/is-status 200)) - - - (-> session-admin - (request (str abs-uri i)) - (ltu/body->edn) - (ltu/is-status 404))) - - - (testing "delete latest version without specifying version" - (-> session-admin - (request (str abs-uri "/delete-version")) - (ltu/body->edn) - (ltu/is-status 200))) - - - (ltu/is-last-event uri - {:name "module.delete-version" - :description (str admin-group-name " executed action delete-version on module " uri ".") - :category "action" - :success true - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners event-owners}}) - - - (testing "delete out of bound index should return 404" - (-> session-admin - (request (str abs-uri "_50/delete-version")) - (ltu/body->edn) - (ltu/is-status 404))) - - (-> session-admin - (request (str abs-uri "_50")) - (ltu/body->edn) - (ltu/is-status 404)) - - ;; delete: NOK for anon - (-> session-anon - (request abs-uri - :request-method :delete) - (ltu/body->edn) - (ltu/is-status 403)) - - (-> session-admin - (request abs-uri - :request-method :delete) - (ltu/body->edn) - (ltu/is-status 200)) - - - (ltu/is-last-event uri - {:name "module.delete" - :description (str admin-group-name " deleted module " uri ".") - :category "delete" - :success true - :linked-identifiers [] - :authn-info authn-info-admin - :acl {:owners event-owners}}) - - ;; verify that the resource was deleted. - (-> session-admin - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 404)))))) -======= - (doseq [[session event-owners authn-info] - [[session-admin ["group/nuvla-admin"] authn-info-admin] - [session-user ["group/nuvla-admin" "user/jane"] authn-info-jane]]] - (let [uri (create-module session valid-entry event-owners authn-info) - module (retrieve-module uri valid-entry valid-content)] - (edit-module uri valid-entry event-owners) - (publish-unpublish session uri event-owners authn-info) - (versions uri valid-entry event-owners) - (delete-module uri event-owners))))) - ->>>>>>> b3844f4f (Fix tests) - -(deftest lifecycle-component - (let [valid-component {:author "someone" - :commit "wip" - - :architectures ["amd64" "arm/v6"] - :image {:image-name "ubuntu" - :tag "16.04"} - :ports [{:protocol "tcp" - :target-port 22 - :published-port 8022}]}] - (lifecycle-test-module utils/subtype-comp valid-component))) - - -(deftest lifecycle-application - (let [valid-application {:author "someone" - :commit "wip" - :docker-compose "version: \"3.6\"\n\nx-common: &common\n stop_grace_period: 4s\n logging:\n options:\n max-size: \"250k\"\n max-file: \"10\"\n labels:\n - \"nuvlabox.component=True\"\n - \"nuvlabox.deployment=production\"\n\nvolumes:\n nuvlabox-db:\n driver: local\n\nnetworks:\n nuvlabox-shared-network:\n driver: overlay\n name: nuvlabox-shared-network\n attachable: true\n\nservices:\n data-gateway:\n <<: *common\n image: traefik:2.1.1\n container_name: datagateway\n restart: on-failure\n command:\n - --entrypoints.mqtt.address=:1883\n - --entrypoints.web.address=:80\n - --providers.docker=true\n - --providers.docker.exposedbydefault=false\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n networks:\n - default\n - nuvlabox-shared-network\n\n nb-mosquitto:\n <<: *common\n image: eclipse-mosquitto:1.6.8\n container_name: nbmosquitto\n restart: on-failure\n labels:\n - \"traefik.enable=true\"\n - \"traefik.tcp.routers.mytcprouter.rule=HostSNI(`*`)\"\n - \"traefik.tcp.routers.mytcprouter.entrypoints=mqtt\"\n - \"traefik.tcp.routers.mytcprouter.service=mosquitto\"\n - \"traefik.tcp.services.mosquitto.loadbalancer.server.port=1883\"\n - \"nuvlabox.component=True\"\n - \"nuvlabox.deployment=production\"\n healthcheck:\n test: [\"CMD-SHELL\", \"timeout -t 5 mosquitto_sub -t '$$SYS/#' -C 1 | grep -v Error || exit 1\"]\n interval: 10s\n timeout: 10s\n start_period: 10s\n\n system-manager:\n <<: *common\n image: nuvlabox/system-manager:1.0.1\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n ports:\n - 127.0.0.1:3636:3636\n healthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:3636\"]\n interval: 30s\n timeout: 10s\n retries: 4\n start_period: 10s\n\n agent:\n <<: *common\n image: nuvlabox/agent:1.3.2\n restart: on-failure\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n - NUVLA_ENDPOINT=${NUVLA_ENDPOINT:-nuvla.io}\n - NUVLA_ENDPOINT_INSECURE=${NUVLA_ENDPOINT_INSECURE:-False}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n - /:/rootfs:ro\n expose:\n - 5000\n depends_on:\n - system-manager\n - compute-api\n\n management-api:\n <<: *common\n image: nuvlabox/management-api:0.1.0\n restart: on-failure\n environment:\n - NUVLA_ENDPOINT=${NUVLA_ENDPOINT:-nuvla.io}\n - NUVLA_ENDPOINT_INSECURE=${NUVLA_ENDPOINT_INSECURE:-False}\n volumes:\n - /proc/sysrq-trigger:/sysrq\n - ${HOME}/.ssh/authorized_keys:/rootfs/.ssh/authorized_keys\n - nuvlabox-db:/srv/nuvlabox/shared\n - /var/run/docker.sock:/var/run/docker.sock\n ports:\n - 5001:5001\n healthcheck:\n test: curl -k https://localhost:5001 2>&1 | grep SSL\n interval: 20s\n timeout: 10s\n start_period: 30s\n\n compute-api:\n <<: *common\n image: nuvlabox/compute-api:0.2.5\n restart: on-failure\n pid: \"host\"\n environment:\n - HOST=${HOSTNAME:-nuvlabox}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n ports:\n - 5000:5000\n depends_on:\n - system-manager\n\n network-manager:\n <<: *common\n image: nuvlabox/network-manager:0.0.4\n restart: on-failure\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n - VPN_INTERFACE_NAME=${NUVLABOX_VPN_IFACE:-vpn}\n volumes:\n - nuvlabox-db:/srv/nuvlabox/shared\n depends_on:\n - system-manager\n\n vpn-client:\n <<: *common\n image: nuvlabox/vpn-client:0.0.4\n container_name: vpn-client\n restart: always\n network_mode: host\n cap_add:\n - NET_ADMIN\n devices:\n - /dev/net/tun\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n volumes:\n - nuvlabox-db:/srv/nuvlabox/shared\n depends_on:\n - network-manager"}] - - (lifecycle-test-module utils/subtype-app valid-application))) - -(deftest lifecycle-creating-applications - (let [session-anon (-> (session (ltu/ring-app)) - (content-type "application/json")) - session-admin (header session-anon authn-info-header - "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") - session-user (header session-anon authn-info-header - "user/jane user/jane group/nuvla-user group/nuvla-anon") - - project {:resource-type module/resource-type - :created timestamp - :updated timestamp - :parent-path "" - :path "example" - :subtype utils/subtype-project} - - valid-app {:parent-path "example" - :path "example/app" - :subtype utils/subtype-app - :compatibility "docker-compose" - :logo-url "https://example.org/logo" - :data-accept-content-types ["application/json" "application/x-something"] - :data-access-protocols ["http+s3" "posix+nfs"] - :content {:author "someone" - :commit "wip" - :docker-compose "version: \"3.6\"\n\nx-common: &common\n stop_grace_period: 4s\n logging:\n options:\n max-size: \"250k\"\n max-file: \"10\"\n labels:\n - \"nuvlabox.component=True\"\n - \"nuvlabox.deployment=production\"\n\nvolumes:\n nuvlabox-db:\n driver: local\n\nnetworks:\n nuvlabox-shared-network:\n driver: overlay\n name: nuvlabox-shared-network\n attachable: true\n\nservices:\n data-gateway:\n <<: *common\n image: traefik:2.1.1\n container_name: datagateway\n restart: on-failure\n command:\n - --entrypoints.mqtt.address=:1883\n - --entrypoints.web.address=:80\n - --providers.docker=true\n - --providers.docker.exposedbydefault=false\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n networks:\n - default\n - nuvlabox-shared-network\n\n nb-mosquitto:\n <<: *common\n image: eclipse-mosquitto:1.6.8\n container_name: nbmosquitto\n restart: on-failure\n labels:\n - \"traefik.enable=true\"\n - \"traefik.tcp.routers.mytcprouter.rule=HostSNI(`*`)\"\n - \"traefik.tcp.routers.mytcprouter.entrypoints=mqtt\"\n - \"traefik.tcp.routers.mytcprouter.service=mosquitto\"\n - \"traefik.tcp.services.mosquitto.loadbalancer.server.port=1883\"\n - \"nuvlabox.component=True\"\n - \"nuvlabox.deployment=production\"\n healthcheck:\n test: [\"CMD-SHELL\", \"timeout -t 5 mosquitto_sub -t '$$SYS/#' -C 1 | grep -v Error || exit 1\"]\n interval: 10s\n timeout: 10s\n start_period: 10s\n\n system-manager:\n <<: *common\n image: nuvlabox/system-manager:1.0.1\n restart: always\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n ports:\n - 127.0.0.1:3636:3636\n healthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:3636\"]\n interval: 30s\n timeout: 10s\n retries: 4\n start_period: 10s\n\n agent:\n <<: *common\n image: nuvlabox/agent:1.3.2\n restart: on-failure\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n - NUVLA_ENDPOINT=${NUVLA_ENDPOINT:-nuvla.io}\n - NUVLA_ENDPOINT_INSECURE=${NUVLA_ENDPOINT_INSECURE:-False}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n - /:/rootfs:ro\n expose:\n - 5000\n depends_on:\n - system-manager\n - compute-api\n\n management-api:\n <<: *common\n image: nuvlabox/management-api:0.1.0\n restart: on-failure\n environment:\n - NUVLA_ENDPOINT=${NUVLA_ENDPOINT:-nuvla.io}\n - NUVLA_ENDPOINT_INSECURE=${NUVLA_ENDPOINT_INSECURE:-False}\n volumes:\n - /proc/sysrq-trigger:/sysrq\n - ${HOME}/.ssh/authorized_keys:/rootfs/.ssh/authorized_keys\n - nuvlabox-db:/srv/nuvlabox/shared\n - /var/run/docker.sock:/var/run/docker.sock\n ports:\n - 5001:5001\n healthcheck:\n test: curl -k https://localhost:5001 2>&1 | grep SSL\n interval: 20s\n timeout: 10s\n start_period: 30s\n\n compute-api:\n <<: *common\n image: nuvlabox/compute-api:0.2.5\n restart: on-failure\n pid: \"host\"\n environment:\n - HOST=${HOSTNAME:-nuvlabox}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - nuvlabox-db:/srv/nuvlabox/shared\n ports:\n - 5000:5000\n depends_on:\n - system-manager\n\n network-manager:\n <<: *common\n image: nuvlabox/network-manager:0.0.4\n restart: on-failure\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n - VPN_INTERFACE_NAME=${NUVLABOX_VPN_IFACE:-vpn}\n volumes:\n - nuvlabox-db:/srv/nuvlabox/shared\n depends_on:\n - system-manager\n\n vpn-client:\n <<: *common\n image: nuvlabox/vpn-client:0.0.4\n container_name: vpn-client\n restart: always\n network_mode: host\n cap_add:\n - NET_ADMIN\n devices:\n - /dev/net/tun\n environment:\n - NUVLABOX_UUID=${NUVLABOX_UUID}\n volumes:\n - nuvlabox-db:/srv/nuvlabox/shared\n depends_on:\n - network-manager"}}] - - (testing "Failure creating application 1: no parent project is specified" - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (-> valid-app - (assoc :parent-path "") - (assoc :path "app")))) - ltu/body->edn - (ltu/is-status 400) - (ltu/message-matches "Application subtype must have a parent project!"))) - - (testing "Failure creating application 2: specified parent project does not exist" - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (assoc valid-app :path "non-existent-parent/path"))) - ltu/body->edn - (ltu/is-status 400) - (ltu/message-matches "No parent project found for path: non-existent-parent"))) - - (testing "Failure creating application 3: user does not have edit rights in parent project" - ;; Creating a parent project with nuvla-admin as owner - (let [uri (-> session-admin - (request base-uri - :request-method :post - :body (json/write-str project)) - ltu/body->edn - (ltu/is-status 201) - ltu/location-url)] - - ;; If user has no view rights, failure message says that parent project does not exist. - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str valid-app)) - ltu/body->edn - (ltu/is-status 400) - (ltu/message-matches "No parent project found for path: example")) - - ;; Adding view rights for user - (-> session-admin - (request uri - :request-method :put - :body (json/write-str - (assoc project - :acl {:owners ["group/nuvla-admin"] - :view-meta ["user/jane"] - :view-data ["user/jane"] - :view-acl ["user/jane"]}))) - ltu/body->edn - (ltu/is-status 200))) - - ;; If user has view rights, message says user lacks edit rights for parent project. - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str valid-app)) - ltu/body->edn - (ltu/is-status 403) - (ltu/message-matches "You do not have edit rights for:"))) - - ;; Trying to add app to parent app should fail - (testing "Failure creating application 4: Parent is not a project." - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (assoc project :path "example2"))) - ltu/body->edn - (ltu/is-status 201)) - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (assoc valid-app :path "example2/app"))) - ltu/body->edn - (ltu/is-status 201)) - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (assoc valid-app :path "example2/app/not-allowed"))) - ltu/body->edn - (ltu/is-status 403) - (ltu/message-matches "Parent must be a project!"))) - - (testing "new application can be in a project nested inside another project" - ;; Creating a parent project with wrong edit rights - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (assoc project :path "grandparent"))) - ltu/body->edn - (ltu/is-status 201)) - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (assoc project :path "grandparent/parent"))) - ltu/body->edn - (ltu/is-status 201)) - (-> session-user - (request base-uri - :request-method :post - :body (json/write-str (assoc valid-app :path "grandparent/parent/app"))) - ltu/body->edn - (ltu/is-status 201))) - - (testing "new application can not be top-level is also applied to admin-users" - (-> session-admin - (request base-uri - :request-method :post - :body (json/write-str (assoc valid-app :path "fails"))) - ltu/body->edn - (ltu/is-status 400) - (ltu/message-matches "Application subtype must have a parent project!"))))) - -(def valid-applications-sets-content - {:author "someone" - :commit "wip" - :applications-sets [{:name "x" - :applications [{:id "module/x" - :version 0}]}]}) - -(deftest lifecycle-applications-sets - (lifecycle-test-module utils/subtype-apps-sets valid-applications-sets-content)) - - -(deftest lifecycle-applications-sets-extended - (let [session-anon (-> (session (ltu/ring-app)) - (content-type "application/json")) - session-user (header session-anon authn-info-header - "user/jane user/jane group/nuvla-user group/nuvla-anon") - - valid-app-1 {:parent-path "a/b" - :path "clara/app-1" - :subtype utils/subtype-app - :compatibility "docker-compose" - :content {:author "someone" - :commit "initial" - :docker-compose "some content"}} - _project (create-parent-projects (:path valid-app-1) session-user) - app-1-create-resp (-> session-user - (request base-uri - :request-method :post - :body (json/write-str valid-app-1)) - (ltu/body->edn) - (ltu/is-status 201)) - app-1-uri (ltu/location-url app-1-create-resp) - app-1-id (ltu/location app-1-create-resp)] - - (let [valid-entry {:parent-path "a/b" - :path "a/b/c" - :subtype utils/subtype-apps-sets - :content (assoc-in valid-applications-sets-content - [:applications-sets 0 - :applications 0 :id] app-1-id)}] - - (-> session-user - (request app-1-uri - :request-method :put - :body (json/write-str - (update valid-app-1 :content assoc - :docker-compose "content changed" - :commit "second commit"))) - (ltu/body->edn) - (ltu/is-status 200)) - - (create-parent-projects (:path valid-entry) session-user) - (let [response (-> session-user - (request base-uri - :request-method :post - :body (json/write-str valid-entry)) - (ltu/body->edn) - (ltu/is-status 201)) - uri (ltu/location response) - abs-uri (ltu/location-url response) - deploy-uri (-> session-user - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/get-op-url :deploy))] - (-> session-user - (request deploy-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-key-value :application uri) - (ltu/is-key-value :version 0) - (ltu/is-key-value #(-> % - first - :applications - first - :resolved - :content - :docker-compose) - :applications-sets - "some content")))))) - - -(deftest bad-methods - (let [resource-uri (str p/service-context (u/new-resource-id module/resource-type))] - (ltu/verify-405-status [[base-uri :delete] - [resource-uri :post]]))) diff --git a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj.orig b/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj.orig deleted file mode 100644 index 269d1a0b1..000000000 --- a/code/test/sixsq/nuvla/server/resources/session_api_key_lifecycle_test.clj.orig +++ /dev/null @@ -1,263 +0,0 @@ -(ns sixsq.nuvla.server.resources.session-api-key-lifecycle-test - (:require - [clojure.data.json :as json] - [clojure.string :as str] - [clojure.test :refer [are deftest is use-fixtures]] - [peridot.core :refer [content-type header request session]] - [sixsq.nuvla.auth.cookies :as cookies] - [sixsq.nuvla.auth.utils.sign :as sign] - [sixsq.nuvla.server.app.params :as p] - [sixsq.nuvla.server.middleware.authn-info :refer [authn-cookie authn-info-header]] - [sixsq.nuvla.server.resources.common.utils :as u] - [sixsq.nuvla.server.resources.credential-template-api-key :as api-key-tpl] - [sixsq.nuvla.server.resources.credential.key-utils :as key-utils] - [sixsq.nuvla.server.resources.lifecycle-test-utils :as ltu] - [sixsq.nuvla.server.resources.session :as session] - [sixsq.nuvla.server.resources.session-api-key :as t] - [sixsq.nuvla.server.resources.session-template :as st] - [sixsq.nuvla.server.resources.session-template-api-key :as api-key] - [sixsq.nuvla.server.util.time :as time])) - -(use-fixtures :once ltu/with-test-server-fixture) - -(def base-uri (str p/service-context session/resource-type)) - -(def session-template-base-uri (str p/service-context st/resource-type)) - - -(def session-template-api-key {:method api-key/authn-method - :instance api-key/authn-method - :name "API Key" - :description "Authentication with API Key and Secret" - :key "key" - :secret "secret" - :acl st/resource-acl}) - -(deftest check-uuid->id - (let [uuid (u/random-uuid) - correct-id (str "credential/" uuid)] - (is (= correct-id (t/uuid->id uuid))) - (is (= correct-id (t/uuid->id correct-id))))) - -(deftest check-valid-api-key - (let [subtype api-key-tpl/credential-subtype - expired (time/to-str (time/ago 10 :seconds)) - current (time/to-str (time/from-now 1 :hours)) - [secret digest] (key-utils/generate) - [_ bad-digest] (key-utils/generate) - valid-api-key {:subtype subtype - :expiry current - :digest digest}] - (is (true? (t/valid-api-key? valid-api-key secret))) - (are [v] (true? (t/valid-api-key? v secret)) - valid-api-key - (dissoc valid-api-key :expiry)) - (are [v] (false? (t/valid-api-key? v secret)) - {} - (dissoc valid-api-key :subtype) - (assoc valid-api-key :subtype "incorrect-subtype") - (assoc valid-api-key :expiry expired) - (assoc valid-api-key :digest bad-digest)) - (is (false? (t/valid-api-key? valid-api-key "bad-secret"))))) - -(deftest check-create-claims - (let [user-id "user/root" - server "nuvla.io" - headers {:nuvla-ssl-server-name server} - claims #{"user/root" "group/nuvla-user" "group/nuvla-anon"} - session-id "session/72e9f3d8-805a-421b-b3df-86f1af294233" - client-ip "127.0.0.1"] - (is (= {:client-ip "127.0.0.1" - :claims (str "group/nuvla-anon group/nuvla-user user/root " session-id) - :user-id "user/root" - :server "nuvla.io" - :session "session/72e9f3d8-805a-421b-b3df-86f1af294233"} - (cookies/create-cookie-info user-id - :claims claims - :headers headers - :session-id session-id - :client-ip client-ip))))) - - -(deftest lifecycle - - (let [[secret digest] (key-utils/generate) - [_ bad-digest] (key-utils/generate) - uuid (u/random-uuid) - valid-api-key {:id (str "credential/" uuid) - :subtype api-key-tpl/credential-subtype - :method api-key-tpl/method - :expiry (time/to-str (time/from-now 1 :hours)) - :digest digest - :claims {:identity "user/abcdef01-abcd-abcd-abcd-abcdef012345" - :roles ["group/nuvla-user" "group/nuvla-anon"]}} - mock-retrieve-by-id {(:id valid-api-key) valid-api-key - uuid valid-api-key}] - - (with-redefs [t/retrieve-credential-by-id mock-retrieve-by-id] - - ;; check that the mocking is working correctly - (is (= valid-api-key (t/retrieve-credential-by-id (:id valid-api-key)))) - (is (= valid-api-key (t/retrieve-credential-by-id uuid))) - - (let [app (ltu/ring-app) - session-json (content-type (session app) "application/json") - session-anon (header session-json authn-info-header "user/unknown user/unknown group/nuvla-anon") - session-user (header session-json authn-info-header "user/user group/nuvla-user group/nuvla-anon") - session-admin (header session-json authn-info-header - "group/nuvla-admin group/nuvla-user group/nuvla-anon") - - ;; - ;; create the session template to use for these tests - ;; - href (str st/resource-type "/api-key") - - name-attr "name" - description-attr "description" - tags-attr ["one", "two"] - - valid-create {:name name-attr - :description description-attr - :tags tags-attr - :template {:href href - :key uuid - :secret secret}} - unauthorized-create (update-in valid-create [:template :secret] (constantly bad-digest)) - invalid-create (assoc-in valid-create [:template :invalid] "BAD") - event-authn-info {:user-id "user/unknown" - :active-claim "user/unknown" - :claims ["user/unknown" "group/nuvla-anon"]}] - - ;; anonymous query should succeed but have no entries - (-> session-anon - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count zero?)) - - ;; unauthorized create must return a 403 response - (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str unauthorized-create)) - (ltu/body->edn) - (ltu/is-status 403)) - -<<<<<<< HEAD - (ltu/is-last-event uuid {:name "session.add" -======= - (ltu/is-last-event uuid {:event-type "session.add" - :description "Login attempt failed." ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - :category "add" - :success false - :linked-identifiers [(str "credential/" uuid)] - :authn-info event-authn-info - :acl {:owners ["group/nuvla-admin"]}}) - - ;; anonymous create must succeed; also with redirect - (let [resp (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str valid-create)) - (ltu/body->edn) - (ltu/is-set-cookie) - (ltu/is-status 201)) - id (ltu/body-resource-id resp) -<<<<<<< HEAD - _ (ltu/is-last-event id {:name "session.add" -======= - _ (ltu/is-last-event id {:event-type "session.add" - :description (str (:id valid-api-key) " logged in.") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - :category "add" - :success true - :linked-identifiers [(str "credential/" uuid)] - :authn-info event-authn-info - :acl {:owners ["group/nuvla-admin" id]}}) - - token (get-in resp [:response :cookies authn-cookie :value]) - cookie-info (if token (sign/unsign-cookie-info token) {}) - - uri (-> resp - (ltu/location)) - abs-uri (str p/service-context uri)] - - ;; check cookie-info in cookie - (is (= "user/abcdef01-abcd-abcd-abcd-abcdef012345" (:user-id cookie-info))) - (is (= (str/join " " ["group/nuvla-anon" "group/nuvla-user" uri]) (:claims cookie-info))) ;; uri is also session id - (is (= uri (:session cookie-info))) ;; uri is also session id - (is (not (nil? (:exp cookie-info)))) - - ;; user should not be able to see session without session role - (-> session-user - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 403)) - - ;; anonymous query should succeed but still have no entries - (-> session-anon - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count zero?)) - - ;; user query should succeed but have no entries because of missing session role - (-> session-user - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count zero?)) - - ;; admin query should succeed, but see no sessions without the correct session role - (-> session-admin - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count 0)) - - ;; user should be able to see session with session role - (-> (session app) - (header authn-info-header (str "user/user group/nuvla-user " id)) - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-id id) - (ltu/is-operation-present :delete) - (ltu/is-operation-absent :edit)) - - ;; user query with session role should succeed but and have one entry - (-> (session app) - (header authn-info-header (str "user/user group/nuvla-user " id)) - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count 1)) - - ;; check contents of session resource - (let [{:keys [name description tags]} (-> (session app) - (header authn-info-header (str "user/user group/nuvla-user " id)) - (request abs-uri) - (ltu/body->edn) - :response - :body)] - (is (= name name-attr)) - (is (= description description-attr)) - (is (= tags tags-attr))) - - ;; user with session role can delete resource - (-> (session app) - (header authn-info-header (str "user/user group/nuvla-user " id)) - (request abs-uri - :request-method :delete) - (ltu/is-unset-cookie) - (ltu/body->edn) - (ltu/is-status 200)) - - ;; create with invalid template fails - (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str invalid-create)) - (ltu/body->edn) - (ltu/is-status 400))))))) diff --git a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj.orig b/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj.orig deleted file mode 100644 index 3a9d87b3a..000000000 --- a/code/test/sixsq/nuvla/server/resources/session_password_lifecycle_test.clj.orig +++ /dev/null @@ -1,784 +0,0 @@ -(ns sixsq.nuvla.server.resources.session-password-lifecycle-test - (:require - [clojure.data.json :as json] - [clojure.string :as str] - [clojure.test :refer [deftest is testing use-fixtures]] - [peridot.core :refer [content-type header request session]] - [postal.core :as postal] - [sixsq.nuvla.auth.password :as auth-password] - [sixsq.nuvla.auth.utils :as auth] - [sixsq.nuvla.auth.utils.sign :as sign] - [sixsq.nuvla.server.app.params :as p] - [sixsq.nuvla.server.middleware.authn-info - :refer [authn-cookie authn-info-header wrap-authn-info]] - [sixsq.nuvla.server.resources.configuration-nuvla :as config-nuvla] - [sixsq.nuvla.server.resources.email.sending :as email-sending] - [sixsq.nuvla.server.resources.group :as group] - [sixsq.nuvla.server.resources.group-template :as group-tpl] - [sixsq.nuvla.server.resources.lifecycle-test-utils :as ltu] - [sixsq.nuvla.server.resources.nuvlabox :as nuvlabox] - [sixsq.nuvla.server.resources.session :as session] - [sixsq.nuvla.server.resources.session-template :as st] - [sixsq.nuvla.server.resources.user :as user] - [sixsq.nuvla.server.resources.user-template :as user-tpl] - [sixsq.nuvla.server.resources.user-template-email-password :as email-password])) - - -(use-fixtures :once ltu/with-test-server-fixture) - - -(def base-uri (str p/service-context session/resource-type)) -(def grp-base-uri (str p/service-context group/resource-type)) -(def nb-base-uri (str p/service-context nuvlabox/resource-type)) - -(defn create-user - [session-admin & {:keys [username password email activated?]}] - (let [validation-link (atom nil) - href (str user-tpl/resource-type "/" email-password/registration-method) - href-create {:template {:href href - :password password - :username username - :email email}}] - - (with-redefs [email-sending/extract-smtp-cfg - (fn [_] {:host "smtp@example.com" - :port 465 - :ssl true - :user "admin" - :pass "password"}) - - ;; WARNING: This is a fragile! Regex matching to recover callback URL. - postal/send-message (fn [_ {:keys [body]}] - (let [url (->> body second :content - (re-matches #"(?s).*visit:\n\n\s+(.*?)\n.*") - second)] - (reset! validation-link url)) - {:code 0, :error :SUCCESS, :message "OK"})] - - (let [user-id (-> session-admin - (request (str p/service-context user/resource-type) - :request-method :post - :body (json/write-str href-create)) - (ltu/body->edn) - (ltu/is-status 201) - (ltu/location))] - - (when activated? - (is (re-matches #"^email.*successfully validated$" - (-> session-admin - (request @validation-link) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - :message)))) - user-id)))) - -(defn valid-create-grp - [group-id] - {:template {:href "group-template/generic" - :group-identifier group-id - :name (str "Group " group-id) - :description (str "Group " group-id " description")}}) - -(deftest lifecycle - - (let [app (ltu/ring-app) - session-json (content-type (session app) "application/json") - session-anon (header session-json authn-info-header "user/unknown user/unknown group/nuvla-anon") - session-user (header session-json authn-info-header "user group/nuvla-user") - session-admin (header session-json authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") - - href (str st/resource-type "/password") - - template-url (str p/service-context href) - - name-attr "name" - description-attr "description" - tags-attr ["one", "two"]] - - ;; password session template should exist - (-> session-anon - (request template-url) - (ltu/body->edn) - (ltu/is-status 200)) - - - ;; anon without valid user can not create session - (let [username "anon" - plaintext-password "anon" - - valid-create {:name name-attr - :description description-attr - :tags tags-attr - :template {:href href - :username username - :password plaintext-password}} - unauthorized-create (update-in valid-create [:template :password] (constantly "BAD"))] - - ; anonymous query should succeed but have no entries - (-> session-anon - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count zero?)) - - ; unauthorized create must return a 403 response - (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str unauthorized-create)) - (ltu/body->edn) - (ltu/is-status 403)) - ) - - - ;; anon with valid activated user can create session - (let [username "user/jane" - plaintext-password "JaneJane-0" - - valid-create {:name name-attr - :description description-attr - :tags tags-attr - :template {:href href - :username username - :password plaintext-password}} - - invalid-create (assoc-in valid-create [:template :invalid] "BAD") - jane-user-id (create-user session-admin - :username username - :password plaintext-password - :activated? true - :email "jane@example.org")] - - ; anonymous create must succeed - (let [resp (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str valid-create)) - (ltu/body->edn) - (ltu/is-set-cookie) - (ltu/is-status 201)) - id (ltu/body-resource-id resp) - - token (get-in resp [:response :cookies authn-cookie :value]) - authn-info (if token (sign/unsign-cookie-info token) {}) - event-authn-info {:user-id "user/user" - :active-claim "group/nuvla-user" - :claims ["group/nuvla-anon" id "user/user"]} - - uri (ltu/location resp) - abs-uri (str p/service-context uri)] - - ; check claims in cookie - (is (= jane-user-id (:user-id authn-info))) - (is (= #{"group/nuvla-user" - "group/nuvla-anon" - uri - jane-user-id} - (some-> authn-info - :claims - (str/split #"\s") - set))) - (is (= uri (:session authn-info))) - (is (not (nil? (:exp authn-info)))) - - ; user should not be able to see session without session role - (-> session-user - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 403)) - - ; anonymous query should succeed but still have no entries - (-> session-anon - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count zero?)) - - ; user query should succeed but have no entries because of missing session role - (-> session-user - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count zero?)) - - ; admin query should succeed, but see no sessions without the correct session role - (-> session-admin - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count 0)) - - ; user should be able to see session with session role - (-> (session app) - (header authn-info-header (str "user/user group/nuvla-user " id)) - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-id id) - (ltu/is-operation-present :delete) - (ltu/is-operation-absent :edit) - (ltu/is-operation-present :switch-group)) - - ; check contents of session - (let [{:keys [name description tags]} (-> session-user - (header authn-info-header (str "user/user group/nuvla-user group/nuvla-anon " id)) - (request abs-uri) - (ltu/body->edn) - :response - :body)] - (is (= name name-attr)) - (is (= description description-attr)) - (is (= tags tags-attr))) - - ; user query with session role should succeed but and have one entry - (-> (session app) - (header authn-info-header (str "user/user group/nuvla-user " id)) - (request base-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count 1)) - - ; user with session role can delete resource - (-> (session app) - (header authn-info-header (str "user/user group/nuvla-user " id)) - (request abs-uri - :request-method :delete) - (ltu/is-unset-cookie) - (ltu/body->edn) - (ltu/is-status 200)) - - (ltu/is-last-event id -<<<<<<< HEAD - {:name "session.delete" -======= - {:event-type "session.delete" - :description (str "user/user logged out.") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - :category "delete" - :success true - :linked-identifiers [] - :authn-info event-authn-info - :acl {:owners ["group/nuvla-admin" - "user/user"]}}) - - - - ; create with invalid template fails - (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str invalid-create)) - (ltu/body->edn) - (ltu/is-status 400))) - - ;; admin create with invalid template fails - (-> session-admin - (request base-uri - :request-method :post - :body (json/write-str invalid-create)) - (ltu/body->edn) - (ltu/is-status 400))) - - ;; anon with valid non activated user cannot create session - (let [username "alex" - plaintext-password "AlexAlex-0" - - valid-create {:name name-attr - :description description-attr - :tags tags-attr - :template {:href href - :username username - :password plaintext-password}}] - - (create-user session-admin - :username username - :password plaintext-password - :activated? false - :email "alex@example.org") - - ; unauthorized create must return a 403 response - (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str valid-create)) - (ltu/body->edn) - (ltu/is-status 403))))) - - -(deftest switch-group-lifecycle-test - (let [app (ltu/ring-app) - session-json (content-type (session app) "application/json") - session-anon (header session-json authn-info-header "user/unknown user/unknown group/nuvla-anon") - session-admin (header session-json authn-info-header "user/super group/nuvla-admin group/nuvla-user group/nuvla-anon group/nuvla-admin") - - href (str st/resource-type "/password") - - username "user/bob" - plaintext-password "BobBob-0" - user-id (create-user session-admin - :username username - :password plaintext-password - :activated? true - :email "bob@example.org") - - valid-create {:template {:href href - :username username - :password plaintext-password}} - session-user (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str valid-create)) - (ltu/body->edn) - (ltu/is-set-cookie) - (ltu/is-status 201)) - session-user-id (ltu/body-resource-id session-user) - sesssion-user-url (ltu/location-url session-user) - credential-id (:credential-password (auth-password/user-id->user user-id)) - _ (ltu/is-last-event session-user-id -<<<<<<< HEAD - {:name "session.add" -======= - {:event-type "session.add" - :description (str username " logged in.") ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - :category "add" - :success true - :linked-identifiers [user-id credential-id] - :authn-info {:user-id "user/unknown" - :active-claim "user/unknown" - :claims ["user/unknown" "group/nuvla-anon"]} - :acl {:owners ["group/nuvla-admin" user-id]}}) - handler (wrap-authn-info identity) - authn-session-user (-> session-user - :response - (select-keys [:cookies]) - handler - seq - flatten) - group-a-identifier "switch-test-a" - group-a (str group/resource-type "/" group-a-identifier) - group-b-identifier "switch-test-b" - group-b (str group/resource-type "/" group-b-identifier) - switch-op-url (-> (apply request session-json (concat [sesssion-user-url] authn-session-user)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/get-op-url :switch-group)) - event-authn-info {:user-id user-id - :active-claim user-id - :claims ["group/nuvla-anon" "group/nuvla-user" session-user-id user-id]}] - - (testing "User cannot switch to a group that he is not part of." - (-> (apply request session-json - (concat [switch-op-url :body (json/write-str {:claim group-b}) - :request-method :post] authn-session-user)) - (ltu/body->edn) - (ltu/is-status 403) - (ltu/message-matches #"Switch group cannot be done to requested group:.*")) - -<<<<<<< HEAD - (ltu/is-last-event session-user-id {:name "session.switch-group" -======= - (ltu/is-last-event session-user-id {:event-type "session.switch-group" - :description "Switch group attempt failed." ->>>>>>> bc60a2ab (use description field to provide human readable label for events) - :category "action" - :success false - :linked-identifiers [group-b] - :authn-info event-authn-info - :acl {:owners ["group/nuvla-admin" group-b]}})) - - (testing "User can switch to a group that he is part of." - (-> session-admin - (request (-> session-admin - (request (str p/service-context group/resource-type) - :request-method :post - :body (json/write-str - {:template - {:href (str group-tpl/resource-type "/generic") - :group-identifier group-a-identifier}})) - (ltu/body->edn) - (ltu/is-status 201) - (ltu/location-url)) - :request-method :put - :body (json/write-str {:users [user-id]})) - (ltu/body->edn) - (ltu/is-status 200)) - (let [response (-> (apply request session-json - (concat [switch-op-url :body (json/write-str {:claim group-a}) - :request-method :post] authn-session-user)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-set-cookie) - :response) - authn-session-group-a (-> response - (select-keys [:cookies]) - handler - seq - flatten)] - (testing "Cookie is set and claims correspond to group a" - (is (= {:active-claim group-a - :claims #{"group/nuvla-anon" - "group/nuvla-user" - session-user-id - group-a} - :user-id user-id} - (-> response - handler - auth/current-authentication)))) - - (testing "Nuvlabox owner is set correctly to the active-claim" - (binding [config-nuvla/*stripe-api-key* nil] - (let [nuvlabox-url (-> (apply request session-json - (concat [nb-base-uri - :body (json/write-str {}) - :request-method :post] authn-session-group-a)) - (ltu/body->edn) - (ltu/is-status 201) - (ltu/location-url))] - - (-> (apply request session-json (concat [nuvlabox-url] authn-session-group-a)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-key-value :owner group-a))))) - - (testing "switch back to user is possible" - (is (= user-id - (-> (apply request session-json - (concat [switch-op-url :body (json/write-str {:claim user-id}) - :request-method :post] authn-session-group-a)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-set-cookie) - :response - (select-keys [:cookies]) - handler - auth/current-authentication - :active-claim)))) - - (testing "switch to subgroup is possible" - (-> (header session-json authn-info-header (str "user/x " group-a " user/x group/nuvla-user group/nuvla-anon " group-a)) - (request grp-base-uri - :request-method :post - :body (json/write-str (valid-create-grp "switch-test-b"))) - (ltu/body->edn) - (ltu/is-status 201)) - - (let [response (-> (apply request session-json - (concat [switch-op-url :body (json/write-str {:claim "group/switch-test-b"}) - :request-method :post] authn-session-user)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-set-cookie) - :response) - authn-session-group-b (-> response - (select-keys [:cookies]) - handler - seq - flatten)] - (is (= "group/switch-test-b" - (-> response - (select-keys [:cookies]) - handler - auth/current-authentication - :active-claim))) - - (-> (apply request session-json (concat [(str p/service-context nuvlabox/resource-type)] authn-session-group-b)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count 0)) - - (-> (apply request session-json - (concat [nb-base-uri - :body (json/write-str {}) - :request-method :post] authn-session-group-b)) - (ltu/body->edn) - (ltu/is-status 201))))) - (testing "switch to subgroup with extended claims" - (let [response (-> (apply request session-json - (concat [switch-op-url :body (json/write-str {:claim group-a :extended true}) - :request-method :post] authn-session-user)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-set-cookie) - :response) - authn-session-group-a-ext (-> response - (select-keys [:cookies]) - handler - seq - flatten)] - (testing "Cookie is set and claims correspond to group a but claims are extended" - (is (= {:active-claim group-a - :claims #{"group/nuvla-anon" - "group/nuvla-user" - session-user-id - group-a - group-b} - :user-id user-id} - (-> response - handler - auth/current-authentication)))) - - (testing "NuvlaEdge of group b are visible for group a" - (-> (apply request session-json - (concat [nb-base-uri] authn-session-group-a-ext)) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-count 2)))))))) - - -(deftest get-groups-lifecycle-test - (let [app (ltu/ring-app) - session-json (content-type (session app) "application/json") - session-anon (header session-json authn-info-header "user/unknown user/unknown group/nuvla-anon") - session-admin (header session-json authn-info-header "user/super group/nuvla-admin group/nuvla-user group/nuvla-anon group/nuvla-admin") - user-id (create-user session-admin - :username "tarzan" - :password "TarzanTarzan-0" - :activated? true - :email "tarzan@example.org") - session-user (header session-json authn-info-header (str user-id user-id " group/nuvla-user group/nuvla-anon")) - session-group-a (header session-json authn-info-header "user/x group/a user/x group/nuvla-user group/nuvla-anon group/a") - session-group-b (header session-json authn-info-header "user/x group/b user/x group/nuvla-user group/nuvla-anon group/b") - href (str st/resource-type "/password")] - - (-> session-admin - (request grp-base-uri - :request-method :post - :body (json/write-str (valid-create-grp "a"))) - (ltu/body->edn) - (ltu/is-status 201)) - (-> session-group-a - (request grp-base-uri - :request-method :post - :body (json/write-str (valid-create-grp "b"))) - (ltu/body->edn) - (ltu/is-status 201)) - (-> session-group-a - (request grp-base-uri - :request-method :post - :body (json/write-str (valid-create-grp "b1"))) - (ltu/body->edn) - (ltu/is-status 201)) - (-> session-group-b - (request grp-base-uri - :request-method :post - :body (json/write-str (valid-create-grp "c"))) - (ltu/body->edn) - (ltu/is-status 201)) - - (let [resp (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str {:template {:href href - :username "tarzan" - :password "TarzanTarzan-0"}})) - (ltu/body->edn) - (ltu/is-set-cookie) - (ltu/is-status 201)) - id (ltu/body-resource-id resp) - abs-uri (ltu/location-url resp) - session-with-id (header session-json authn-info-header (str user-id user-id " group/nuvla-user group/nuvla-anon " id))] - (testing "User should be able to see session with session role" - (-> session-with-id - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/is-id id) - (ltu/is-operation-present :delete) - (ltu/is-operation-absent :edit) - (ltu/is-operation-present :switch-group) - (ltu/is-operation-present :get-peers) - (ltu/is-operation-present :get-groups))) - - (let [get-groups-url (-> session-user - (header authn-info-header (str user-id " " user-id " group/nuvla-user group/nuvla-anon " id)) - (request abs-uri) - (ltu/body->edn) - (ltu/get-op-url :get-groups))] - - (testing "User who is not in any group should get empty list of groups" - (-> session-with-id - (request get-groups-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - (= []) - (is "Get groups body should have no childs"))) - - (testing "When user is part of a group, he should get subgroups" - (-> session-admin - (request (str p/service-context "group/b") - :request-method :put - :body (json/write-str {:users [user-id]})) - (ltu/body->edn) - (ltu/is-status 200)) - (-> session-with-id - (request get-groups-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - (= [{:children [{:description "Group c description" - :id "group/c" - :name "Group c"}] - :description "Group b description" - :id "group/b" - :name "Group b"}]) - (is "User get group/b and subgroup group/c"))) - - (testing "When user is part of a root group he should get - the full group hierarchy and group/b is not duplicated" - (-> session-admin - (request (str p/service-context "group/a") - :request-method :put - :body (json/write-str {:users [user-id]})) - (ltu/body->edn) - (ltu/is-status 200)) - (-> session-admin - (request grp-base-uri - :request-method :post - :body (json/write-str (valid-create-grp "z"))) - (ltu/body->edn) - (ltu/is-status 201)) - (-> session-admin - (request (str p/service-context "group/z") - :request-method :put - :body (json/write-str {:users [user-id]})) - (ltu/body->edn) - (ltu/is-status 200)) - (-> session-with-id - (request get-groups-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - (= [{:children [{:children [{:description "Group c description" - :id "group/c" - :name "Group c"}] - :description "Group b description" - :id "group/b" - :name "Group b"} - {:description "Group b1 description" - :id "group/b1" - :name "Group b1"}] - :description "Group a description" - :id "group/a" - :name "Group a"} - {:description "Group z description" - :id "group/z" - :name "Group z"}]) - (is "Get groups body should contain tree of groups"))))))) - - -(deftest get-peers-lifecycle-test - (let [app (ltu/ring-app) - session-json (content-type (session app) "application/json") - session-anon (header session-json authn-info-header "user/unknown user/unknown group/nuvla-anon") - session-admin (header session-json authn-info-header "user/super group/nuvla-admin group/nuvla-user group/nuvla-anon group/nuvla-admin") - user-id (create-user session-admin - :username "peer0" - :password "Peer0Peer-0" - :activated? true - :email "peer-0@example.org") - peer-1 (create-user session-admin - :username "peer1" - :password "Peer1Peer-1" - :activated? true - :email "peer-1@example.org") - peer-2 (create-user session-admin - :username "peer2" - :password "Peer2Peer-2" - :activated? false - :email "peer-2@example.org") - peer-3 (create-user session-admin - :username "peer3" - :password "Peer3Peer-3" - :activated? true - :email "peer-3@example.org") - session-user (header session-json authn-info-header (str user-id user-id " group/nuvla-user group/nuvla-anon")) - session-group-a (header session-json authn-info-header "user/x group/peers-test-a user/x group/nuvla-user group/nuvla-anon group/peers-test-a") - href (str st/resource-type "/password") - - resp (-> session-anon - (request base-uri - :request-method :post - :body (json/write-str {:template {:href href - :username "peer0" - :password "Peer0Peer-0"}})) - (ltu/body->edn) - (ltu/is-set-cookie) - (ltu/is-status 201)) - id (ltu/body-resource-id resp) - abs-uri (ltu/location-url resp) - session-with-id (header session-json authn-info-header (str user-id user-id " group/nuvla-user group/nuvla-anon " id)) - get-peers-url (-> session-user - (header authn-info-header (str user-id " " user-id " group/nuvla-user group/nuvla-anon " id)) - (request abs-uri) - (ltu/body->edn) - (ltu/get-op-url :get-peers))] - - (testing "admin should get all users with validated emails" - (-> session-admin - (request get-peers-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - vals - set - (= #{"peer-0@example.org" "peer-1@example.org" "peer-3@example.org"}) - (is "Get peers body should contain all users with validated emails"))) - - (testing "user who is not in any group should get empty map of peers" - (-> session-with-id - (request get-peers-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - (= {}) - (is "Get peers body should be empty"))) - - (-> session-admin - (request (-> session-admin - (request grp-base-uri - :request-method :post - :body (json/write-str (valid-create-grp "peers-test-a"))) - (ltu/body->edn) - (ltu/is-status 201) - (ltu/location-url)) - :request-method :put - :body (json/write-str {:users [peer-1 user-id peer-2]})) - (ltu/body->edn) - (ltu/is-status 200)) - - (testing "user should get peers of the group when email is validated only" - (-> session-with-id - (request get-peers-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - vals - set - (= #{"peer-0@example.org" "peer-1@example.org"}) - (is "Get peers body should be himself and peer-1"))) - - (testing "user should get peers of subgroup also" - (-> session-admin - (request (-> session-group-a - (request grp-base-uri - :request-method :post - :body (json/write-str (valid-create-grp "peers-test-b"))) - (ltu/body->edn) - (ltu/is-status 201) - (ltu/location-url)) - :request-method :put - :body (json/write-str {:users [peer-3 user-id peer-2]})) - (ltu/body->edn) - (ltu/is-status 200)) - (-> session-with-id - (request get-peers-url) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body) - vals - set - (= #{"peer-0@example.org" "peer-1@example.org" "peer-3@example.org"}) - (is "Get peers body should contain peer-3"))))) From aa54728b368c47f3aa56fa66599c188b3b4a9646 Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Wed, 30 Aug 2023 11:10:54 +0200 Subject: [PATCH 16/17] basic support for credentials --- .../nuvla/server/resources/credential.clj | 28 +++- .../resources/credential/vpn_utils_test.clj | 49 ++++++- .../credential_api_key_lifecycle_test.clj | 92 ++++++++++--- .../credential_gpg_key_lifecycle_test.clj | 84 ++++++++---- ...dential_hashed_password_lifecycle_test.clj | 47 ++++++- ...cture_service_amazonec2_lifecycle_test.clj | 47 ++++++- ...structure_service_azure_lifecycle_test.clj | 47 ++++++- ...ucture_service_exoscale_lifecycle_test.clj | 47 ++++++- ...tructure_service_google_lifecycle_test.clj | 47 ++++++- ...structure_service_minio_lifecycle_test.clj | 47 ++++++- ...cture_service_openstack_lifecycle_test.clj | 59 ++++++-- ...ucture_service_registry_lifecycle_test.clj | 47 ++++++- ...structure_service_swarm_lifecycle_test.clj | 49 ++++++- .../credential_ssh_key_lifecycle_test.clj | 127 ++++++++++++------ .../credential_swarm_token_lifecycle_test.clj | 48 ++++++- .../credential_totp_2fa_lifecycle_test.clj | 60 +++++++-- .../resources/deployment_lifecycle_test.clj | 4 +- 17 files changed, 777 insertions(+), 152 deletions(-) diff --git a/code/src/sixsq/nuvla/server/resources/credential.clj b/code/src/sixsq/nuvla/server/resources/credential.clj index cacd63a8c..fbd031c81 100644 --- a/code/src/sixsq/nuvla/server/resources/credential.clj +++ b/code/src/sixsq/nuvla/server/resources/credential.clj @@ -9,6 +9,8 @@ passwords) or other services (e.g. TLS credentials for Docker). Creating new [sixsq.nuvla.auth.utils :as auth] [sixsq.nuvla.db.impl :as db] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-config :as ec] + [sixsq.nuvla.server.resources.common.event-context :as ectx] [sixsq.nuvla.server.resources.common.std-crud :as std-crud] [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.job :as job] @@ -314,13 +316,31 @@ passwords) or other services (e.g. TLS credentials for Docker). Creating new (defmethod crud/delete resource-type [{{uuid :uuid} :params :as request}] - (-> (str resource-type "/" uuid) - (db/retrieve request) - (a/throw-cannot-delete request) - (special-delete request))) + (let [resource (-> (str resource-type "/" uuid) + (db/retrieve request)) + delete-resp (-> resource + (a/throw-cannot-delete request) + (special-delete request))] + (ectx/add-to-context :resource resource) + (ectx/add-to-context :acl (:acl resource)) + delete-resp)) (def query-impl (std-crud/query-fn resource-type collection-acl collection-type)) (defmethod crud/query resource-type [request] (query-impl request)) + + +;; +;; Events +;; + +(defmethod ec/events-enabled? resource-type + [_resource-type] + true) + + +(defmethod ec/log-event? "credential.check" + [_event _response] + false) diff --git a/code/test/sixsq/nuvla/server/resources/credential/vpn_utils_test.clj b/code/test/sixsq/nuvla/server/resources/credential/vpn_utils_test.clj index 6dcee3725..82d4c80cd 100644 --- a/code/test/sixsq/nuvla/server/resources/credential/vpn_utils_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential/vpn_utils_test.clj @@ -1,6 +1,7 @@ (ns sixsq.nuvla.server.resources.credential.vpn-utils-test (:require [clojure.data.json :as json] + [clojure.string :as string] [clojure.string :as str] [clojure.test :refer [is]] [peridot.core :refer [content-type header request session]] @@ -76,7 +77,18 @@ :description description-attr :tags tags-attr :template {:href href - :parent infra-service-id}}] + :parent infra-service-id}} + + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + test-claims (-> claims (string/split #"\s")) + authn-info-test {:user-id (first test-claims) + :active-claim (second test-claims) + :claims (set test-claims)} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; admin/user query should succeed but be empty (no credentials created yet) (doseq [session [session-admin session-test]] @@ -96,13 +108,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-test session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-test ["group/nuvla-admin"] authn-info-test] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-import-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -162,6 +186,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str user-id " added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-test + :acl {:owners ["group/nuvla-admin"]}}) + ;; admin should be able to see and delete credential (-> session-admin (request abs-uri) @@ -226,7 +259,15 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))) + (ltu/is-status 200)) + (ltu/is-last-event uri + {:name "credential.delete" + :description (str user-id " deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-test + :acl {:owners ["group/nuvla-admin"]}})) )) )) diff --git a/code/test/sixsq/nuvla/server/resources/credential_api_key_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_api_key_lifecycle_test.clj index f87adc105..3c46afcdf 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_api_key_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_api_key_lifecycle_test.clj @@ -35,7 +35,7 @@ session (content-type "application/json")) session-admin (header session authn-info-header - "group/nuvla-admin group/nuvla-user group/nuvla-anon") + "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") session-user (header session authn-info-header "user/jane user/jane group/nuvla-user group/nuvla-anon") session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon") @@ -63,7 +63,17 @@ create-import-href-zero-ttl {:template {:href href :ttl 0}} - create-import-href-no-ttl {:template {:href href}}] + create-import-href-no-ttl {:template {:href href}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}} + admin-group-name "Nuvla Administrator Group"] ;; admin/user query should succeed but be empty (no credentials created yet) (if (env/env :nuvla-super-password) @@ -91,13 +101,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-import-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -123,6 +145,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; the secret key must be returned as part of the 201 response (is secret-key) @@ -155,7 +186,16 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})) ;; execute the same tests but now create an API key without an expiry date (let [resp (-> session-user @@ -236,7 +276,8 @@ (request abs-uri) (ltu/body->edn) (ltu/is-status 200) - (ltu/body))] + (ltu/body)) + new-name-attr "UPDATED!"] (is digest) (is (key-utils/valid? secret-key digest)) (is (nil? expiry)) @@ -249,7 +290,7 @@ :request-method :put :body (json/write-str (assoc current - :name "UPDATED!" + :name new-name-attr :claims {:identity "super", :roles ["group/nuvla-user" "group/nuvla-anon" "group/nuvla-admin"]}))) (ltu/body->edn) @@ -266,6 +307,15 @@ (is (= (dissoc expected :updated) (dissoc reread :updated :updated-by))) (is (not= (:updated expected) (:updated reread)))) + (ltu/is-last-event uri + {:name "credential.edit" + :description (str "user/jane edited credential " new-name-attr ".") + :category "edit" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; update the credential by changing the name attribute ;; claims are editable for super (-> session-admin @@ -280,17 +330,27 @@ (ltu/is-status 200)) ;; verify that the attribute has been changed - (let [expected (assoc current :name "UPDATED by super!" - :claims {:identity "super", - :roles ["group/nuvla-user" "group/nuvla-anon" "group/nuvla-admin"]}) - reread (-> session-admin - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body))] + (let [new-name-attr "UPDATED by super!" + expected (assoc current :name new-name-attr + :claims {:identity "super", + :roles ["group/nuvla-user" "group/nuvla-anon" "group/nuvla-admin"]}) + reread (-> session-admin + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body))] (is (= (dissoc expected :updated) (dissoc reread :updated :updated-by))) - (is (not= (:updated expected) (:updated reread))))) + (is (not= (:updated expected) (:updated reread))) + + (ltu/is-last-event uri + {:name "credential.edit" + :description (str admin-group-name " edited credential " new-name-attr ".") + :category "edit" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners ["group/nuvla-admin" "user/jane"]}}))) ;; delete the credential (-> session-user diff --git a/code/test/sixsq/nuvla/server/resources/credential_gpg_key_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_gpg_key_lifecycle_test.clj index cc4bce469..3b7c4fd36 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_gpg_key_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_gpg_key_lifecycle_test.clj @@ -46,24 +46,33 @@ (ltu/body)) upload {:template (-> template - ltu/strip-unwanted-attrs - (assoc :href href - :public-key "mypublickey"))} + ltu/strip-unwanted-attrs + (assoc :href href + :public-key "mypublickey"))} upload-pvt-key {:template (-> template - ltu/strip-unwanted-attrs - (assoc :href href - :public-key "mypublickey" - :private-key "******"))} + ltu/strip-unwanted-attrs + (assoc :href href + :public-key "mypublickey" + :private-key "******"))} create-no-href {:template (-> template - ltu/strip-unwanted-attrs - (assoc :public-key "mypublickey"))} + ltu/strip-unwanted-attrs + (assoc :public-key "mypublickey"))} create {:name name-attr :description description-attr :tags tags-attr - :template {:href href}}] + :template {:href href}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; check we can perform search with ordering by name (-> session-admin @@ -90,13 +99,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -124,6 +145,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " uri ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -160,15 +190,15 @@ ;;;;;; ;; upload an existing gpg key and save the private key (let [resp (-> session-user - (request base-uri - :request-method :post - :body (json/write-str upload-pvt-key)) - (ltu/body->edn) - (ltu/is-status 201)) + (request base-uri + :request-method :post + :body (json/write-str upload-pvt-key)) + (ltu/body->edn) + (ltu/is-status 201)) id (ltu/body-resource-id resp) keypair (get-in resp [:response :body]) uri (-> resp - (ltu/location)) + (ltu/location)) abs-uri (str p/service-context uri)] ;; resource id and the uri (location) should be the same @@ -190,9 +220,17 @@ ;; delete the credential (-> session-user - (request abs-uri - :request-method :delete) - (ltu/body->edn) - (ltu/is-status 200))) )) - + (request abs-uri + :request-method :delete) + (ltu/body->edn) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " uri ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_hashed_password_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_hashed_password_lifecycle_test.clj index 8877bd8b6..7f6cecf8d 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_hashed_password_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_hashed_password_lifecycle_test.clj @@ -55,7 +55,16 @@ :description description-attr :tags tags-attr :template {:href href - :password plaintext-password}}] + :password plaintext-password}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; admin/user query should succeed but be empty (no credentials created yet) (doseq [session [session-admin session-user]] @@ -75,13 +84,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -106,6 +127,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -185,6 +215,15 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_amazonec2_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_amazonec2_lifecycle_test.clj index 61722c41d..4bc3eedf4 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_amazonec2_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_amazonec2_lifecycle_test.clj @@ -50,7 +50,16 @@ :tags tags-attr :template {:href href :amazonec2-access-key "abc" - :amazonec2-secret-key "def"}}] + :amazonec2-secret-key "def"}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; admin/user query should succeed but be empty (no credentials created yet) (doseq [session [session-admin session-user]] @@ -70,13 +79,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-import-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -101,6 +122,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -128,4 +158,13 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_azure_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_azure_lifecycle_test.clj index 5b03aa27f..bd119a108 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_azure_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_azure_lifecycle_test.clj @@ -51,7 +51,16 @@ :template {:href href :azure-subscription-id "abc" :azure-client-secret "def" - :azure-client-id "ghi"}}] + :azure-client-id "ghi"}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; check we can perform search with ordering by name (-> session-admin @@ -78,13 +87,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-import-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -109,6 +130,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -137,4 +167,13 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_exoscale_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_exoscale_lifecycle_test.clj index 9fe8b80c2..58651aa82 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_exoscale_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_exoscale_lifecycle_test.clj @@ -50,7 +50,16 @@ :tags tags-attr :template {:href href :exoscale-api-key "abc" - :exoscale-api-secret-key "def"}}] + :exoscale-api-secret-key "def"}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; admin/user query should succeed but be empty (no credentials created yet) (doseq [session [session-admin session-user]] @@ -70,13 +79,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-import-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -101,6 +122,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -128,4 +158,13 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_google_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_google_lifecycle_test.clj index 3751ae23e..39e5285ae 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_google_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_google_lifecycle_test.clj @@ -53,7 +53,16 @@ :google-username "username" :client-id "12345678901.apps.googleusercontent.com" :client-secret "secret" - :refresh-token "refresh token"}}] + :refresh-token "refresh token"}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; admin/user query should succeed but be empty (no credentials created yet) (doseq [session [session-admin session-user]] @@ -73,13 +82,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-import-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -104,6 +125,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -138,4 +168,13 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_minio_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_minio_lifecycle_test.clj index 60ad81d56..f406b898f 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_minio_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_minio_lifecycle_test.clj @@ -56,7 +56,16 @@ :template {:href href :parent parent-value :access-key access-key-value - :secret-key secret-key-value}}] + :secret-key secret-key-value}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; admin/user query should succeed but be empty (no credentials created yet) (doseq [session [session-admin session-user]] @@ -76,13 +85,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-import-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -107,6 +128,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -137,4 +167,13 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_openstack_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_openstack_lifecycle_test.clj index 7179c2655..544ed84dd 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_openstack_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_openstack_lifecycle_test.clj @@ -48,9 +48,18 @@ create-import-href {:name name-attr :description description-attr :tags tags-attr - :template {:href href - :openstack-username "foo" - :openstack-password "bar"}}] + :template {:href href + :openstack-username "foo" + :openstack-password "bar"}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; admin/user query should succeed but be empty (no credentials created yet) (doseq [session [session-admin session-user]] @@ -70,13 +79,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-import-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -101,6 +122,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -114,10 +144,10 @@ (let [{:keys [name description tags openstack-username openstack-password]} (-> session-user - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body))] + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body))] (is (= name name-attr)) (is (= description description-attr)) (is (= tags tags-attr)) @@ -129,4 +159,13 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_registry_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_registry_lifecycle_test.clj index a7b8b6e3a..b31f009bf 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_registry_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_registry_lifecycle_test.clj @@ -57,7 +57,16 @@ :template {:href href :parent parent-value :username username-value - :password password-value}}] + :password password-value}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; admin/user query should succeed but be empty (no credentials created yet) (doseq [session [session-admin session-user]] @@ -77,13 +86,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-import-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -108,6 +129,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -149,4 +179,13 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_swarm_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_swarm_lifecycle_test.clj index 7f5e1dd7a..20bdc99ff 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_swarm_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_infrastructure_service_swarm_lifecycle_test.clj @@ -60,7 +60,16 @@ :parent parent-value :ca ca-value :cert cert-value - :key key-value}}] + :key key-value}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] (testing "Admin/user query should succeed but be empty (no credentials created yet)" (doseq [session [session-admin session-user]] @@ -80,13 +89,25 @@ (ltu/is-status 403))) (testing "Creating a new credential without reference will fail for all types of users" - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-import-no-href)) (ltu/body->edn) - (ltu/is-status 400)))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}}))) (testing "Creating a new credential as anon will fail; expect 400 because href cannot be accessed" (-> session-anon @@ -111,6 +132,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -145,7 +175,7 @@ (ltu/is-status 202))))) (testing "Ensure that create and check credential created 2 cred check jobs" - (let [descr-changed "descr changed" + (let [descr-changed "descr changed" job-url-filter (str job-base-uri "?filter=action='credential_check'&target-resource/href='" id "'")] (-> session-user (request job-url-filter @@ -158,4 +188,13 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_ssh_key_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_ssh_key_lifecycle_test.clj index ebb4198ae..c6f81c948 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_ssh_key_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_ssh_key_lifecycle_test.clj @@ -46,24 +46,33 @@ (ltu/body)) upload {:template (-> template - ltu/strip-unwanted-attrs - (assoc :href href - :public-key "mypublickey"))} + ltu/strip-unwanted-attrs + (assoc :href href + :public-key "mypublickey"))} upload-pvtkey {:template (-> template - ltu/strip-unwanted-attrs - (assoc :href href - :public-key "mypublickey" - :private-key "******"))} + ltu/strip-unwanted-attrs + (assoc :href href + :public-key "mypublickey" + :private-key "******"))} create-no-href {:template (-> template - ltu/strip-unwanted-attrs - (assoc :public-key "mypublickey"))} + ltu/strip-unwanted-attrs + (assoc :public-key "mypublickey"))} create {:name name-attr :description description-attr :tags tags-attr - :template {:href href}}] + :template {:href href}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; check we can perform search with ordering by name (-> session-admin @@ -90,13 +99,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -124,6 +145,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " uri ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -141,10 +171,10 @@ ;; ensure credential contains correct information (let [{:keys [public-key private-key]} (-> session-user - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body))] + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body))] (is (= "mypublickey" public-key (:public-key keypair))) ; it is a custom user SSH key, and no private key was provided...so none was generated nor stored @@ -160,15 +190,15 @@ ;;;;;; ;; upload an existing ssh key and save the private key (let [resp (-> session-user - (request base-uri - :request-method :post - :body (json/write-str upload-pvtkey)) - (ltu/body->edn) - (ltu/is-status 201)) + (request base-uri + :request-method :post + :body (json/write-str upload-pvtkey)) + (ltu/body->edn) + (ltu/is-status 201)) id (ltu/body-resource-id resp) keypair (get-in resp [:response :body]) uri (-> resp - (ltu/location)) + (ltu/location)) abs-uri (str p/service-context uri)] ;; resource id and the uri (location) should be the same @@ -178,10 +208,10 @@ ;; ensure credential contains correct information (let [{:keys [public-key private-key]} (-> session-user - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body))] + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body))] (is (= "mypublickey" public-key (:public-key keypair))) ; even though no private key is generated, the original one is still returned back in the response @@ -190,22 +220,31 @@ ;; delete the credential (-> session-user - (request abs-uri - :request-method :delete) - (ltu/body->edn) - (ltu/is-status 200))) + (request abs-uri + :request-method :delete) + (ltu/body->edn) + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " uri ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})) ;;;; ;; ask Nuvla to generate the keypair from scratch (let [resp (-> session-user - (request base-uri - :request-method :post - :body (json/write-str create)) - (ltu/body->edn) - (ltu/is-status 201)) + (request base-uri + :request-method :post + :body (json/write-str create)) + (ltu/body->edn) + (ltu/is-status 201)) id (ltu/body-resource-id resp) uri (-> resp - (ltu/location)) + (ltu/location)) keypair (get-in resp [:response :body]) abs-uri (str p/service-context uri)] @@ -216,10 +255,10 @@ ;; ensure credential contains correct information (let [{:keys [name description tags public-key private-key]} (-> session-user - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body))] + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body))] (is (= name name-attr)) (is (= description description-attr)) (is (= tags tags-attr)) @@ -231,9 +270,9 @@ ;; delete the credential (-> session-user - (request abs-uri - :request-method :delete) - (ltu/body->edn) - (ltu/is-status 200))))) + (request abs-uri + :request-method :delete) + (ltu/body->edn) + (ltu/is-status 200))))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_swarm_token_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_swarm_token_lifecycle_test.clj index 2df8891fd..5fb388e57 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_swarm_token_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_swarm_token_lifecycle_test.clj @@ -55,7 +55,16 @@ :tags tags-attr :template {:href href :scope "MANAGER" - :token "some-swarm-token"}}] + :token "some-swarm-token"}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] ;; admin/user query should succeed but be empty (no credentials created yet) (doseq [session [session-admin session-user]] @@ -75,13 +84,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -106,6 +127,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str "user/jane added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}}) + ;; admin/user should be able to see and delete credential (doseq [session [session-admin session-user]] (-> session @@ -138,6 +168,14 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) - + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str "user/jane deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-jane + :acl {:owners ["group/nuvla-admin" "user/jane"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/credential_totp_2fa_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/credential_totp_2fa_lifecycle_test.clj index fd925d52c..f2cf76836 100644 --- a/code/test/sixsq/nuvla/server/resources/credential_totp_2fa_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/credential_totp_2fa_lifecycle_test.clj @@ -51,8 +51,18 @@ create-href {:name name-attr :description description-attr :tags tags-attr - :template {:href href - :secret "some-secret"}}] + :template {:href href + :secret "some-secret"}} + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-jane {:user-id "user/jane" + :active-claim "user/jane" + :claims ["group/nuvla-anon" "user/jane" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}} + admin-group-name "Nuvla Administrator Group"] ;; admin/user query should succeed but be empty (no credentials created yet) (doseq [session [session-admin session-user]] @@ -73,13 +83,25 @@ (ltu/is-status 403)) ;; creating a new credential without reference will fail for all types of users - (doseq [session [session-admin session-user session-anon]] + (doseq [[session event-owners authn-info] + [[session-admin ["group/nuvla-admin"] authn-info-admin] + [session-user ["group/nuvla-admin"] authn-info-jane] + [session-anon ["group/nuvla-admin"] authn-info-anon]]] (-> session (request base-uri :request-method :post :body (json/write-str create-no-href)) (ltu/body->edn) - (ltu/is-status 400))) + (ltu/is-status 400)) + + (ltu/is-last-event nil + {:name "credential.add" + :description "credential.add attempt failed." + :category "add" + :success false + :linked-identifiers [] + :authn-info authn-info + :acl {:owners event-owners}})) ;; creating a new credential as anon will fail; expect 400 because href cannot be accessed (-> session-anon @@ -111,6 +133,15 @@ ;; resource id and the uri (location) should be the same (is (= id uri)) + (ltu/is-last-event uri + {:name "credential.add" + :description (str admin-group-name " added credential " name-attr ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners ["group/nuvla-admin"]}}) + ;; admin should be able to see and delete credential (-> session-admin (request abs-uri) @@ -127,10 +158,10 @@ ;; ensure credential contains correct information (let [{:keys [name description tags secret]} (-> session-admin - (request abs-uri) - (ltu/body->edn) - (ltu/is-status 200) - (ltu/body))] + (request abs-uri) + (ltu/body->edn) + (ltu/is-status 200) + (ltu/body))] (is (= name name-attr)) (is (= description description-attr)) (is (= tags tags-attr)) @@ -141,6 +172,13 @@ (request abs-uri :request-method :delete) (ltu/body->edn) - (ltu/is-status 200))))) - - + (ltu/is-status 200)) + + (ltu/is-last-event uri + {:name "credential.delete" + :description (str admin-group-name " deleted credential " name-attr ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info-admin + :acl {:owners ["group/nuvla-admin"]}})))) diff --git a/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj index ff87e4536..9f785cf11 100644 --- a/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/deployment_lifecycle_test.clj @@ -561,11 +561,11 @@ (ltu/is-last-event deployment-id {:name "deployment.delete" - :description (str "user/jane deleted deployment " deployment-id ".") + :description (str "user/jane deleted deployment API credential for " deployment-id ".") :category "delete" :success true :authn-info authn-info-jane - :acl {:owners (conj event-owners-jane deployment-id "user/tarzan")}}) + :acl {:owners (conj ["group/nuvla-admin"] deployment-id)}}) ;; verify that the deployment has disappeared (-> session-user From 7c17817d67a3f13628431e335bbac80eef2a88cf Mon Sep 17 00:00:00 2001 From: Alessandro Bellucci Date: Thu, 31 Aug 2023 11:41:24 +0200 Subject: [PATCH 17/17] support for nuvlabox and comment out legacy events on nuvlabox resource --- .../sixsq/nuvla/server/resources/nuvlabox.clj | 138 ++++++++++- .../resources/nuvlabox_0_lifecycle_test.clj | 219 ++++++++++++++---- .../resources/nuvlabox_1_lifecycle_test.clj | 149 ++++++++++-- .../resources/nuvlabox_2_lifecycle_test.clj | 148 ++++++++++-- 4 files changed, 548 insertions(+), 106 deletions(-) diff --git a/code/src/sixsq/nuvla/server/resources/nuvlabox.clj b/code/src/sixsq/nuvla/server/resources/nuvlabox.clj index 37a906d84..ebb83a1bd 100644 --- a/code/src/sixsq/nuvla/server/resources/nuvlabox.clj +++ b/code/src/sixsq/nuvla/server/resources/nuvlabox.clj @@ -13,10 +13,11 @@ particular NuvlaBox release. [sixsq.nuvla.auth.utils.acl :as acl-utils] [sixsq.nuvla.db.impl :as db] [sixsq.nuvla.server.resources.common.crud :as crud] + [sixsq.nuvla.server.resources.common.event-config :as ec] + [sixsq.nuvla.server.resources.common.event-context :as ectx] [sixsq.nuvla.server.resources.common.std-crud :as std-crud] [sixsq.nuvla.server.resources.common.utils :as u] [sixsq.nuvla.server.resources.credential.vpn-utils :as vpn-utils] - [sixsq.nuvla.server.resources.event.utils :as event-utils] [sixsq.nuvla.server.resources.job :as job] [sixsq.nuvla.server.resources.job.interface :as job-interface] [sixsq.nuvla.server.resources.nuvlabox.utils :as utils] @@ -331,9 +332,12 @@ particular NuvlaBox release. (defmethod crud/delete resource-type [{{uuid :uuid} :params :as request}] - (let [id (str resource-type "/" uuid)] + (let [id (str resource-type "/" uuid) + nuvlabox (db/retrieve id request)] + (ectx/add-to-context :acl (:acl nuvlabox)) + (ectx/add-to-context :resource nuvlabox) (try - (-> (db/retrieve id request) + (-> nuvlabox (a/throw-cannot-delete request) (u/throw-can-not-do-action utils/can-delete? "delete")) (let [resp (delete-impl request)] @@ -500,7 +504,9 @@ particular NuvlaBox release. (when (not= job-status 201) (throw (r/ex-response "unable to create async job to decommission nuvlabox resources" 500 id))) - (event-utils/create-event id job-msg acl) + (ectx/add-linked-identifier job-id) + ;; Legacy event + ;; (event-utils/create-event id job-msg acl) (r/map-response job-msg 202 id job-id)) (catch Exception e (or (ex-data e) (throw e))))) @@ -542,7 +548,9 @@ particular NuvlaBox release. (when (not= job-status 201) (throw (r/ex-response "unable to create async job to check nuvlabox api" 500 id))) - (event-utils/create-event id job-msg acl) + (ectx/add-linked-identifier job-id) + ;; Legacy event + ;; (event-utils/create-event id job-msg acl) (r/map-response job-msg 202 id job-id)) (catch Exception e (or (ex-data e) (throw e))))) @@ -580,7 +588,9 @@ particular NuvlaBox release. (when (not= job-status 201) (throw (r/ex-response "unable to create async job to reboot nuvlabox" 500 id))) - (event-utils/create-event id job-msg acl) + (ectx/add-linked-identifier job-id) + ;; Legacy event + ;; (event-utils/create-event id job-msg acl) (r/map-response job-msg 202 id job-id)) (catch Exception e (or (ex-data e) (throw e))))) @@ -629,7 +639,9 @@ particular NuvlaBox release. ", with async " job-id)] (when (not= job-status 201) (throw (r/ex-response "unable to create async job to cluster NuvlaBox" 500 id))) - (event-utils/create-event id job-msg acl) + (ectx/add-linked-identifier job-id) + ;; Legacy event + ;; (event-utils/create-event id job-msg acl) (r/map-response job-msg 202 id job-id)) (catch Exception e (or (ex-data e) (throw e))))) @@ -676,7 +688,9 @@ particular NuvlaBox release. (when (not= job-status 201) (throw (r/ex-response "unable to create async job to add SSH key to NuvlaBox" 500 id))) - (event-utils/create-event id job-msg acl) + (ectx/add-linked-identifier job-id) + ;; Legacy event + ;; (event-utils/create-event id job-msg acl) (r/map-response (or (:private-key ssh-credential) job-msg) 202 id job-id)) (catch Exception e (or (ex-data e) (throw e))))) @@ -745,7 +759,9 @@ particular NuvlaBox release. (when (not= job-status 201) (throw (r/ex-response "unable to create async job to remove SSH key from NuvlaBox" 500 id))) - (event-utils/create-event id job-msg acl) + (ectx/add-linked-identifier job-id) + ;; Legacy event + ;; (event-utils/create-event id job-msg acl) (r/map-response job-msg 202 id job-id)) (catch Exception e (or (ex-data e) (throw e))))))) @@ -797,7 +813,9 @@ particular NuvlaBox release. ", with async " job-id)] (when (not= job-status 201) (throw (r/ex-response "unable to create async job to update NuvlaBox" 500 id))) - (event-utils/create-event id job-msg acl) + (ectx/add-linked-identifier job-id) + ;; Legacy event + ;; (event-utils/create-event id job-msg acl) (r/map-response job-msg 202 id job-id)) (catch Exception e (or (ex-data e) (throw e))))))) @@ -1074,6 +1092,106 @@ particular NuvlaBox release. (utils/can-unsuspend? resource) (conj unsuspend-op) ))))) + +;; +;; Events +;; + +(defmethod ec/events-enabled? resource-type + [_resource-type] + true) + + +(defmethod ec/log-event? "nuvlabox.check-api" + [_event _response] + false) + + +(defmethod ec/log-event? "nuvlabox.assemble-playbooks" + [_event _response] + false) + + +(defmethod ec/event-description "nuvlabox.activate" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " activated nuvlabox.")) + "Nuvlabox activation attempt failed.")) + + +(defmethod ec/event-description "nuvlabox.commission" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " commissioned nuvlabox.")) + "Nuvlabox commissioning attempt failed.")) + + +(defmethod ec/event-description "nuvlabox.decommission" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " decommissioned nuvlabox.")) + "Nuvlabox decommission attempt failed.")) + + +(defmethod ec/event-description "nuvlabox.reboot" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " rebooted nuvlabox.")) + "Nuvlabox reboot attempt failed.")) + + +(defmethod ec/event-description "nuvlabox.add-ssh-key" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " added ssh key to nuvlabox.")) + "Nuvlabox ssh key addition attempt failed.")) + + +(defmethod ec/event-description "nuvlabox.revoke-ssh-key" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " revoked ssh key from nuvlabox.")) + "Nuvlabox commission attempt failed.")) + + +(defmethod ec/event-description "nuvlabox.update-nuvlabox" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " updated nuvlabox.")) + "Nuvlabox update attempt failed.")) + + +(defmethod ec/event-description "nuvlabox.enable-host-level-management" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " enabled host-level management on nuvlabox.")) + "Nuvlabox host-level management enabling attempt failed.")) + + +(defmethod ec/event-description "nuvlabox.disable-host-level-management" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " disabled host-level management on nuvlabox.")) + "Nuvlabox host-level management disabling attempt failed.")) + + +(defmethod ec/event-description "nuvlabox.unsuspend" + [{:keys [success] {:keys [user-id]} :authn-info :as _event} & _] + (if success + (when-let [user-name (or (some-> user-id crud/retrieve-by-id-as-admin1 :name) user-id)] + (str user-name " unsuspended nuvlabox.")) + "Nuvlabox unsuspend attempt failed.")) + + ;; ;; initialization ;; diff --git a/code/test/sixsq/nuvla/server/resources/nuvlabox_0_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/nuvlabox_0_lifecycle_test.clj index a1a086619..cba6289ed 100644 --- a/code/test/sixsq/nuvla/server/resources/nuvlabox_0_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/nuvlabox_0_lifecycle_test.clj @@ -81,6 +81,8 @@ :owners ["group/nuvla-admin"] }}) +(def admin-group-name "Nuvla Administrator Group") + (deftest check-metadata (mdtu/check-metadata-exists nb/resource-type @@ -93,7 +95,10 @@ session (content-type "application/json")) - session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon")] + session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") + authn-info {:user-id "user/alpha" + :active-claim "user/alpha" + :claims ["group/nuvla-anon" "user/alpha" "group/nuvla-user"]}] (let [nuvlabox-id (-> session-owner (request base-uri @@ -104,6 +109,14 @@ (ltu/location)) nuvlabox-url (str p/service-context nuvlabox-id) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.add" + :description (str "user/alpha added nuvlabox " nuvlabox-id ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) {:keys [id acl owner]} (-> session-owner (request nuvlabox-url) (ltu/body->edn) @@ -139,10 +152,28 @@ (ltu/is-key-value :edit-acl :acl (conj (:edit-acl acl) new-owner)) (ltu/body))) + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.edit" + :description "user/alpha edited nuvlabox name NB changed." + :category "edit" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha" "user/beta"]}}) + (-> session-owner (request nuvlabox-url :request-method :delete) - (ltu/is-status 200))) + (ltu/is-status 200)) + + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.delete" + :description "user/alpha deleted nuvlabox name NB changed." + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha" "user/beta"]}})) ;; create nuvlabox with inexistent vpn id will fail (-> session-owner @@ -157,15 +188,26 @@ (deftest create-activate-decommission-delete-lifecycle (binding [config-nuvla/*stripe-api-key* nil] - (let [session (-> (ltu/ring-app) - session - (content-type "application/json")) - session-admin (header session authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") - - session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") - session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon")] - - (doseq [session [session-admin session-owner]] + (let [session (-> (ltu/ring-app) + session + (content-type "application/json")) + session-admin (header session authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") + + session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") + session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon") + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-owner {:user-id "user/alpha" + :active-claim "user/alpha" + :claims ["group/nuvla-anon" "user/alpha" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] + + (doseq [[session authn-info user-name-or-id] + [[session-admin authn-info-admin admin-group-name] + [session-owner authn-info-owner "user/alpha"]]] (let [nuvlabox-id (-> session (request base-uri :request-method :post @@ -174,6 +216,14 @@ (ltu/is-status 201) (ltu/location)) nuvlabox-url (str p/service-context nuvlabox-id) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.add" + :description (str user-name-or-id " added nuvlabox " nuvlabox-id ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) activate-url (-> session (request nuvlabox-url) @@ -199,6 +249,15 @@ :api-key (ltu/href->url)) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.activate" + :description "user/unknown activated nuvlabox." + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) + credential-nuvlabox (-> session-admin (request credential-url) (ltu/body->edn) @@ -258,11 +317,21 @@ (ltu/is-key-value :state "ACTIVATED") (ltu/get-op-url :decommission))] - (-> session - (request decommission-url - :request-method :post) - (ltu/body->edn) - (ltu/is-status 202)) + (let [job-id (-> session + (request decommission-url + :request-method :post) + (ltu/body->edn) + (ltu/is-status 202) + (ltu/location))] + + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.decommission" + :description (str user-name-or-id " decommissioned nuvlabox.") + :category "action" + :success true + :linked-identifiers [job-id] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin"]}})) ;; verify state of the resource and that ACL has been updated (let [{:keys [owner acl]} (-> session @@ -312,6 +381,15 @@ (ltu/body->edn) (ltu/is-status 200)) + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.delete" + :description (str user-name-or-id " deleted nuvlabox " nuvlabox-id ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin"]}}) + ;; verify that the nuvlabox has been removed (-> session (request nuvlabox-url) @@ -321,15 +399,26 @@ (deftest create-activate-commission-decommission-error-delete-lifecycle (binding [config-nuvla/*stripe-api-key* nil] - (let [session (-> (ltu/ring-app) - session - (content-type "application/json")) - session-admin (header session authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") - - session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") - session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon")] - - (doseq [session [session-admin session-owner]] + (let [session (-> (ltu/ring-app) + session + (content-type "application/json")) + session-admin (header session authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") + + session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") + session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon") + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-owner {:user-id "user/alpha" + :active-claim "user/alpha" + :claims ["group/nuvla-anon" "user/alpha" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] + + (doseq [[session authn-info user-name-or-id] + [[session-admin authn-info-admin admin-group-name] + [session-owner authn-info-owner "user/alpha"]]] (let [nuvlabox-id (-> session (request base-uri :request-method :post @@ -339,6 +428,15 @@ (ltu/location)) nuvlabox-url (str p/service-context nuvlabox-id) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.add" + :description (str user-name-or-id " added nuvlabox " nuvlabox-id ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) + activate-url (-> session (request nuvlabox-url) (ltu/body->edn) @@ -362,6 +460,15 @@ :api-key (ltu/href->url)) + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.activate" + :description "user/unknown activated nuvlabox." + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) + (let [{isg-id :id} (-> session-admin (content-type "application/x-www-form-urlencoded") (request isg-collection-uri @@ -401,6 +508,15 @@ (ltu/body->edn) (ltu/is-status 200)) + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.commission" + :description (str user-name-or-id " commissioned nuvlabox.") + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) + ;; verify state of the resource (-> session (request nuvlabox-url) @@ -556,7 +672,34 @@ (ltu/body->edn) (ltu/is-status 201) (ltu/body) - :resource-id)] + :resource-id) + + action (fn [action-url action-id method body event-description] + (let [job-id (-> (case method + :get + (request session action-url) + :post + (request session + action-url + :request-method :post + :body (json/write-str body)) + nil) + (ltu/body->edn) + (ltu/is-status 202) + (ltu/location))] + + (ltu/is-last-event nuvlabox-id + {:name (str "nuvlabox." action-id) + :description (str user-name-or-id " " event-description ".") + :category "action" + :success true + :linked-identifiers [job-id] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}))) + action-get (fn [action-url action-id event-description] + (action action-url action-id :get nil event-description)) + action-post (fn [action-url action-id body event-description] + (action action-url action-id :post body event-description))] ;; check-api action (-> session @@ -565,32 +708,16 @@ (ltu/is-status 202)) ;; reboot action - (-> session - (request reboot) - (ltu/body->edn) - (ltu/is-status 202)) + (action-get reboot "reboot" "rebooted nuvlabox") ;; add-ssh-key action - (-> session - (request add-ssh-key) - (ltu/body->edn) - (ltu/is-status 202)) + (action-get add-ssh-key "add-ssh-key" "added ssh key to nuvlabox") ;; revoke-ssh-key action - (-> session - (request revoke-ssh-key - :request-method :post - :body (json/write-str {:credential aux-ssh-cred})) - (ltu/body->edn) - (ltu/is-status 202)) + (action-post revoke-ssh-key "revoke-ssh-key" {:credential aux-ssh-cred} "revoked ssh key from nuvlabox") ;; update-nuvlabox-action - (-> session - (request update-nuvlabox - :request-method :post - :body (json/write-str {:nuvlabox-release nuvlabox-release})) - (ltu/body->edn) - (ltu/is-status 202))) + (action-post update-nuvlabox "update-nuvlabox" {:nuvlabox-release nuvlabox-release} "updated nuvlabox")) ;; second commissioning of the resource (with swarm credentials) (-> session diff --git a/code/test/sixsq/nuvla/server/resources/nuvlabox_1_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/nuvlabox_1_lifecycle_test.clj index b8be0e601..cf5766d64 100644 --- a/code/test/sixsq/nuvla/server/resources/nuvlabox_1_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/nuvlabox_1_lifecycle_test.clj @@ -68,6 +68,9 @@ (str nb/resource-type "-" nb-1/schema-version))) +(def admin-group-name "Nuvla Administrator Group") + + (deftest create-edit-delete-lifecycle ;; Disable stripe (binding [config-nuvla/*stripe-api-key* nil] @@ -75,7 +78,10 @@ session (content-type "application/json")) - session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon")] + session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") + authn-info {:user-id "user/alpha" + :active-claim "user/alpha" + :claims ["group/nuvla-anon" "user/alpha" "group/nuvla-user"]}] (let [nuvlabox-id (-> session-owner (request base-uri @@ -86,6 +92,14 @@ (ltu/location)) nuvlabox-url (str p/service-context nuvlabox-id) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.add" + :description (str "user/alpha added nuvlabox " nuvlabox-id ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) {:keys [id acl owner]} (-> session-owner (request nuvlabox-url) (ltu/body->edn) @@ -121,15 +135,26 @@ (deftest create-activate-decommission-delete-lifecycle (binding [config-nuvla/*stripe-api-key* nil] - (let [session (-> (ltu/ring-app) - session - (content-type "application/json")) - session-admin (header session authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") - - session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") - session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon")] - - (doseq [session [session-admin session-owner]] + (let [session (-> (ltu/ring-app) + session + (content-type "application/json")) + session-admin (header session authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") + + session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") + session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon") + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-owner {:user-id "user/alpha" + :active-claim "user/alpha" + :claims ["group/nuvla-anon" "user/alpha" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] + + (doseq [[session authn-info user-name-or-id] + [[session-admin authn-info-admin admin-group-name] + [session-owner authn-info-owner "user/alpha"]]] (let [nuvlabox-id (-> session (request base-uri :request-method :post @@ -139,6 +164,14 @@ (ltu/is-status 201) (ltu/location)) nuvlabox-url (str p/service-context nuvlabox-id) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.add" + :description (str user-name-or-id " added nuvlabox " nuvlabox-id ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) activate-url (-> session (request nuvlabox-url) @@ -164,6 +197,15 @@ :api-key (ltu/href->url)) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.activate" + :description "user/unknown activated nuvlabox." + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) + credential-nuvlabox (-> session-admin (request credential-url) (ltu/body->edn) @@ -223,11 +265,21 @@ (ltu/is-key-value :state "ACTIVATED") (ltu/get-op-url :decommission))] - (-> session - (request decommission-url - :request-method :post) - (ltu/body->edn) - (ltu/is-status 202)) + (let [job-id (-> session + (request decommission-url + :request-method :post) + (ltu/body->edn) + (ltu/is-status 202) + (ltu/location))] + + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.decommission" + :description (str user-name-or-id " decommissioned nuvlabox.") + :category "action" + :success true + :linked-identifiers [job-id] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin"]}})) ;; verify state of the resource and that ACL has been updated (let [{:keys [owner acl]} (-> session @@ -277,6 +329,15 @@ (ltu/body->edn) (ltu/is-status 200)) + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.delete" + :description (str user-name-or-id " deleted nuvlabox " nuvlabox-id ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin"]}}) + ;; verify that the nuvlabox has been removed (-> session (request nuvlabox-url) @@ -286,16 +347,27 @@ (deftest create-activate-commission-decommission-error-delete-lifecycle (binding [config-nuvla/*stripe-api-key* nil] - (let [session (-> (ltu/ring-app) - session - (content-type "application/json")) - session-admin (header session authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") - - session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") - session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon") - tags #{"tag-1", "tag-2"}] - - (doseq [session [session-admin session-owner]] + (let [session (-> (ltu/ring-app) + session + (content-type "application/json")) + session-admin (header session authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") + + session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") + session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon") + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-owner {:user-id "user/alpha" + :active-claim "user/alpha" + :claims ["group/nuvla-anon" "user/alpha" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}} + tags #{"tag-1", "tag-2"}] + + (doseq [[session authn-info user-name-or-id] + [[session-admin authn-info-admin admin-group-name] + [session-owner authn-info-owner "user/alpha"]]] (let [nuvlabox-id (-> session (request base-uri :request-method :post @@ -306,6 +378,15 @@ (ltu/location)) nuvlabox-url (str p/service-context nuvlabox-id) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.add" + :description (str user-name-or-id " added nuvlabox " nuvlabox-id ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) + activate-url (-> session (request nuvlabox-url) (ltu/body->edn) @@ -329,6 +410,15 @@ :api-key (ltu/href->url)) + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.activate" + :description "user/unknown activated nuvlabox." + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) + (let [{isg-id :id} (-> session-admin (content-type "application/x-www-form-urlencoded") (request isg-collection-uri @@ -371,6 +461,15 @@ (ltu/body->edn) (ltu/is-status 200)) + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.commission" + :description (str user-name-or-id " commissioned nuvlabox.") + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) + ;; verify state of the resource (-> session (request nuvlabox-url) diff --git a/code/test/sixsq/nuvla/server/resources/nuvlabox_2_lifecycle_test.clj b/code/test/sixsq/nuvla/server/resources/nuvlabox_2_lifecycle_test.clj index 53c2fc97e..c9d8f59b4 100644 --- a/code/test/sixsq/nuvla/server/resources/nuvlabox_2_lifecycle_test.clj +++ b/code/test/sixsq/nuvla/server/resources/nuvlabox_2_lifecycle_test.clj @@ -68,6 +68,9 @@ :capabilities ["RANDOM" "NUVLA_JOB_PULL"]}) +(def admin-group-name "Nuvla Administrator Group") + + (deftest check-metadata (mdtu/check-metadata-exists nb/resource-type (str nb/resource-type "-" nb-2/schema-version))) @@ -80,7 +83,10 @@ session (content-type "application/json")) - session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon")] + session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") + authn-info {:user-id "user/alpha" + :active-claim "user/alpha" + :claims ["group/nuvla-anon" "user/alpha" "group/nuvla-user"]}] (let [nuvlabox-id (-> session-owner (request base-uri @@ -90,6 +96,14 @@ (ltu/is-status 201) (ltu/location)) nuvlabox-url (str p/service-context nuvlabox-id) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.add" + :description (str "user/alpha added nuvlabox " nb-name ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) {:keys [id acl owner]} (-> session-owner (request nuvlabox-url) @@ -111,7 +125,16 @@ (-> session-owner (request nuvlabox-url :request-method :delete) - (ltu/is-status 200))) + (ltu/is-status 200)) + + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.delete" + :description (str "user/alpha deleted nuvlabox " nb-name ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}})) ;; create nuvlabox with inexistent vpn id will fail (-> session-owner @@ -126,14 +149,25 @@ (deftest create-activate-create-log-decommission-delete-lifecycle (binding [config-nuvla/*stripe-api-key* nil] - (let [session (-> (ltu/ring-app) - session - (content-type "application/json")) - session-admin (header session authn-info-header (str "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon " session-id)) - session-owner (header session authn-info-header (str "user/alpha user/alpha group/nuvla-user group/nuvla-anon " session-id)) - session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon")] - - (doseq [session [session-admin session-owner]] + (let [session (-> (ltu/ring-app) + session + (content-type "application/json")) + session-admin (header session authn-info-header (str "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon " session-id)) + session-owner (header session authn-info-header (str "user/alpha user/alpha group/nuvla-user group/nuvla-anon " session-id)) + session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon") + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user" session-id]} + authn-info-owner {:user-id "user/alpha" + :active-claim "user/alpha" + :claims ["group/nuvla-anon" "user/alpha" "group/nuvla-user" session-id]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}}] + + (doseq [[session authn-info user-name-or-id] + [[session-admin authn-info-admin admin-group-name] + [session-owner authn-info-owner "user/alpha"]]] (let [nuvlabox-id (-> session (request base-uri :request-method :post @@ -143,6 +177,14 @@ (ltu/is-status 201) (ltu/location)) nuvlabox-url (str p/service-context nuvlabox-id) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.add" + :description (str user-name-or-id " added nuvlabox " nb-name ".") + :category "add" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) activate-url (-> session (request nuvlabox-url) @@ -169,6 +211,14 @@ (ltu/body) :api-key (ltu/href->url)) + _ (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.activate" + :description "user/unknown activated nuvlabox." + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) credential-nuvlabox (-> session-admin (request credential-url) @@ -251,11 +301,21 @@ (ltu/is-key-value :delete :acl ["group/nuvla-admin" session-id]) (ltu/is-key-value :view-acl :acl ["group/nuvla-admin" session-id])))) - (-> session - (request decommission-url - :request-method :post) - (ltu/body->edn) - (ltu/is-status 202)) + (let [job-id (-> session + (request decommission-url + :request-method :post) + (ltu/body->edn) + (ltu/is-status 202) + (ltu/location))] + + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.decommission" + :description (str user-name-or-id " decommissioned nuvlabox.") + :category "action" + :success true + :linked-identifiers [job-id] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin"]}})) ;; verify state of the resource and that ACL has been updated (let [{:keys [owner acl]} (-> session @@ -306,6 +366,15 @@ (ltu/body->edn) (ltu/is-status 200)) + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.delete" + :description (str user-name-or-id " deleted nuvlabox " nb-name ".") + :category "delete" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin"]}}) + ;; verify that the nuvlabox has been removed (-> session (request nuvlabox-url) @@ -363,16 +432,27 @@ (deftest create-activate-commission-decommission-error-delete-lifecycle (binding [config-nuvla/*stripe-api-key* nil] - (let [session (-> (ltu/ring-app) - session - (content-type "application/json")) - session-admin (header session authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") - - session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") - session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon") - tags #{"tag-1", "tag-2"}] - - (doseq [session [session-admin session-owner]] + (let [session (-> (ltu/ring-app) + session + (content-type "application/json")) + session-admin (header session authn-info-header "group/nuvla-admin group/nuvla-admin group/nuvla-user group/nuvla-anon") + + session-owner (header session authn-info-header "user/alpha user/alpha group/nuvla-user group/nuvla-anon") + session-anon (header session authn-info-header "user/unknown user/unknown group/nuvla-anon") + authn-info-admin {:user-id "group/nuvla-admin" + :active-claim "group/nuvla-admin" + :claims ["group/nuvla-admin" "group/nuvla-anon" "group/nuvla-user"]} + authn-info-owner {:user-id "user/alpha" + :active-claim "user/alpha" + :claims ["group/nuvla-anon" "user/alpha" "group/nuvla-user"]} + authn-info-anon {:user-id "user/unknown" + :active-claim "user/unknown" + :claims #{"user/unknown" "group/nuvla-anon"}} + tags #{"tag-1", "tag-2"}] + + (doseq [[session authn-info user-name-or-id] + [[session-admin authn-info-admin admin-group-name] + [session-owner authn-info-owner "user/alpha"]]] (let [nuvlabox-id (-> session (request base-uri :request-method :post @@ -406,6 +486,15 @@ :api-key (ltu/href->url)) + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.activate" + :description "user/unknown activated nuvlabox." + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info-anon + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) + (let [{isg-id :id} (-> session-admin (content-type "application/x-www-form-urlencoded") (request isg-collection-uri @@ -448,6 +537,15 @@ (ltu/body->edn) (ltu/is-status 200)) + (ltu/is-last-event nuvlabox-id + {:name "nuvlabox.commission" + :description (str user-name-or-id " commissioned nuvlabox.") + :category "action" + :success true + :linked-identifiers [] + :authn-info authn-info + :acl {:owners ["group/nuvla-admin" "user/alpha"]}}) + ;; verify state of the resource (-> session (request nuvlabox-url)