Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Anonymize handling workflow #3165

Merged
merged 27 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0942a57
feat: add workflow anonymize-handling option
aatkin Sep 19, 2023
5ec16d3
feat: anonymize handling users in personalized application
aatkin Sep 19, 2023
2c61bc2
feat: localize anonymized user in application and emails
aatkin Sep 19, 2023
dc5fc7b
test: application api with anonymized workflow
aatkin Sep 19, 2023
175b18e
test: anonymized workflow option in browser test and ui test
aatkin Sep 19, 2023
4e7817a
test: anonymize handling workflow in emails
aatkin Sep 19, 2023
414ad08
feat: add anonymize handling to returned test data application
aatkin Sep 19, 2023
2904f74
refactor: use handling roles instead of show-reviews?
aatkin Sep 19, 2023
f329066
doc: update changelog
aatkin Sep 19, 2023
e8b3934
feat: show anonymize handling only when enabled in read-only workflow
aatkin Sep 19, 2023
8e087b9
feat: rephrase anonymize handling translation
aatkin Sep 19, 2023
e415ee9
feat: da placeholder translations
aatkin Sep 20, 2023
52c3db6
fix: edit anonymize-handling only when included in request
aatkin Sep 20, 2023
9133484
feat: keep only userid, name, email for censored users
aatkin Sep 20, 2023
06a17e5
refactor: inject config in duo enrichment instead of require
aatkin Sep 20, 2023
9b99a0f
deps: update kaocha to 1.86.1355
aatkin Sep 20, 2023
4fdbdec
fix: use default value for anonymize handling edit
aatkin Sep 20, 2023
80aef69
feat: loosen schema requirements
aatkin Sep 20, 2023
9b9796b
chore: remove extra comment
aatkin Sep 20, 2023
f7e243b
chore: cljfmt
aatkin Sep 20, 2023
9dcef87
Merge remote-tracking branch 'origin/master' into 3130/anonymous-work…
aatkin Oct 12, 2023
b4fea0d
Merge remote-tracking branch 'origin/master' into 3130/anonymous-work…
aatkin Oct 12, 2023
c3276b1
fix: change old attribute name in test
aatkin Oct 13, 2023
52a3ab6
feat: add title to workflow type and re-order fields in workflow admi…
aatkin Oct 13, 2023
db07458
feat: add decider to test data application "returned"
aatkin Oct 13, 2023
08d4c04
fix: add missing functionality due to merge conflict and test it
aatkin Oct 13, 2023
a095cae
feat: add anonymized user example to component guide
aatkin Oct 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Changes since v2.33
- (Experimental) Workflow can be configured to enable voting for the approval. Currently all handlers can vote (including bots). Use `:enable-voting`. (#3174)
- There is now a Danish language translation (#3176). We are considering supporting a limited set of languages officially, and improving support for community maintained translations (see #3179).
- Added experimental support for named format parameters in translations. (#3183)
- Workflow has new option to anonymize handling users. When enabled, applying users may only see "Handler" in events where handling user's name would appear. Event emails to applying users similarly show only anonymized name where handling user's name would appear. Application API also returns only anonymized name to applying users. Anonymized name is customizable with translation key `:t.roles/anonymous-handler`, which defaults to `:t.roles/handler`.

### Fixes
- Email template parameters for `:application-expiration-notification` event are now documented. The parameters are different from standard event email parameters, which may have caused confusion.
Expand Down
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
:project/dev {:dependencies [[binaryage/devtools "1.0.6"]
[com.clojure-goes-fast/clj-memory-meter "0.2.1"]
[criterium "0.4.6"]
[lambdaisland/kaocha "1.80.1274"]
[lambdaisland/kaocha "1.86.1355"]
[lambdaisland/kaocha-junit-xml "1.17.101"]
[etaoin "1.0.39"]
[ring/ring-mock "0.4.0" :exclusions [cheshire]]
Expand Down
6 changes: 5 additions & 1 deletion resources/translations/da.edn
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
:add "Tilføj"
:added-at "Tilføjet"
:added-by "Tilføjet af"
:anonymize-handling nil
:application-state "Ansøgningsstatus"
:archive "Arkiver"
:back "Tilbage"
Expand Down Expand Up @@ -139,6 +140,7 @@
:name "Navn"
:no-categories "Ingen kategorier"
:no-form "Ingen blanket"
:no-forms nil
:no-licenses "Ingen brugsvilkår"
:no-voting nil
:optional "(valgfrit)"
Expand Down Expand Up @@ -167,6 +169,7 @@
:voting nil
:workflow "Arbejdsgang"
:workflows "Arbejdsgange"
:workflow-anonymize-handling-explanation nil
:workflow-disabled-commands-explanation [:p "Denne arbejdsgang deaktiverer funktioner for brugere når vilkår er opfyldt. Funktioner, ansøgningsstatus og brugerroller kan findes på " [:a {:href "https://github.com/CSCfi/rems/blob/master/docs/application-permissions.md" :target :_blank} "REMS Github-side."]]}
:applicant-info {:applicant "Ansøger"
:applicants "Ansøgere"
Expand Down Expand Up @@ -635,7 +638,8 @@
:settings "Indstillinger"
:warning-about-missing-email [:<> "Du er nødt til at " [:a {:href "/profile"} "tilføje en e-mailadresse til dine indstillinger"] ", så du kan modtage notifikationer om dine ansøgninger."]
:your-details "Dine brugerdetaljer"}
:roles {:applicant "Ansøger"
:roles {:anonymous-handler :da.t.roles/handler
:applicant "Ansøger"
:decider "Beslutningstager"
:handler "Behandler"
:member "Deltager"
Expand Down
6 changes: 5 additions & 1 deletion resources/translations/en.edn
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
:add "Add"
:added-at "Added at"
:added-by "Added by"
:anonymize-handling "Anonymized handling"
:application-state "Application state"
:archive "Archive"
:back "Back"
Expand Down Expand Up @@ -139,6 +140,7 @@
:name "Name"
:no-categories "No categories"
:no-form "No form"
:no-forms "No forms"
:no-licenses "No licenses"
:no-voting "No voting"
:optional "(optional)"
Expand Down Expand Up @@ -167,6 +169,7 @@
:voting "Voting"
:workflow "Workflow"
:workflows "Workflows"
:workflow-anonymize-handling-explanation "This workflow hides handling users personally identifiable information (e.g. name and email) from applying users."
:workflow-disabled-commands-explanation [:p "This workflow disables actions for users when conditions apply. Actions, application states and user roles can be found on " [:a {:href "https://github.com/CSCfi/rems/blob/master/docs/application-permissions.md" :target :_blank} "REMS Github page."]]}
:applicant-info {:applicant "Applicant"
:applicants "Applicants"
Expand Down Expand Up @@ -635,7 +638,8 @@
:settings "Settings"
:warning-about-missing-email [:<> "You will need to " [:a {:href "/profile"} "add an email address to your settings"] ", so that you can receive notifications about your applications."]
:your-details "Your user details"}
:roles {:applicant "Applicant"
:roles {:anonymous-handler :en.t.roles/handler ; use handler as default translation for workflow anonymized handlers
:applicant "Applicant"
:decider "Decider"
:handler "Handler"
:member "Member"
Expand Down
6 changes: 5 additions & 1 deletion resources/translations/fi.edn
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
:add "Lisää"
:added-at "Lisätty"
:added-by "Lisääjä"
:anonymize-handling "Anonymisoitu käsittely"
:application-state "Hakemuksen tila"
:archive "Arkistoi"
:back "Takaisin"
Expand Down Expand Up @@ -136,6 +137,7 @@
:name "Nimi"
:no-categories "Ei kategorioita"
:no-form "Ei lomaketta"
:no-forms "Ei lomakkeita"
:no-licenses "Ei lisenssejä"
:no-voting "Ei äänestystä"
:optional "(valinnainen)"
Expand Down Expand Up @@ -163,6 +165,7 @@
:voting "Äänestys"
:workflow "Työvuo"
:workflows "Työvuot"
:workflow-anonymize-handling-explanation "Tämä työvuo piilottaa käsittelijöiden henkilökohtaiset tunnistetiedot (kuten nimen ja sähköpostiosoitteen) hakijoilta."
:workflow-disabled-commands-explanation [:p "Tämä työvuo poistaa toimintoja käyttäjiltä ehtojen täyttyessä. Toiminnot, hakemusten tilat ja käyttäjien roolit löytyvät " [:a {:href "https://github.com/CSCfi/rems/blob/master/docs/application-permissions.md" :target :_blank} "REMSin Github-sivulta."]]}
:applicant-info {:applicant "Hakija"
:applicants "Hakijat"
Expand Down Expand Up @@ -586,7 +589,8 @@
:settings "Asetukset"
:warning-about-missing-email [:<> "Sinun on " [:a {:href "/profile"} "lisättävä sähköpostiosoite asetuksiisi"] ", jotta voisit vastaanottaa ilmoituksia hakemuksistasi."]
:your-details "Käyttäjätietosi"}
:roles {:applicant "Hakija"
:roles {:anonymous-handler :fi.t.roles/handler
:applicant "Hakija"
:decider "Päättäjä"
:handler "Käsittelijä"
:member "Jäsen"
Expand Down
6 changes: 5 additions & 1 deletion resources/translations/sv.edn
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
:add "Lägg till"
:added-at "Tillagd"
:added-by "Tillagd av"
:anonymize-handling "Anonymiserad hantering"
:application-state "Ansökningsstatus"
:archive "Arkivera"
:back "Tillbaka"
Expand Down Expand Up @@ -136,6 +137,7 @@
:name "Namn"
:no-categories "Inga kategorier"
:no-form "Ingen blankett"
:no-forms "Inga blanketter"
:no-licenses "Inga licenser"
:no-voting "Inga omröstning"
:optional "(valfritt)"
Expand Down Expand Up @@ -163,6 +165,7 @@
:voting "Omröstning"
:workflow "Arbetsflöde"
:workflows "Arbetsflöden"
:workflow-anonymize-handling-explanation "Detta arbetsflöde döljer handläggarens personligt identifierbar information (t.ex. namn och e-postaddress) för sökande."
:workflow-disabled-commands-explanation [:p "Detta arbetsflöde avaktiverar funktioner för användarna när villkoren uppfylls. Funktioner, ansökningar statusar och användarroller hittas på " [:a {:href "https://github.com/CSCfi/rems/blob/master/docs/application-permissions.md" :target :_blank} "REMS Github-sidan."]]}
:applicant-info {:applicant "Sökande"
:applicants "Sökande"
Expand Down Expand Up @@ -586,7 +589,8 @@
:settings "Inställningar"
:warning-about-missing-email [:<> "Du måste " [:a {:href "/profile"} "lägga till din e-postadress i dina inställningar"] ", för att kunna ta emot meddelanden om din ansökan."]
:your-details "Dina användardetaljer"} ; TODO check
:roles {:applicant "Sökande"
:roles {:anonymous-handler :sv.t.roles/handler
:applicant "Sökande"
:decider "Beslutare"
:handler "Handläggare"
:member "Deltagare"
Expand Down
3 changes: 2 additions & 1 deletion src/clj/rems/api/schema.clj
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,8 @@
:application/workflow {:workflow/id s/Int
:workflow/type s/Keyword
(s/optional-key :workflow.dynamic/handlers) [Handler]
(s/optional-key :workflow/voting) schema-base/WorkflowVoting}
(s/optional-key :workflow/voting) schema-base/WorkflowVoting
(s/optional-key :workflow/anonymize-handling) s/Bool}
:application/roles #{s/Keyword}
:application/permissions Permissions
:application/attachments [ApplicationAttachment]
Expand Down
8 changes: 5 additions & 3 deletions src/clj/rems/api/workflows.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[rems.application.events :as events]
[rems.common.roles :refer [+admin-read-roles+ +admin-write-roles+]]
[rems.schema-base :as schema-base]
[ring.util.http-response :refer :all]
[ring.util.http-response :refer [ok]]
[schema.core :as s]))

(s/defschema CreateWorkflowCommand
Expand All @@ -17,7 +17,8 @@
(s/optional-key :handlers) [schema-base/UserId]
(s/optional-key :licenses) [schema-base/LicenseId]
(s/optional-key :disable-commands) [schema-base/DisableCommandRule]
(s/optional-key :voting) (s/maybe schema-base/WorkflowVoting)})
(s/optional-key :voting) (s/maybe schema-base/WorkflowVoting)
(s/optional-key :anonymize-handling) (s/maybe s/Bool)})

(s/defschema EditWorkflowCommand
{:id s/Int
Expand All @@ -26,7 +27,8 @@
(s/optional-key :title) s/Str
(s/optional-key :handlers) [schema-base/UserId]
(s/optional-key :disable-commands) [schema-base/DisableCommandRule]
(s/optional-key :voting) (s/maybe schema-base/WorkflowVoting)})
(s/optional-key :voting) (s/maybe schema-base/WorkflowVoting)
(s/optional-key :anonymize-handling) (s/maybe s/Bool)})

(s/defschema CreateWorkflowResponse
{:success s/Bool
Expand Down
63 changes: 54 additions & 9 deletions src/clj/rems/application/model.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
(:require [clojure.set :as set]
[clojure.test :refer [deftest is testing]]
[com.rpl.specter :refer [ALL transform select]]
[medley.core :refer [assoc-some distinct-by find-first map-vals update-existing update-existing-in]]
[medley.core :refer [assoc-some distinct-by filter-vals find-first map-vals update-existing update-existing-in]]
[rems.application.events :as events]
[rems.application.master-workflow :as master-workflow]
[rems.common.application-util :as application-util]
[rems.common.form :as form]
[rems.common.roles :refer [+handling-roles+]]
[rems.common.util :refer [assoc-some-in conj-vec getx getx-in into-vec]]
[rems.permissions :as permissions]
[rems.json :as json]
Expand Down Expand Up @@ -51,8 +52,7 @@
(-> application
(assoc :application/modified (:event/time event))
(assoc ::draft-answers (:application/field-values event))
(assoc-some-in [:application/duo :duo/codes] (when (:enable-duo rems.config/env)
(:application/duo-codes event)))))
(assoc-some-in [:application/duo :duo/codes] (:application/duo-codes event))))

(defmethod application-base-view :application.event/licenses-accepted
[application event]
Expand Down Expand Up @@ -511,8 +511,8 @@
:duo/restrictions (:restrictions dataset-code)}]
nil)}))

(defn- enrich-application-duo-matches [application]
(if-not (:enable-duo rems.config/env)
(defn- enrich-application-duo-matches [application get-config]
(if-not (:enable-duo (get-config))
application
(let [duos (->> application :application/duo :duo/codes)
matches (for [resource (:application/resources application)
Expand Down Expand Up @@ -734,6 +734,13 @@
(transform [:application/attachments ALL] #(let [roles (get-attachment-redact-roles % application)]
(assoc-some % :attachment/redact-roles roles)))))

(defn- enrich-workflow-anonymize-handling [application get-workflow]
(let [workflow-id (get-in application [:application/workflow :workflow/id])
workflow (get-workflow workflow-id)
anonymize-handling (get-in workflow [:workflow :anonymize-handling])]
(-> application
(assoc-some-in [:application/workflow :workflow/anonymize-handling] anonymize-handling))))

(defn enrich-with-injections
[application {:keys [blacklisted?
get-form-template
Expand All @@ -751,7 +758,7 @@
enrich-field-visible ; uses enriched answers
set-application-description ; uses enriched answers
(update :application/resources enrich-resources get-catalogue-item)
enrich-application-duo-matches ; uses enriched resources
(enrich-application-duo-matches get-config) ; uses enriched resources
(update-existing-in [:application/duo :duo/codes] duo/enrich-duo-codes)
(enrich-workflow-licenses get-workflow)
(update :application/licenses enrich-licenses get-license)
Expand All @@ -761,6 +768,7 @@
(enrich-user-attributes get-user)
(enrich-blacklist blacklisted?) ; uses enriched users
(enrich-workflow-handlers get-workflow)
(enrich-workflow-anonymize-handling get-workflow)
(enrich-deadline get-config)
(enrich-super-users get-users-with-role)
(enrich-workflow-disable-commands get-config get-workflow)
Expand Down Expand Up @@ -820,7 +828,7 @@
(remove (comp false? :application/public)))) ; :application/public might not be set

(defn- censor-user [user]
(select-keys user [:userid :name :email :organizations :notification-email]))
(select-keys user [:userid :name :email]))

(defn- censor-users-in-event [event]
(-> event
Expand All @@ -844,12 +852,49 @@
(update :application/events (partial mapv censor-users-in-event))
(update :application/attachments (partial mapv #(update % :attachment/user censor-user)))))

(def anonymous-handler {:userid "rems-handler"
:name nil
:email nil})

(defn anonymize-users-in-attachments [attachments userids]
(vec
(for [attachment attachments
:let [userid (get-in attachment [:attachment/user :userid])]]
(if (some #{userid} userids)
(assoc attachment :attachment/user anonymous-handler)
attachment))))

(defn anonymize-users-in-events [events userids]
(vec
(for [event events
:let [userid (:event/actor event)]]
(if (some #{userid} userids)
(-> event
(assoc :event/actor (:userid anonymous-handler))
(assoc :event/actor-attributes anonymous-handler))
event))))

(defn get-handling-users [application]
(let [get-handling-roles #(clojure.set/intersection +handling-roles+ (set %))]
(set
(some->> (:application/user-roles application)
(filter-vals (comp seq get-handling-roles))
keys))))

(defn apply-workflow-anonymization [application]
(if (get-in application [:application/workflow :workflow/anonymize-handling])
(let [handling-users (get-handling-users application)]
(-> application
(update :application/attachments anonymize-users-in-attachments handling-users)
(update :application/events anonymize-users-in-events handling-users)))
application))

(defn- hide-sensitive-information [application]
(-> application
(dissoc :application/blacklist)
(update :application/events hide-sensitive-events)
(update :application/workflow dissoc :workflow.dynamic/handlers)
(update :application/workflow dissoc :workflow/voting)
apply-workflow-anonymization
(update :application/workflow dissoc :workflow.dynamic/handlers :workflow/voting :workflow/anonymize-handling)
(dissoc :application/votes)
hide-extra-user-attributes))

Expand Down
5 changes: 3 additions & 2 deletions src/clj/rems/db/test_data_helpers.clj
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
(assert (:success result) {:command command :result result})
(:id result)))

(defn create-workflow! [{:keys [actor organization title type handlers forms licenses disable-commands voting]
(defn create-workflow! [{:keys [actor organization title type handlers forms licenses disable-commands voting anonymize-handling]
:as command}]
(let [actor (or actor (create-owner!))
result (with-user actor
Expand All @@ -191,7 +191,8 @@
["developer"]))
:licenses (mapv (fn [id] {:license/id id}) licenses)
:disable-commands disable-commands
:voting voting}))]
:voting voting
:anonymize-handling anonymize-handling}))]
(assert (:success result) {:command command :result result})
(:id result)))

Expand Down
13 changes: 8 additions & 5 deletions src/clj/rems/db/workflow.clj
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,23 @@
(s/optional-key :forms) [{:form/id s/Num}]
(s/optional-key :licenses) [s/Int]
(s/optional-key :disable-commands) [schema-base/DisableCommandRule]
(s/optional-key :voting) (s/maybe schema-base/WorkflowVoting)})
(s/optional-key :voting) (s/maybe schema-base/WorkflowVoting)
(s/optional-key :anonymize-handling) s/Bool})

(def ^:private coerce-workflow-body
(coerce/coercer! WorkflowBody coerce/string-coercion-matcher))

(def ^:private validate-workflow-body
(s/validator WorkflowBody))

(defn create-workflow! [{:keys [organization type title handlers forms licenses disable-commands voting]}]
(defn create-workflow! [{:keys [organization type title handlers forms licenses disable-commands voting anonymize-handling]}]
(let [body (cond-> {:type type
:handlers handlers
:forms forms
:licenses licenses}
(seq disable-commands) (assoc :disable-commands disable-commands)
voting (assoc :voting voting))]
voting (assoc :voting voting)
anonymize-handling (assoc :anonymize-handling anonymize-handling))]
(:id (db/create-workflow! {:organization (:organization/id organization)
:title title
:workflow (json/generate-string
Expand Down Expand Up @@ -62,12 +64,13 @@
(update-existing-in [:workflow :handlers] #(map :userid %))
(update-existing-in [:workflow :licenses] #(map :license/id %))))

(defn edit-workflow! [{:keys [id organization title handlers disable-commands voting]}]
(defn edit-workflow! [{:keys [id organization title handlers disable-commands voting anonymize-handling]}]
(let [workflow (unrich-workflow (get-workflow id))
workflow-body (cond-> (:workflow workflow)
handlers (assoc :handlers handlers)
disable-commands (assoc :disable-commands disable-commands)
voting (assoc :voting voting))]
voting (assoc :voting voting)
(some? anonymize-handling) (assoc :anonymize-handling anonymize-handling))]
(db/edit-workflow! {:id (or id (:id workflow))
:title (or title (:title workflow))
:organization (or (:organization/id organization)
Expand Down
Loading