Repository: CSCfi/rems
Pull request #2858 (2022-03-15 10:51:27)

API support for any userid (internal or external)
Implements #2772 and #2785. On top of #2834.

Additional PR for rename user command to be based on this, and idea task for #2860.

# Checklist for author

Remove items that aren't applicable, check items that are done.

## Reviewability
- [x] Link to issue
- [x] Note if PR is on top of other PR

## Backwards compatibility
- [x] API is backwards compatible or completely new
- [x] Events are backwards compatible _or_ have migrations
- [x] Config is backwards compatible

## Documentation
- [x] Update changelog if necessary
- [x] API is documented and shows up in Swagger UI
- [x] Update docs/ (if applicable)
- [ ] Update manual/ (if applicable)
- [x] ADR for major architectural decisions or experiments
- [ ] New config options in config-defaults.edn

## Testing
- [x] Complex logic is unit tested
- [x] Valuable features are integration / browser / acceptance tested automatically

## Follow-up
- [x] New tasks are created for pending or remaining tasks



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a17b7dcd1..65f41eaf6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,12 +8,19 @@ have notable changes.
 
 Changes since v2.25
 
+NB: The login has changed to allow more configurable user identity and other attributes. Consider this a big change worth some manual testing to see that everything works.
+
 ### Breaking changes
+- The actor of the command API (for applications) is now always validated. Previously, there was a chance that a non-existent user could be sent (used mostly by our internal tests). (#2771, #2824, #2772, #2821)
 - User attributes are not saved on every request, only when logging in. (#2829)
 - The `:oidc-userid-attribute` config has been renamed to `:oidc-userid-attributes` and has new options to allow internally renaming an attribute from IdP to REMS db. (#2771, #2821)
 
 ### Additions
 - You can configure the OIDC attributes for name and email (see configuration.md)
+- User in the API can be an internal REMS id or any of the `:oidc-userid-attributes` (provided that the user has logged in once and we have stored the identity. (#2821 #2772)
+
+### Fixes
+- API-key validity is not checked unless it is actually sent. (#2785)
 - API-Keys are cached for a minute for a slight performance improvement. (#2785)
 
 ## v2.25 "Meripuistotie" 2022-02-15
diff --git a/docs/using-the-api.md b/docs/using-the-api.md
index 4751e9211..ae8f258bd 100644
--- a/docs/using-the-api.md
+++ b/docs/using-the-api.md
@@ -26,6 +26,9 @@ You can also authenticate by setting these two headers:
 - `x-rems-api-key` -- an API key
 - `x-rems-user-id` -- the user id of a user to impersonate
 
+NB: For the user id it can be an internal REMS id or an external id configured in `config.edn` through `oidc-userid-attributes`.
+If the user can't be found, an error will be returned.
+
 API keys can be defined and modified using the `api-key` command line option to REMS. NB: API-Keys are cached for a minute.
 
 Here are some examples:
diff --git a/src/clj/rems/api.clj b/src/clj/rems/api.clj
index a4b1b5c9f..11be70a21 100644
--- a/src/clj/rems/api.clj
+++ b/src/clj/rems/api.clj
@@ -184,7 +184,7 @@
     (context "/api" []
       ;; :middleware [slow-middleware]
       :header-params [{x-rems-api-key :- (describe s/Str "REMS API-Key (optional for UI, required for API)") nil}
-                      {x-rems-user-id :- (describe s/Str "user id (optional for UI, required for API)") nil}]
+                      {x-rems-user-id :- (describe s/Str "user (optional for UI, required for API). This can be a REMS internal or an external user identity attribute (specified in config.edn).") nil}]
 
       public/translations-api
       public/theme-api
diff --git a/src/clj/rems/api/blacklist.clj b/src/clj/rems/api/blacklist.clj
index 3a39c4ad6..01007350e 100644
--- a/src/clj/rems/api/blacklist.clj
+++ b/src/clj/rems/api/blacklist.clj
@@ -8,8 +8,9 @@
             [rems.application.rejecter-bot :as rejecter-bot]
             [rems.common.roles :refer [+admin-read-roles+]]
             [rems.db.users :as users]
+            [rems.db.user-mappings :as user-mappings]
             [rems.schema-base :as schema-base]
-            [rems.util :refer [getx-user-id]]
+            [rems.util :refer [getx-in getx-user-id]]
             [ring.util.http-response :refer [ok]]
             [schema.core :as s])
   (:import [org.joda.time DateTime]))
@@ -25,13 +26,6 @@
          :blacklist/added-by schema-base/UserWithAttributes
          :blacklist/added-at DateTime))
 
-(defn- format-blacklist-entry [entry]
-  {:blacklist/resource {:resource/ext-id (:resource/ext-id entry)}
-   :blacklist/user (users/get-user (:userid entry))
-   :blacklist/added-at (:event/time entry)
-   :blacklist/comment (:event/comment entry)
-   :blacklist/added-by (users/get-user (:event/actor entry))})
-
 (def blacklist-api
   (context "/blacklist" []
     :tags ["blacklist"]
@@ -42,10 +36,8 @@
       :query-params [{user :- schema-base/UserId nil}
                      {resource :- s/Str nil}]
       :return [BlacklistEntryWithDetails]
-      (->> (blacklist/get-blacklist {:userid user
-                                     :resource/ext-id resource})
-           (mapv format-blacklist-entry)
-           (ok)))
+      (ok (blacklist/get-blacklist {:userid (user-mappings/find-userid user)
+                                    :resource/ext-id resource})))
 
     (GET "/users" []
       :summary "Existing REMS users available for adding to the blacklist"
@@ -60,11 +52,13 @@
       :roles #{:owner :handler}
       :body [command BlacklistCommand]
       :return schema/SuccessResponse
-      (blacklist/add-user-to-blacklist! (getx-user-id) command)
-      (doseq [cmd (rejecter-bot/reject-all-applications-by (get-in command [:blacklist/user :userid]))]
-        (let [result (command/command! cmd)]
-          (when (:errors result)
-            (log/error "Failure when running rejecter bot commands:" {:cmd cmd :result result}))))
+      (let [userid (user-mappings/find-userid (getx-in command [:blacklist/user :userid]))
+            command (assoc-in command [:blacklist/user :userid] userid)]
+        (blacklist/add-user-to-blacklist! (getx-user-id) command)
+        (doseq [cmd (rejecter-bot/reject-all-applications-by userid)]
+          (let [result (command/command! cmd)]
+            (when (:errors result)
+              (log/error "Failure when running rejecter bot commands:" {:cmd cmd :result result})))))
 
       (ok {:success true}))
 
@@ -73,5 +67,7 @@
       :roles #{:owner :handler}
       :body [command BlacklistCommand]
       :return schema/SuccessResponse
-      (blacklist/remove-user-from-blacklist! (getx-user-id) command)
-      (ok {:success true}))))
+      (let [userid (user-mappings/find-userid (getx-in command [:blacklist/user :userid]))
+            command (assoc-in command [:blacklist/user :userid] userid)]
+        (blacklist/remove-user-from-blacklist! (getx-user-id) command)
+        (ok {:success true})))))
diff --git a/src/clj/rems/api/services/blacklist.clj b/src/clj/rems/api/services/blacklist.clj
index 9602ddc7e..d125d37f1 100644
--- a/src/clj/rems/api/services/blacklist.clj
+++ b/src/clj/rems/api/services/blacklist.clj
@@ -1,7 +1,8 @@
 (ns rems.api.services.blacklist
   (:require [clj-time.core :as time]
             [rems.db.applications :as applications]
-            [rems.db.blacklist :as blacklist]))
+            [rems.db.blacklist :as blacklist]
+            [rems.db.users :as users]))
 
 (defn- command->event [command actor]
   {:event/actor actor
@@ -20,4 +21,13 @@
                             (assoc :event/type :blacklist.event/remove)))
   (applications/reload-cache!))
 
-(defn get-blacklist [params] (blacklist/get-blacklist params))
+(defn- format-blacklist-entry [entry]
+  {:blacklist/resource {:resource/ext-id (:resource/ext-id entry)}
+   :blacklist/user (users/get-user (:userid entry))
+   :blacklist/added-at (:event/time entry)
+   :blacklist/comment (:event/comment entry)
+   :blacklist/added-by (users/get-user (:event/actor entry))})
+
+(defn get-blacklist [params]
+  (->> (blacklist/get-blacklist params)
+       (mapv format-blacklist-entry)))
diff --git a/src/clj/rems/api/services/command.clj b/src/clj/rems/api/services/command.clj
index 16ade1a9b..bab45d611 100644
--- a/src/clj/rems/api/services/command.clj
+++ b/src/clj/rems/api/services/command.clj
@@ -12,6 +12,8 @@
             [rems.db.core :as db]
             [rems.db.events :as events]
             [rems.db.entitlements :as entitlements]
+            [rems.db.users :as users]
+            [rems.db.user-mappings :as user-mappings]
             [rems.email.core :as email]
             [rems.event-notification :as event-notification]
             [rems.util :refer [secure-token]])
@@ -52,7 +54,9 @@
   (merge applications/fetcher-injections
          {:secure-token secure-token
           :allocate-application-ids! applications/allocate-application-ids!
-          :copy-attachment! attachments/copy-attachment!}))
+          :copy-attachment! attachments/copy-attachment!
+          :valid-user? users/user-exists?
+          :find-userid user-mappings/find-userid}))
 
 (def ^:dynamic *fail-on-process-manager-errors* false)
 
diff --git a/src/clj/rems/application/commands.clj b/src/clj/rems/application/commands.clj
index 5dfa17fe0..7e011477b 100644
--- a/src/clj/rems/application/commands.clj
+++ b/src/clj/rems/application/commands.clj
@@ -1,10 +1,8 @@
 (ns rems.application.commands
   (:require [clojure.test :refer [deftest is testing]]
-            [com.rpl.specter :refer [ALL transform]]
             [medley.core :refer [assoc-some]]
             [rems.common.application-util :as application-util]
             [rems.common.form :as form]
-            [rems.common.util :refer [build-index]]
             [rems.form-validation :as form-validation]
             [rems.permissions :as permissions]
             [rems.schema-base :as schema-base]
@@ -661,10 +659,26 @@
   (when (and (:application-id cmd) (not application))
     {:errors [{:type :application-not-found}]}))
 
+(defn- find-users [cmd injections]
+  ;; actor is handled already in middleware
+  (case (:type cmd)
+    (:application.command/add-member :application.command/remove-member :application.command/change-applicant)
+    (update-in cmd [:member :userid] (getx injections :find-userid))
+
+    :application.command/request-review
+    (update cmd :reviewers #(mapv (getx injections :find-userid) %))
+
+    :application.command/request-decision
+    (update cmd :deciders #(mapv (getx injections :find-userid) %))
+
+    cmd))
+
 (defn handle-command [cmd application injections]
   (validate-command cmd) ; this is here mostly for tests, commands via the api are validated by compojure-api
-  (or (application-not-found-error application cmd)
+  (or (invalid-user-error (:actor cmd) injections)
+      (application-not-found-error application cmd)
       (let [result (-> cmd
+                       (find-users injections)
                        (command-handler application injections)
                        (finalize-events cmd))]
         (or (when (:errors result) result) ;; prefer more specific errors
diff --git a/src/clj/rems/auth/auth.clj b/src/clj/rems/auth/auth.clj
index 739717599..3efc85b6a 100644
--- a/src/clj/rems/auth/auth.clj
+++ b/src/clj/rems/auth/auth.clj
@@ -8,13 +8,16 @@
             [rems.config :refer [env]]
             [rems.db.api-key :as api-key]
             [rems.db.users :as users]
+            [rems.db.user-mappings :as user-mappings]
             [ring.util.response :refer [redirect]]))
 
 (defn get-api-key [request]
   (get-in request [:headers "x-rems-api-key"]))
 
 (defn get-api-user [request]
-  (get-in request [:headers "x-rems-user-id"]))
+  (-> request
+      (get-in [:headers "x-rems-user-id"])
+      user-mappings/find-userid))
 
 (defn- api-key-backend []
   (reify
@@ -33,10 +36,15 @@
 
 (defn- wrap-uses-valid-api-key [handler]
   (fn [request]
-    (handler (assoc request :uses-valid-api-key? (api-key/valid? (get-api-key request)
-                                                                 (get-api-user request)
-                                                                 (:request-method request)
-                                                                 (:uri request))))))
+
+    (handler (assoc request :uses-valid-api-key?
+                    (if-some [api-key (get-api-key request)] ; don't try to check without
+
+                      (api-key/valid? api-key
+                                      (get-api-user request)
+                                      (:request-method request)
+                                      (:uri request))
+                      false)))))
 
 (defn wrap-auth [handler]
   (wrap-uses-valid-api-key
diff --git a/src/clj/rems/db/applications.clj b/src/clj/rems/db/applications.clj
index c12397007..445407859 100644
--- a/src/clj/rems/db/applications.clj
+++ b/src/clj/rems/db/applications.clj
@@ -93,8 +93,7 @@
                                                                   (blacklist/blacklisted? userid resource)))
    ;; TODO: no caching for these, but they're only used by command handlers currently
    :get-attachment-metadata attachments/get-attachment-metadata
-   :get-catalogue-item-licenses get-catalogue-item-licenses
-   :valid-user? users/user-exists?})
+   :get-catalogue-item-licenses get-catalogue-item-licenses})
 
 (defn get-application-internal
   "Returns the full application state without any user permission
diff --git a/src/clj/rems/db/user_mappings.clj b/src/clj/rems/db/user_mappings.clj
index 10f18b2f8..748d9fd69 100644
--- a/src/clj/rems/db/user_mappings.clj
+++ b/src/clj/rems/db/user_mappings.clj
@@ -1,5 +1,6 @@
 (ns rems.db.user-mappings
-  (:require [rems.db.core :as db]
+  (:require [clojure.string :as str]
+            [rems.db.core :as db]
             [schema.core :as s]))
 
 (s/defschema UserMappings
@@ -20,3 +21,14 @@
       validate-user-mapping
       db/create-user-mapping!))
 
+(defn find-userid
+  "Figures out the `userid` of a user reference.
+
+  If a user mapping is found, the corresponding `:userid` is returned.
+  Else the string is assumed to be a `userid`."
+  [userid-or-ext-id]
+  (when-not (str/blank? userid-or-ext-id)
+    (let [mappings (db/get-user-mappings {:ext-id-value userid-or-ext-id})]
+      (assert (< (count mappings) 2) (str "Multiple users found with identity " (pr-str mappings)))
+      (or (some :userid mappings)
+          userid-or-ext-id))))


Test patch:
diff --git a/src/clj/rems/db/test_data_users.clj b/src/clj/rems/db/test_data_users.clj
index 086b16ae9..aa6796059 100644
--- a/src/clj/rems/db/test_data_users.clj
+++ b/src/clj/rems/db/test_data_users.clj
@@ -30,10 +30,10 @@
 (def +fake-user-data+
   {"developer" {:eppn "developer" :mail "developer@example.com" :commonName "Developer" :nickname "The Dev"}
    "alice" {:eppn "alice" :mail "alice@example.com" :commonName "Alice Applicant" :organizations [{:organization/id "default"}] :nickname "In Wonderland" :researcher-status-by "so"}
-   "malice" {:eppn "malice" :mail "malice@example.com" :commonName "Malice Applicant" :twinOf "alice" :other "Attribute Value"}
-   "handler" {:eppn "handler" :mail "handler@example.com" :commonName "Hannah Handler"}
-   "carl" {:eppn "carl" :mail "carl@example.com" :commonName "Carl Reviewer"}
-   "elsa" {:eppn "elsa" :mail "elsa@example.com" :commonName "Elsa Roleless"}
+   "malice" {:eppn "malice" :mail "malice@example.com" :commonName "Malice Applicant" :twinOf "alice" :other "Attribute Value" :mappings {"alt-id" "malice-alt-id"}}
+   "handler" {:eppn "handler" :mail "handler@example.com" :commonName "Hannah Handler" :mappings {"alt-id" "handler-alt-id"}}
+   "carl" {:eppn "carl" :mail "carl@example.com" :commonName "Carl Reviewer" :mappings {"alt-id" "carl-alt-id"}}
+   "elsa" {:eppn "elsa" :mail "elsa@example.com" :commonName "Elsa Roleless" :mappings {"alt-id" "elsa-alt-id"}}
    "frank" {:eppn "frank" :mail "frank@example.com" :commonName "Frank Roleless" :organizations [{:organization/id "frank"}]}
    "organization-owner1" {:eppn "organization-owner1" :mail "organization-owner1@example.com" :commonName "Organization Owner 1"}
    "organization-owner2" {:eppn "organization-owner2" :mail "organization-owner2@example.com" :commonName "Organization Owner 2" :organizations [{:organization/id "organization2"}]}
diff --git a/test/clj/rems/api/test_api.clj b/test/clj/rems/api/test_api.clj
index 0c9bd73c7..2061e06d5 100644
--- a/test/clj/rems/api/test_api.clj
+++ b/test/clj/rems/api/test_api.clj
@@ -106,6 +106,7 @@
 
 (deftest test-health-api
   ;; create at least one event
+  (test-helpers/create-user! {:eppn "alice"})
   (test-helpers/create-application! {:actor "alice"})
   (let [body (-> (request :get "/api/health")
                  handler
diff --git a/test/clj/rems/api/test_applications.clj b/test/clj/rems/api/test_applications.clj
index 5d43cf7d5..aa938c82b 100644
--- a/test/clj/rems/api/test_applications.clj
+++ b/test/clj/rems/api/test_applications.clj
@@ -163,9 +163,12 @@
 
 (deftest test-application-commands
   (let [user-id "alice"
-        handler-id "developer"
+        handler-id "handler"
+        handler-alt-id "handler-alt-id"
         reviewer-id "carl"
+        reviewer-alt-id "carl-alt-id"
         decider-id "elsa"
+        decider-alt-id "elsa-alt-id"
         license-id1 (test-helpers/create-license! {})
         license-id2 (test-helpers/create-license! {})
         license-id3 (test-helpers/create-license! {})
@@ -283,12 +286,25 @@
                                 :comment "this is a remark"}))))))
 
     (testing "send command without user"
-      (is (= {:success false
-              :errors [{:type "forbidden"}]}
+      (is (= "unauthorized"
+             (send-command nil {:type :application.command/approve
+                                :application-id application-id
+                                :comment ""}))
+          "shouldn't be able to send command with nil user")
+
+      (is (= "unauthorized"
              (send-command "" {:type :application.command/approve
                                :application-id application-id
                                :comment ""}))
-          "user should be forbidden to send command"))
+          "shouldn't be able to send command with blank user"))
+
+    (testing "send command with nonexistent user"
+      (is (= {:success false
+              :errors [{:userid "does-not-exist" :type "t.form.validation/invalid-user"}]}
+             (send-command "does-not-exist" {:type :application.command/approve
+                                             :application-id application-id
+                                             :comment ""}))
+          "with a valid api-key a non-existent user is invalid"))
 
     (testing "send command with a user that is not a handler"
       (is (= {:success false
@@ -298,7 +314,7 @@
                                     :comment ""}))
           "user should be forbidden to send command"))
 
-    (testing "assing external id"
+    (testing "assign external id"
       (is (= {:success true} (send-command handler-id
                                            {:type :application.command/assign-external-id
                                             :application-id application-id
@@ -348,7 +364,34 @@
                   request-event (get-in application [:application/events eventcount])
                   review-event (get-in application [:application/events (inc eventcount)])]
               (is (= (:application/request-id request-event)
-                     (:application/request-id review-event)))))))
+                     (:application/request-id review-event)))))
+
+          (testing "requesting review with alternate id"
+            (is (= {:success true} (send-command handler-alt-id
+                                                 {:type :application.command/request-review
+                                                  :application-id application-id
+                                                  :reviewers [reviewer-alt-id]
+                                                  :comment "What say you again?"}))))
+          (testing "reviewer can now review with alternate-id"
+            (is (= {:success true} (send-command reviewer-alt-id
+                                                 {:type :application.command/review
+                                                  :application-id application-id
+                                                  :comment "Yeah, I still dunno"}))))
+          (testing "review was linked to request"
+            (let [application (get-application-for-user application-id handler-id)
+                  request-event (get-in application [:application/events eventcount])
+                  review-event (get-in application [:application/events (inc eventcount)])]
+              (is (= (:application/request-id request-event)
+                     (:application/request-id review-event)))))
+
+          (testing "non-existent user"
+            (is (= {:success false
+                    :errors [{:userid "does-not-exist" :type "t.form.validation/invalid-user"}]}
+                   (send-command handler-id
+                                 {:type :application.command/request-review
+                                  :application-id application-id
+                                  :reviewers ["does-not-exist"]
+                                  :comment "What say you?"}))))))
 
       (testing "adding and then accepting additional licenses"
         (testing "add licenses"
@@ -390,11 +433,11 @@
           (is (= #{cat-item-id2} (catalogue-item-ids-for-application application)))
           (is (= #{license-id1 license-id2} (license-ids-for-application application)))))
 
-      (testing "request-decision"
+      (testing "request-decision with alternate id"
         (is (= {:success true} (send-command handler-id
                                              {:type :application.command/request-decision
                                               :application-id application-id
-                                              :deciders [decider-id]
+                                              :deciders [decider-alt-id]
                                               :comment ""}))))
       (testing "decide"
         (is (= {:success true} (send-command decider-id
@@ -434,6 +477,8 @@
                     "application.event/submitted"
                     "application.event/review-requested"
                     "application.event/reviewed"
+                    "application.event/review-requested"
+                    "application.event/reviewed"
                     "application.event/licenses-added"
                     "application.event/licenses-accepted"
                     "application.event/resources-changed"
@@ -511,7 +556,13 @@
     (testing "creating"
       (is (some? application-id))
       (let [created (get-application-for-user application-id user-id)]
-        (is (= "application.state/draft" (get created :application/state)))))
+        (is (= "application.state/draft" (get created :application/state))))
+
+      (testing "fails with invalid user"
+        (is (= {:success false
+                :errors [{:userid "does-not-exist" :type "t.form.validation/invalid-user"}]}
+               (api-call :post "/api/applications/create" {:catalogue-item-ids [cat-id]}
+                         "42" "does-not-exist")))))
 
     (testing "seeing draft is forbidden"
       (testing "as unrelated user"
@@ -524,10 +575,16 @@
     (testing "modifying application as other user is forbidden"
       (is (= {:success false
               :errors [{:type "forbidden"}]}
-             (send-command "bob" {:type :application.command/save-draft
-                                  :application-id application-id
-                                  :field-values []}))))
+             (send-command "frank" {:type :application.command/save-draft
+                                    :application-id application-id
+                                    :field-values []}))))
     (testing "submitting"
+      (testing "fails with invalid user"
+        (is (= {:success false
+                :errors [{:userid "does-not-exist" :type "t.form.validation/invalid-user"}]}
+               (send-command "does-not-exist" {:type :application.command/submit
+                                               :application-id application-id}))))
+
       (is (= {:success true}
              (send-command user-id {:type :application.command/submit
                                     :application-id application-id})))
@@ -683,6 +740,9 @@
         applicant "alice"
         handler "developer"
         app-id (test-helpers/create-application! {:actor applicant})]
+    (test-helpers/create-user! {:eppn "member1"})
+    (test-helpers/create-user! {:eppn "reviewer1"})
+    (test-helpers/create-user! {:eppn "decider1"})
     (testing "invite member for draft as applicant"
       (is (= {:success true}
              (send-command applicant {:type :application.command/invite-member
@@ -765,7 +825,7 @@
       (is (= {:success true}
              (send-command handler {:type :application.command/add-member
                                     :application-id app-id
-                                    :member {:userid "carl"}})))
+                                    :member {:userid "carl-alt-id"}})))
       (is (= {:success true}
              (send-command handler {:type :application.command/add-member
                                     :application-id app-id
@@ -774,7 +834,7 @@
       (is (= {:success true}
              (send-command handler {:type :application.command/change-applicant
                                     :application-id app-id
-                                    :member {:userid "carl"}})))
+                                    :member {:userid "carl-alt-id"}})))
       (let [application (get-application-for-user app-id applicant)]
         (is (= "carl" (get-in application [:application/applicant :userid])))
         (is (= #{"alice" "malice"} (->> application :application/members (map :userid) set)))
@@ -795,6 +855,12 @@
              (send-command "carl" {:type :application.command/change-applicant
                                    :application-id app-id
                                    :member {:userid "malice"}}))))
+
+    (testing "member can be removed with alternate id"
+      (is (= {:success true}
+             (send-command handler {:type :application.command/remove-member
+                                    :application-id app-id
+                                    :member {:userid "malice-alt-id"}}))))
     (testing "can't promote non-member to applicant"
       (is (= {:success false :errors [{:type "user-not-member" :user {:userid "elsa"}}]}
              (send-command handler {:type :application.command/change-applicant
diff --git a/test/clj/rems/api/test_blacklist.clj b/test/clj/rems/api/test_blacklist.clj
index 2dce6ca11..f20ea4672 100644
--- a/test/clj/rems/api/test_blacklist.clj
+++ b/test/clj/rems/api/test_blacklist.clj
@@ -52,12 +52,12 @@
   (api-key/add-api-key! "42")
   (test-helpers/create-user! {:eppn +command-user+ :commonName "Owner" :mail "owner@example.com"} :owner)
   (test-helpers/create-user! {:eppn +fetch-user+} :reporter)
-  (test-helpers/create-user! {:eppn "user1" :email ""})
+  (test-helpers/create-user! {:eppn "user1" :email "" :mappings {"alt-id" "user1-alt-id"}})
   (test-helpers/create-user! {:eppn "user2" :email ""})
   (test-helpers/create-user! {:eppn "user3" :email ""})
-  (let [res-id-1 (test-helpers/create-resource! {:resource-ext-id "A"})
+  (let [_ (test-helpers/create-resource! {:resource-ext-id "A"})
         res-id-2 (test-helpers/create-resource! {:resource-ext-id "B"})
-        res-id-3 (test-helpers/create-resource! {:resource-ext-id "C"})
+        _ (test-helpers/create-resource! {:resource-ext-id "C"})
 
         cat-id (test-helpers/create-catalogue-item! {:resource-id res-id-2})
         app-id (test-helpers/create-application! {:catalogue-item-ids [cat-id]
@@ -71,7 +71,7 @@
       (add! {:blacklist/user {:userid "user1"}
              :blacklist/resource {:resource/ext-id "A"}
              :comment "bad"})
-      (add! {:blacklist/user {:userid "user1"}
+      (add! {:blacklist/user {:userid "user1-alt-id"}
              :blacklist/resource {:resource/ext-id "B"}
              :comment "quite bad"})
       (add! {:blacklist/user {:userid "user2"}
@@ -101,6 +101,13 @@
       (is (= [{:resource/ext-id "A" :userid "user1"}
               {:resource/ext-id "B" :userid "user1"}]
              (simplify (fetch {:user "user1"}))))
+      (is (= []
+             (simplify (fetch {:user "user1-alt-id-does-not-exist"})))
+          "alternate identity of user1, no mapping exists")
+      (is (= [{:resource/ext-id "A" :userid "user1"}
+              {:resource/ext-id "B" :userid "user1"}]
+             (simplify (fetch {:user "user1-alt-id"})))
+          "alternate identity of user1, from a mapping")
       (is (= [{:resource/ext-id "B" :userid "user1"}
               {:resource/ext-id "B" :userid "user2"}]
              (simplify (fetch {:resource "B"}))))
diff --git a/test/clj/rems/api/test_catalogue_items.clj b/test/clj/rems/api/test_catalogue_items.clj
index 16fedf270..ec7acf8a4 100644
--- a/test/clj/rems/api/test_catalogue_items.clj
+++ b/test/clj/rems/api/test_catalogue_items.clj
@@ -97,6 +97,7 @@
 (deftest catalogue-items-edit-test
   (let [owner "owner"
         user "alice"
+        _ (test-helpers/create-user! {:eppn "alice"})
         changed-organization1 (test-helpers/create-organization! {:organization/id "changed-organization1" :organization/owners [{:userid "organization-owner1"}]})
         changed-organization2 (test-helpers/create-organization! {:organization/id "changed-organization2" :organization/owners [{:userid "organization-owner1"}]})
         form-id (test-helpers/create-form! {:organization {:organization/id "organization1"}})
diff --git a/test/clj/rems/api/test_workflows.clj b/test/clj/rems/api/test_workflows.clj
index 3ef6f0638..082fed9ee 100644
--- a/test/clj/rems/api/test_workflows.clj
+++ b/test/clj/rems/api/test_workflows.clj
@@ -187,6 +187,7 @@
 
 (deftest workflows-edit-test
   (create-handlers!)
+  (test-helpers/create-user! {:eppn "tester"})
   (let [user-id "owner"
         wfid (test-helpers/create-workflow! {:organization {:organization/id "organization1"}
                                              :title "workflow title"
diff --git a/test/clj/rems/api/testing.clj b/test/clj/rems/api/testing.clj
index 5d661ea8e..088160c27 100644
--- a/test/clj/rems/api/testing.clj
+++ b/test/clj/rems/api/testing.clj
@@ -40,9 +40,9 @@
                   caches-fixture]))
 
 (defn authenticate [request api-key user-id]
-  (-> request
-      (assoc-in [:headers "x-rems-api-key"] api-key)
-      (assoc-in [:headers "x-rems-user-id"] user-id)))
+  (cond-> request
+    api-key (assoc-in [:headers "x-rems-api-key"] api-key)
+    user-id (assoc-in [:headers "x-rems-user-id"] user-id)))
 
 (defn assert-schema-errors
   "Try more advanced parsing for nicer schema errors."
diff --git a/test/clj/rems/application/test_commands.clj b/test/clj/rems/application/test_commands.clj
index c7fb47ee7..803f04eea 100644
--- a/test/clj/rems/application/test_commands.clj
+++ b/test/clj/rems/application/test_commands.clj
@@ -129,6 +129,8 @@
 (defn- dummy-get-config []
   {})
 
+(def ^:private dummy-valid-user? #{applicant-user-id handler-user-id "somebody" "somebody2" "member1" "member2"})
+
 (def allocated-new-ids? (atom false))
 (def ^:private injections
   {:blacklisted? (constantly false)
@@ -146,7 +148,9 @@
                                 {:application/id new-app-id
                                  :application/external-id new-external-id})
    :copy-attachment! (fn [_new-app-id attachment-id]
-                       (+ attachment-id 100))})
+                       (+ attachment-id 100))
+   :valid-user? dummy-valid-user?
+   :find-userid identity})
 
 ;; could rework tests to use model/build-application-view instead of this
 (defn apply-events [application events]
@@ -163,27 +167,23 @@
     (assoc :application-id app-id)))
 
 (defn- fail-command
-  ([application cmd]
-   (fail-command application cmd nil))
-  ([application cmd injections]
-   (let [cmd (set-command-defaults cmd)
-         result (commands/handle-command cmd application injections)]
-     (assert-ex (:errors result) {:cmd cmd :result result})
-     result)))
+  [application cmd injections]
+  (let [cmd (set-command-defaults cmd)
+        result (commands/handle-command cmd application injections)]
+    (assert-ex (:errors result) {:cmd cmd :result result})
+    result))
 
 (defn- ok-command
-  ([application cmd]
-   (ok-command application cmd nil))
-  ([application cmd injections]
-   (let [cmd (set-command-defaults cmd)
-         result (commands/handle-command cmd application injections)]
-     (assert-ex (not (:errors result)) {:cmd cmd :result result})
-     (let [events (:events result)]
-       (events/validate-events events)
-       ;; most tests expect only one event, so this avoids having to wrap the expectation to a list
-       (if (= 1 (count events))
-         (first events)
-         events)))))
+  [application cmd injections]
+  (let [cmd (set-command-defaults cmd)
+        result (commands/handle-command cmd application injections)]
+    (assert-ex (not (:errors result)) {:cmd cmd :result result})
+    (let [events (:events result)]
+      (events/validate-events events)
+      ;; most tests expect only one event, so this avoids having to wrap the expectation to a list
+      (if (= 1 (count events))
+        (first events)
+        events))))
 
 (defn- apply-command
   ([application cmd]
@@ -254,6 +254,13 @@
                             :catalogue-item-ids [6]}
                        injections))))
 
+  (testing "error: invalid actor"
+    (is (= {:errors [{:userid "does-not-exist", :type :t.form.validation/invalid-user}]}
+           (fail-command nil {:type :application.command/create
+                              :actor "does-not-exist"
+                              :catalogue-item-ids [1]}
+                         injections))))
+
   (testing "error: zero catalogue items"
     (is (= {:errors [{:type :must-not-be-empty
                       :key :catalogue-item-ids}]}
@@ -352,7 +359,8 @@
                          {:type :application.command/save-draft
                           :actor applicant-user-id
                           :field-values [{:form 1 :field "1" :value "foo"}
-                                         {:form 1 :field "2" :value "bar"}]}))))
+                                         {:form 1 :field "2" :value "bar"}]}
+                         injections))))
     (testing "saves a draft"
       (is (= {:event/type :application.event/draft-saved
               :event/time test-time
@@ -364,14 +372,16 @@
                          {:type :application.command/save-draft
                           :actor applicant-user-id
                           :field-values [{:form 1 :field "1" :value "foo"}
-                                         {:form 1 :field "2" :value "bar"}]}))))
+                                         {:form 1 :field "2" :value "bar"}]}
+                         injections))))
 
     (testing "does not save a draft when validations fail"
       (is (= {:errors [{:field-id "1", :type :t.form.validation/invalid-value :form-id 1}]}
              (fail-command application
                            {:type :application.command/save-draft
                             :actor applicant-user-id
-                            :field-values [{:form 1 :field "1" :value "nonexistent_option"}]}))))
+                            :field-values [{:form 1 :field "1" :value "nonexistent_option"}]}
+                           injections))))
 
     (testing "validation of conditional fields"
       (let [created (assoc dummy-created-event :application/forms [{:form/id 7}])
@@ -381,14 +391,16 @@
                              {:type :application.command/save-draft
                               :actor applicant-user-id
                               :field-values [{:form 7 :field "7" :value "y"}
-                                             {:form 7 :field "8" :value "invalid_email"}]}))
+                                             {:form 7 :field "8" :value "invalid_email"}]}
+                             injections))
             "visible field should not accept invalid values")
         (is (= {:errors [{:form-id 7 :field-id "8" :type :t.form.validation/invalid-email}]}
                (fail-command application
                              {:type :application.command/save-draft
                               :actor applicant-user-id
                               :field-values [{:form 7 :field "7" :value "n"}
-                                             {:form 7 :field "8" :value "invalid_email"}]}))
+                                             {:form 7 :field "8" :value "invalid_email"}]}
+                             injections))
             "invisible should not accept invalid values")
         (is (= {:event/type :application.event/draft-saved
                 :event/time test-time
@@ -400,7 +412,8 @@
                            {:type :application.command/save-draft
                             :actor applicant-user-id
                             :field-values [{:form 7 :field "7" :value "y"}
-                                           {:form 7 :field "8" :value "valid@example.com"}]}))
+                                           {:form 7 :field "8" :value "valid@example.com"}]}
+                           injections))
             "answers to visible fields should get stored")
         (is (= {:event/type :application.event/draft-saved
                 :event/time test-time
@@ -411,21 +424,24 @@
                            {:type :application.command/save-draft
                             :actor applicant-user-id
                             :field-values [{:form 7 :field "7" :value "n"}
-                                           {:form 7 :field "8" :value "valid@example.com"}]}))
+                                           {:form 7 :field "8" :value "valid@example.com"}]}
+                           injections))
             "answers to invisible fields should get dropped")))
 
     (testing "only the applicant can save a draft"
       (is (= {:errors [{:type :forbidden}]}
              (fail-command application
                            {:type :application.command/save-draft
-                            :actor "non-applicant"
+                            :actor "somebody"
                             :field-values [{:form 1 :field "1" :value "foo"}
-                                           {:form 1 :field "2" :value "bar"}]})
+                                           {:form 1 :field "2" :value "bar"}]}
+                           injections)
              (fail-command application
                            {:type :application.command/save-draft
                             :actor handler-user-id
                             :field-values [{:form 1 :field "1" :value "foo"}
-                                           {:form 1 :field "2" :value "bar"}]}))))
+                                           {:form 1 :field "2" :value "bar"}]}
+                           injections))))
     (testing "draft cannot be updated after submitting"
       (let [application (apply-events application
                                       [{:event/type :application.event/submitted
@@ -436,7 +452,8 @@
                (fail-command application
                              {:type :application.command/save-draft
                               :actor applicant-user-id
-                              :field-values [{:form 1 :field "1" :value "bar"}]})))))
+                              :field-values [{:form 1 :field "1" :value "bar"}]}
+                             injections)))))
     (testing "draft can be updated after returning it to applicant"
       (let [application (apply-events application
                                       [{:event/type :application.event/submitted
@@ -456,7 +473,8 @@
                (ok-command application
                            {:type :application.command/save-draft
                             :actor applicant-user-id
-                            :field-values [{:form 1 :field "1" :value "bar"}]})))))))
+                            :field-values [{:form 1 :field "1" :value "bar"}]}
+                           injections)))))))
 
 (deftest test-accept-licenses
   (let [application (apply-events nil [dummy-created-event])]
@@ -468,7 +486,8 @@
            (ok-command application
                        {:type :application.command/accept-licenses
                         :actor applicant-user-id
-                        :accepted-licenses [1 2]})))))
+                        :accepted-licenses [1 2]}
+                       injections)))))
 
 (deftest test-add-licenses
   (let [application (apply-events nil [dummy-created-event
@@ -486,13 +505,15 @@
                        {:type :application.command/add-licenses
                         :actor handler-user-id
                         :comment "comment"
-                        :licenses [1 2]})))
+                        :licenses [1 2]}
+                       injections)))
     (is (= {:errors [{:type :must-not-be-empty :key :licenses}]}
            (fail-command application
                          {:type :application.command/add-licenses
                           :actor handler-user-id
                           :comment "comment"
-                          :licenses []})))))
+                          :licenses []}
+                         injections)))))
 
 (deftest test-change-resources
   (let [cat-1 1
@@ -518,6 +539,7 @@
                                              :event/actor handler-user-id
                                              :application/comment "This is good"
                                              :application/id app-id}])
+        ;; TODO: we could remove this and use the common injections for all tests in this file, same for other local injections
         injections {:get-workflow {wf-1 {:workflow {:forms [{:form/id form-3}]}}
                                    wf-2 {}}
                     :get-catalogue-item
@@ -529,7 +551,8 @@
                     {cat-1 [{:id license-1}]
                      cat-2-other-license [{:id license-2}]
                      cat-3-other-workflow [{:id license-1}]
-                     cat-4-other-form [{:id license-1}]}}]
+                     cat-4-other-form [{:id license-1}]}
+                    :valid-user? (:valid-user? injections)}]
 
     (testing "applicant can add resources to a draft application"
       (is (= {:event/type :application.event/resources-changed
@@ -701,10 +724,12 @@
              (fail-command application
                            {:type :application.command/change-resources
                             :actor applicant-user-id
-                            :catalogue-item-ids []}))))))
+                            :catalogue-item-ids []}
+                           injections))))))
 
 (deftest test-submit
-  (let [injections {:get-form-template dummy-get-form-template}
+  (let [injections {:valid-user? #{applicant-user-id "non-applicant"}
+                    :get-form-template dummy-get-form-template}
         created-event {:event/type :application.event/created
                        :event/time test-time
                        :event/actor applicant-user-id
@@ -840,7 +865,8 @@
           returned-event (ok-command application
                                      {:type :application.command/return
                                       :actor handler-user-id
-                                      :comment "ret"})
+                                      :comment "ret"}
+                                     injections)
           submit-command {:type :application.command/submit
                           :actor applicant-user-id}]
       (is (= {:event/type :application.event/returned
@@ -855,14 +881,14 @@
                   :event/time test-time
                   :event/actor applicant-user-id
                   :application/id app-id}
-                 (ok-command returned submit-command)))
+                 (ok-command returned submit-command injections)))
           (testing "succeeds even when catalogue item is disabled"
             (let [disabled (assoc-in returned [:application/resources 0 :catalogue-item/enabled] false)]
               (is (= {:event/type :application.event/submitted
                       :event/time test-time
                       :event/actor applicant-user-id
                       :application/id app-id}
-                     (ok-command disabled submit-command))))))))))
+                     (ok-command disabled submit-command injections))))))))))
 
 
 (deftest test-assign-external-id
@@ -881,13 +907,15 @@
              (ok-command application
                          {:type :application.command/assign-external-id
                           :actor handler-user-id
-                          :external-id "ext123"}))))
+                          :external-id "ext123"}
+                         injections))))
     (testing "applicant can't assign id"
       (is (= {:errors [{:type :forbidden}]}
              (fail-command application
                            {:type :application.command/assign-external-id
                             :actor applicant-user-id
-                            :external-id "ext123"}))))))
+                            :external-id "ext123"}
+                           injections))))))
 
 (deftest test-approve-or-reject
   (let [application (apply-events nil
@@ -905,7 +933,8 @@
              (ok-command application
                          {:type :application.command/approve
                           :actor handler-user-id
-                          :comment "fine"}))))
+                          :comment "fine"}
+                         injections))))
     (testing "approved with end-date"
       (is (= {:event/type :application.event/approved
               :event/time test-time
@@ -919,7 +948,8 @@
                              {:type :application.command/approve
                               :actor handler-user-id
                               :entitlement-end (time/plus (DateTime. 1234) (time/days 1))
-                              :comment "fine"}))))))
+                              :comment "fine"}
+                             injections))))))
     (testing "rejected successfully"
       (is (= {:event/type :application.event/rejected
               :application/comment "bad"
@@ -929,13 +959,15 @@
              (ok-command application
                          {:type :application.command/reject
                           :actor handler-user-id
-                          :comment "bad"}))))
+                          :comment "bad"}
+                         injections))))
     (testing "throws with past entitlement end-date"
       (is (= {:errors [{:type :t.actions.errors/entitlement-end-not-in-future}]}
              (fail-command application
                            {:type :application.command/approve
                             :actor handler-user-id
-                            :entitlement-end (DateTime. 1234)}))))))
+                            :entitlement-end (DateTime. 1234)}
+                           injections))))))
 
 (deftest test-close
   (let [application (apply-events nil
@@ -958,7 +990,8 @@
              (ok-command application
                          {:type :application.command/close
                           :actor handler-user-id
-                          :comment "outdated"})))))
+                          :comment "outdated"}
+                         injections)))))
   (let [application (apply-events nil
                                   [dummy-created-event
                                    {:event/type :application.event/submitted
@@ -979,7 +1012,8 @@
              (ok-command application
                          {:type :application.command/close
                           :actor applicant-user-id
-                          :comment "outdated"}))))
+                          :comment "outdated"}
+                         injections))))
     (testing "handler can close returned application"
       (is (= {:event/type :application.event/closed
               :event/time test-time
@@ -989,7 +1023,8 @@
              (ok-command application
                          {:type :application.command/close
                           :actor handler-user-id
-                          :comment "outdated"}))))))
+                          :comment "outdated"}
+                         injections))))))
 
 (deftest test-revoke
   (let [application (apply-events nil [dummy-created-event
@@ -1020,7 +1055,8 @@
                                     :event/time test-time
                                     :event/actor applicant-user-id
                                     :application/id app-id}])
-        injections {:valid-user? #{"deity"}}]
+        injections {:valid-user? #{handler-user-id "deity" "deity3"}
+                    :find-userid identity}]
     (testing "required :valid-user? injection"
       (is (= {:errors [{:type :missing-injection :injection :valid-user?}]}
              (fail-command application
@@ -1067,7 +1103,7 @@
         (is (= {:errors [{:type :forbidden}]}
                (fail-command requested
                              {:type :application.command/decide
-                              :actor "deity2"
+                              :actor "deity3"
                               :decision :approved
                               :comment ""}
                              injections))))
@@ -1139,8 +1175,7 @@
                                     :event/time test-time
                                     :event/actor applicant-user-id
                                     :application/id app-id
-                                    :application/member {:userid "somebody"}}])
-        injections {:valid-user? #{"member1" "member2" "somebody" applicant-user-id}}]
+                                    :application/member {:userid "somebody"}}])]
     (testing "handler can add members"
       (is (= {:event/type :application.event/member-added
               :event/time test-time
@@ -1165,11 +1200,11 @@
                             :member {:userid "member2"}}
                            injections))))
     (testing "only valid users can be added"
-      (is (= {:errors [{:type :t.form.validation/invalid-user :userid "member3"}]}
+      (is (= {:errors [{:type :t.form.validation/invalid-user :userid "does-not-exist"}]}
              (fail-command application
                            {:type :application.command/add-member
                             :actor handler-user-id
-                            :member {:userid "member3"}}
+                            :member {:userid "does-not-exist"}}
                            injections))))
     (testing "added members can see the application"
       (is (-> (apply-commands application
@@ -1181,7 +1216,8 @@
 
 (deftest test-invite-member
   (let [application (apply-events nil [dummy-created-event])
-        injections {:valid-user? #{"somebody" applicant-user-id}
+        injections {:valid-user? #{"somebody" applicant-user-id handler-user-id "member1"}
+                    :find-userid identity
                     :secure-token (constantly "very-secure")}]
     (testing "applicant can invite members"
       (is (= {:event/type :application.event/member-invited
@@ -1235,7 +1271,9 @@
 
 (deftest test-invite-reviewer-decider
   (let [application (apply-events nil [dummy-created-event])
-        injections {:secure-token (constantly "very-secure")}]
+        injections {:valid-user? #{"somebody" applicant-user-id handler-user-id "member1"}
+                    :find-userid identity
+                    :secure-token (constantly "very-secure")}]
     (testing "applicant can't invite reviewer for draft"
       (is (= {:errors [{:type :forbidden}]}
              (fail-command application
@@ -1316,7 +1354,8 @@
                                          :application/id app-id
                                          :application/member {:name "Some Body" :email "somebody@applicants.com"}
                                          :invitation/token "very-secure"}])
-          injections {:valid-user? #{"somebody" "somebody2" applicant-user-id}}]
+          injections {:valid-user? #{"somebody" "somebody2" applicant-user-id}
+                      :find-userid identity}]
 
       (testing "can join draft"
         (is (= {:event/type :application.event/member-joined
@@ -1505,7 +1544,8 @@
                                     :event/actor handler-user-id
                                     :application/id app-id
                                     :application/member {:userid "somebody"}}])
-        injections {:valid-user? #{"somebody" applicant-user-id handler-user-id}}]
+        injections {:valid-user? #{"somebody" applicant-user-id handler-user-id}
+                    :find-userid identity}]
     (testing "applicant can remove members"
       (is (= {:event/type :application.event/member-removed
               :event/time test-time
@@ -1574,7 +1614,8 @@
                                     :event/time test-time
                                     :event/actor applicant-user-id
                                     :application/id app-id}])
-        injections {}]
+        injections {:valid-user? #{applicant-user-id handler-user-id}
+                    :find-userid identity}]
     (testing "uninvite member by applicant"
       (is (= {:event/type :application.event/member-uninvited
               :event/time test-time
@@ -1631,35 +1672,40 @@
                          {:type :application.command/change-applicant
                           :actor handler-user-id
                           :member {:userid "member1"}
-                          :comment ""}))))
+                          :comment ""}
+                         injections))))
     (testing "applicant can't promote"
       (is (= {:errors [{:type :forbidden}]}
              (fail-command application
                            {:type :application.command/change-applicant
                             :actor applicant-user-id
                             :member {:userid "member1"}
-                            :comment ""}))))
+                            :comment ""}
+                           injections))))
     (testing "member can't promote themself"
       (is (= {:errors [{:type :forbidden}]}
              (fail-command application
                            {:type :application.command/change-applicant
                             :actor "member1"
                             :member {:userid "member1"}
-                            :comment ""}))))
+                            :comment ""}
+                           injections))))
     (testing "handler can't promote non-member"
       (is (= {:errors [{:type :user-not-member :user {:userid "unknown"}}]}
              (fail-command application
                            {:type :application.command/change-applicant
                             :actor handler-user-id
                             :member {:userid "unknown"}
-                            :comment ""}))))
+                            :comment ""}
+                           injections))))
     (testing "handler can't promote current applicant"
       (is (= {:errors [{:type :user-not-member :user {:userid "applicant"}}]}
              (fail-command application
                            {:type :application.command/change-applicant
                             :actor handler-user-id
                             :member {:userid applicant-user-id}
-                            :comment ""}))))))
+                            :comment ""}
+                           injections))))))
 
 (deftest test-review
   (let [reviewer "reviewer"
@@ -1671,7 +1717,8 @@
                                     :event/time test-time
                                     :event/actor applicant-user-id
                                     :application/id app-id}])
-        injections {:valid-user? #{reviewer reviewer2 reviewer3}}]
+        injections {:valid-user? #{handler-user-id reviewer reviewer2 reviewer3}
+                    :find-userid identity}]
     (testing "required :valid-user? injection"
       (is (= {:errors [{:type :missing-injection :injection :valid-user?}]}
              (fail-command application
@@ -1687,7 +1734,7 @@
                             :actor handler-user-id
                             :reviewers []
                             :comment ""}
-                           {}))))
+                           injections))))
     (testing "reviewers must be a valid users"
       (is (= {:errors [{:type :t.form.validation/invalid-user :userid "invaliduser"}
                        {:type :t.form.validation/invalid-user :userid "invaliduser2"}]}
@@ -1808,7 +1855,8 @@
         wrong-application-attachment-id 1235
         wrong-user-attachment-id 1236
         unknown-attachment-id 1237
-        injections {:valid-user? #{reviewer}
+        injections {:valid-user? #{applicant-user-id handler-user-id reviewer}
+                    :find-userid identity
                     :get-attachment-metadata
                     {valid-attachment-id {:application/id (:application/id application)
                                           :attachment/id valid-attachment-id
@@ -1827,7 +1875,7 @@
                                :attachments [{:attachment/id valid-attachment-id}]
                                :public false}
                               injections)
-            application (apply-events application [event])]
+            _application (apply-events application [event])]
         (is (= {:event/type :application.event/remarked
                 :event/time test-time
                 :event/actor handler-user-id
@@ -2031,9 +2079,11 @@
         command {:application-id 123 :time (DateTime. 1000)
                  :type :application.command/save-draft
                  :field-values []
-                 :actor "applicant"}]
+                 :actor "applicant"}
+        injections {:valid-user? #{"applicant"}
+                    :find-userid identity}]
     (testing "executes command when user is authorized"
-      (is (not (:errors (commands/handle-command command application {})))))
+      (is (not (:errors (commands/handle-command command application injections)))))
     (testing "fails when command fails validation"
       (is (thrown-with-msg? ExceptionInfo #"Value does not match schema"
                             (commands/handle-command (assoc command :time 3) application {}))))
@@ -2041,5 +2091,5 @@
       ;; the permission checks should happen before executing the command handler
       ;; and only depend on the roles and permissions
       (let [application (permissions/remove-role-from-user application :applicant "applicant")
-            result (commands/handle-command command application {})]
+            result (commands/handle-command command application injections)]
         (is (= {:errors [{:type :forbidden}]} result))))))
diff --git a/test/clj/rems/db/test_applications.clj b/test/clj/rems/db/test_applications.clj
index 8521a71f0..9fbb05764 100644
--- a/test/clj/rems/db/test_applications.clj
+++ b/test/clj/rems/db/test_applications.clj
@@ -82,6 +82,7 @@
     (is (= "1981/4" (application-external-id! (DateTime. #inst "1981-04-01"))))))
 
 (deftest test-delete-application-and-reload-cache!!
+  (test-helpers/create-user! {:eppn "applicant"})
   (let [app-id (test-helpers/create-application! {:actor "applicant"})]
     (is (applications/get-application app-id))
     (applications/delete-application-and-reload-cache! app-id)