diff --git a/CHANGELOG.md b/CHANGELOG.md index e71f454c1f6..19ea0b49b6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,83 @@ +# [2022-03-18] + +## Release notes + +* Deploy Brig before Spar. (#2149) +* If you are in a federated network of backends (currently beta), you need to update all participating instances at the same time. (#2173) + +## API changes + +* The `client` JSON object now has an additional field `mls_public_keys`, containing an object mapping signature schemes to public keys, e.g. + ``` + { + ... + "mls_public_keys": { "ed25519": "GY+t1EQu0Zsm0r/zrm6zz9UpjPcAPyT5i8L1iaY3ypM=" } + ... + } + ``` + At the moment, `ed25519` is the only supported signature scheme, corresponding to MLS ciphersuite 1. + + When creating a new client with `POST /clients`, the field `mls_public_keys` can be set, and the corresponding public keys are bound to the device identity on the backend, and will be used to verify uploaded key packages with a matching signature scheme. + + When updating a client with `PUT /clients/:client`, the field `mls_public_keys` can also be set, with a similar effect. If a given signature scheme already has a public key set for that device, the request will fail. (#2147) + +* Introduce an endpoint for creating an MLS conversation (#2150) + +* The `/billing` and `/teams/.*/billing` endpoints are now available on a versioned path (e.g. `/v1/billing`) + + (#2167) + + +## Features + + +* MLS implementation progress: + + - key package refs are now mapped after being claimed (#2192) + +* 2nd factor authentication via 6 digit code, sent by email: + - for login, sent by email. The feature is disabled per default and can be enabled server or team wide. (#2142) + - for "create SCIM token". The feature is disabled per default and can be enabled server or team wide. (#2149) + - for "add new client" via 6 digit code, sent by email. This only happens inside the login flow (in particular, when logging in from a new device). The code obtained for logging in is used a second time for adding the device. (#2186) + - 2nd factor authentication for "delete team" via 6 digit code, sent by email. (#2193) + - The `SndFactorPasswordChallenge` team feature is locked by default. (#2205) + - Details: [/docs/reference/config-options.md#2nd-factor-password-challenge](https://github.com/wireapp/wire-server/blob/develop/docs/reference/config-options.md#2nd-factor-password-challenge) + +## Bug fixes and other updates + + +* Fix data consistency issue in import of users from TM invitation to SCIM-managed (#2201) + +* Use the same context string as openmls for key package ref calculation (#2216) + +* Ensure that only conversation admins can create invite links. (Until now we have relied on clients to enforce this.) (#2211) + + +## Internal changes + + +* account-pages Helm chart: Add a "digest" image option (#2194) + +* Add more test mappings (#2185) + +* Internal endpoint for re-authentication (`GET "/i/users/:uid/reauthenticate"`) in brig has changed in a backwards compatible way. Spar depends on this change for creating a SCIM token with 2nd password challenge. (#2149) + +* Asset keys are now internally validated. (#2162) + +* Spar debugging; better internal combinators (#2214) + +* Remove the MonadClient instance of the Brig monad + + - Lots of functions were generalized to run in a monad constrained by + MonadClient instead of running directly in Brig's `AppIO r` monad. (#2187) + + +## Federation changes + + +* Refactor conversation actions to an existential type consisting of a singleton tag (identifying the action) and a dedicated type for the action itself. Previously, actions were represented by a big sum type. The new approach enables us to describe the needed effects of an action much more precisely. The existential type is initialized by the Servant endpoints in a way to mimic the previous behavior. However, the messages between services changed. Thus, all federated backends need to run the same (new) version. The deployment order itself does not matter. (#2173) + + # [2022-03-09] ## Release notes diff --git a/build/ubuntu/Dockerfile.deps b/build/ubuntu/Dockerfile.deps index 0a308ed5f6f..f46807d404a 100644 --- a/build/ubuntu/Dockerfile.deps +++ b/build/ubuntu/Dockerfile.deps @@ -12,6 +12,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ # compile core-crypto cli tool RUN cd /tmp && \ + apt-get install -y libssl-dev && \ git clone -b cli https://github.com/wireapp/core-crypto && \ cd core-crypto/cli && \ cargo build --release diff --git a/cabal.project b/cabal.project index c056574389e..ad3807ebaf3 100644 --- a/cabal.project +++ b/cabal.project @@ -42,6 +42,7 @@ packages: , services/spar/ , tools/api-simulations/ , tools/bonanza/ + , tools/db/assets/ , tools/db/auto-whitelist/ , tools/db/migrate-sso-feature-flag/ , tools/db/service-backfill/ @@ -177,6 +178,8 @@ package api-client ghc-options: -Werror package api-simulations ghc-options: -Werror +package assets + ghc-options: -Werror package auto-whitelist ghc-options: -Werror package bilge diff --git a/changelog.d/0-release-notes/team-settings-upgrade b/changelog.d/0-release-notes/team-settings-upgrade deleted file mode 100644 index 5046a06e109..00000000000 --- a/changelog.d/0-release-notes/team-settings-upgrade +++ /dev/null @@ -1 +0,0 @@ -Asset keys are now internally validated diff --git a/charts/account-pages/templates/deployment.yaml b/charts/account-pages/templates/deployment.yaml index f9fd5586887..138e0ce4dd4 100644 --- a/charts/account-pages/templates/deployment.yaml +++ b/charts/account-pages/templates/deployment.yaml @@ -27,7 +27,11 @@ spec: spec: containers: - name: account-pages + {{- if .Values.image.digest }} + image: "{{ .Values.image.repository }}@{{ .Values.image.digest }}" + {{- else }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + {{- end }} env: - name: BACKEND_REST value: https://{{ .Values.config.externalUrls.backendRest }} diff --git a/charts/brig/templates/configmap.yaml b/charts/brig/templates/configmap.yaml index 3885ccdf8cf..7ea78e2b4f9 100644 --- a/charts/brig/templates/configmap.yaml +++ b/charts/brig/templates/configmap.yaml @@ -181,6 +181,9 @@ data: {{- with .optSettings }} optSettings: setActivationTimeout: {{ .setActivationTimeout }} + {{- if .setVerificationTimeout }} + setVerificationTimeout: {{ .setVerificationTimeout }} + {{- end }} setTeamInvitationTimeout: {{ .setTeamInvitationTimeout }} {{- if .setExpiredUserCleanupTimeout }} setExpiredUserCleanupTimeout: {{ .setExpiredUserCleanupTimeout }} diff --git a/charts/galley/values.yaml b/charts/galley/values.yaml index 26546dba980..91f5d52384d 100644 --- a/charts/galley/values.yaml +++ b/charts/galley/values.yaml @@ -44,7 +44,7 @@ config: # sndFactorPasswordChallenge: # defaults: # status: disabled - # lockStatus: unlocked + # lockStatus: locked aws: region: "eu-west-1" proxy: {} diff --git a/charts/nginz/templates/conf/_nginx.conf.tpl b/charts/nginz/templates/conf/_nginx.conf.tpl index c6c9969e127..de56f95e62a 100644 --- a/charts/nginz/templates/conf/_nginx.conf.tpl +++ b/charts/nginz/templates/conf/_nginx.conf.tpl @@ -224,9 +224,15 @@ http { {{- if or (eq $env $.Values.nginx_conf.env) (eq $env "all") -}} {{- if and (not (eq $.Values.nginx_conf.env "prod")) ($location.doc) -}} + rewrite ^/api-docs{{ $location.path }} {{ $location.path }}/api-docs?base_url=https://{{ $.Values.nginx_conf.env }}-nginz-https.{{ $.Values.nginx_conf.external_env_domain }}/ break; {{- end }} + {{- if $location.strip_version }} + + rewrite ^/v[0-9]+({{ $location.path }}) $1; + {{- end }} + {{- $versioned := ternary $location.versioned true (hasKey $location "versioned") -}} {{- $path := printf "%s%s" (ternary "(/v[0-9]+)?" "" $versioned) $location.path }} diff --git a/charts/nginz/values.yaml b/charts/nginz/values.yaml index e24b6989d3b..99024247fee 100644 --- a/charts/nginz/values.yaml +++ b/charts/nginz/values.yaml @@ -456,9 +456,13 @@ nginx_conf: envs: - all disable_zauth: true + versioned: false + strip_version: true - path: /teams/([^/]*)/billing(.*) envs: - all + versioned: false + strip_version: true calling-test: - path: /calling-test envs: diff --git a/docs/reference/cassandra-schema.cql b/docs/reference/cassandra-schema.cql index 3f7e7e17639..6b54e37d6f0 100644 --- a/docs/reference/cassandra-schema.cql +++ b/docs/reference/cassandra-schema.cql @@ -13,13 +13,43 @@ CREATE TYPE galley_test.pubkey ( pem blob ); -CREATE TABLE galley_test.team_notifications ( +CREATE TABLE galley_test.meta ( + id int, + version int, + date timestamp, + descr text, + PRIMARY KEY (id, version) +) WITH CLUSTERING ORDER BY (version ASC) + AND bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = '' + AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE TABLE galley_test.conversation ( + conv uuid PRIMARY KEY, + access set, + access_role int, + access_roles_v2 set, + creator uuid, + deleted boolean, + group_id blob, + message_timer bigint, + name text, + protocol int, + receipt_mode int, team uuid, - id timeuuid, - payload blob, - PRIMARY KEY (team, id) -) WITH CLUSTERING ORDER BY (id ASC) - AND bloom_filter_fp_chance = 0.1 + type int +) WITH bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} @@ -34,12 +64,11 @@ CREATE TABLE galley_test.team_notifications ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.team_conv ( +CREATE TABLE galley_test.user_team ( + user uuid, team uuid, - conv uuid, - managed boolean, - PRIMARY KEY (team, conv) -) WITH CLUSTERING ORDER BY (conv ASC) + PRIMARY KEY (user, team) +) WITH CLUSTERING ORDER BY (team ASC) AND bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' @@ -101,19 +130,27 @@ CREATE TABLE galley_test.data_migration ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.user_remote_conv ( - user uuid, - conv_remote_domain text, - conv_remote_id uuid, - hidden boolean, - hidden_ref text, - otr_archived boolean, - otr_archived_ref text, - otr_muted_ref text, - otr_muted_status int, - PRIMARY KEY (user, conv_remote_domain, conv_remote_id) -) WITH CLUSTERING ORDER BY (conv_remote_domain ASC, conv_remote_id ASC) - AND bloom_filter_fp_chance = 0.1 +CREATE TABLE galley_test.team_features ( + team_id uuid PRIMARY KEY, + app_lock_enforce int, + app_lock_inactivity_timeout_secs int, + app_lock_status int, + conference_calling int, + digital_signatures int, + file_sharing int, + file_sharing_lock_status int, + guest_links_lock_status int, + guest_links_status int, + legalhold_status int, + search_visibility_status int, + self_deleting_messages_lock_status int, + self_deleting_messages_status int, + self_deleting_messages_ttl int, + snd_factor_password_challenge_lock_status int, + snd_factor_password_challenge_status int, + sso_status int, + validate_saml_emails int +) WITH bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} @@ -128,17 +165,23 @@ CREATE TABLE galley_test.user_remote_conv ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.team ( - team uuid PRIMARY KEY, - binding boolean, - creator uuid, - deleted boolean, - icon text, - icon_key text, - name text, - search_visibility int, - status int -) WITH bloom_filter_fp_chance = 0.1 +CREATE TABLE galley_test.member ( + conv uuid, + user uuid, + conversation_role text, + hidden boolean, + hidden_ref text, + otr_archived boolean, + otr_archived_ref text, + otr_muted boolean, + otr_muted_ref text, + otr_muted_status int, + provider uuid, + service uuid, + status int, + PRIMARY KEY (conv, user) +) WITH CLUSTERING ORDER BY (user ASC) + AND bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} @@ -153,13 +196,14 @@ CREATE TABLE galley_test.team ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.clients ( - user uuid PRIMARY KEY, - clients set +CREATE TABLE galley_test.custom_backend ( + domain text PRIMARY KEY, + config_json_url blob, + webapp_welcome_url blob ) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' - AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} + AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 @@ -171,12 +215,18 @@ CREATE TABLE galley_test.clients ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.legalhold_pending_prekeys ( +CREATE TABLE galley_test.user_remote_conv ( user uuid, - key int, - data text, - PRIMARY KEY (user, key) -) WITH CLUSTERING ORDER BY (key ASC) + conv_remote_domain text, + conv_remote_id uuid, + hidden boolean, + hidden_ref text, + otr_archived boolean, + otr_archived_ref text, + otr_muted_ref text, + otr_muted_status int, + PRIMARY KEY (user, conv_remote_domain, conv_remote_id) +) WITH CLUSTERING ORDER BY (conv_remote_domain ASC, conv_remote_id ASC) AND bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' @@ -192,26 +242,12 @@ CREATE TABLE galley_test.legalhold_pending_prekeys ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.member ( - conv uuid, - user uuid, - conversation_role text, - hidden boolean, - hidden_ref text, - otr_archived boolean, - otr_archived_ref text, - otr_muted boolean, - otr_muted_ref text, - otr_muted_status int, - provider uuid, - service uuid, - status int, - PRIMARY KEY (conv, user) -) WITH CLUSTERING ORDER BY (user ASC) - AND bloom_filter_fp_chance = 0.1 +CREATE TABLE galley_test.legalhold_whitelisted ( + team uuid PRIMARY KEY +) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' - AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} + AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 @@ -223,19 +259,14 @@ CREATE TABLE galley_test.member ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.conversation ( - conv uuid PRIMARY KEY, - access set, - access_role int, - access_roles_v2 set, - creator uuid, - deleted boolean, - message_timer bigint, - name text, - receipt_mode int, - team uuid, - type int -) WITH bloom_filter_fp_chance = 0.1 +CREATE TABLE galley_test.member_remote_user ( + conv uuid, + user_remote_domain text, + user_remote_id uuid, + conversation_role text, + PRIMARY KEY (conv, user_remote_domain, user_remote_id) +) WITH CLUSTERING ORDER BY (user_remote_domain ASC, user_remote_id ASC) + AND bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} @@ -250,14 +281,19 @@ CREATE TABLE galley_test.conversation ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.custom_backend ( - domain text PRIMARY KEY, - config_json_url blob, - webapp_welcome_url blob -) WITH bloom_filter_fp_chance = 0.01 +CREATE TABLE galley_test.team_member ( + team uuid, + user uuid, + invited_at timestamp, + invited_by uuid, + legalhold_status int, + perms frozen, + PRIMARY KEY (team, user) +) WITH CLUSTERING ORDER BY (user ASC) + AND bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' - AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 @@ -269,17 +305,16 @@ CREATE TABLE galley_test.custom_backend ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.meta ( - id int, - version int, - date timestamp, - descr text, - PRIMARY KEY (id, version) -) WITH CLUSTERING ORDER BY (version ASC) - AND bloom_filter_fp_chance = 0.01 +CREATE TABLE galley_test.team_notifications ( + team uuid, + id timeuuid, + payload blob, + PRIMARY KEY (team, id) +) WITH CLUSTERING ORDER BY (id ASC) + AND bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' - AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 @@ -291,11 +326,12 @@ CREATE TABLE galley_test.meta ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.user ( +CREATE TABLE galley_test.legalhold_pending_prekeys ( user uuid, - conv uuid, - PRIMARY KEY (user, conv) -) WITH CLUSTERING ORDER BY (conv ASC) + key int, + data text, + PRIMARY KEY (user, key) +) WITH CLUSTERING ORDER BY (key ASC) AND bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' @@ -311,17 +347,14 @@ CREATE TABLE galley_test.user ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.member_remote_user ( - conv uuid, - user_remote_domain text, - user_remote_id uuid, - conversation_role text, - PRIMARY KEY (conv, user_remote_domain, user_remote_id) -) WITH CLUSTERING ORDER BY (user_remote_domain ASC, user_remote_id ASC) - AND bloom_filter_fp_chance = 0.1 +CREATE TABLE galley_test.group_id_conv_id ( + group_id blob PRIMARY KEY, + conv_id uuid, + domain text +) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' - AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} + AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 @@ -333,12 +366,15 @@ CREATE TABLE galley_test.member_remote_user ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.legalhold_whitelisted ( - team uuid PRIMARY KEY -) WITH bloom_filter_fp_chance = 0.01 +CREATE TABLE galley_test.user ( + user uuid, + conv uuid, + PRIMARY KEY (user, conv) +) WITH CLUSTERING ORDER BY (conv ASC) + AND bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' - AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 @@ -371,15 +407,17 @@ CREATE TABLE galley_test.legalhold_service ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.billing_team_member ( - team uuid, - user uuid, - PRIMARY KEY (team, user) -) WITH CLUSTERING ORDER BY (user ASC) - AND bloom_filter_fp_chance = 0.01 +CREATE TABLE galley_test.conversation_codes ( + key ascii, + scope int, + conversation uuid, + value ascii, + PRIMARY KEY (key, scope) +) WITH CLUSTERING ORDER BY (scope ASC) + AND bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' - AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 @@ -391,14 +429,10 @@ CREATE TABLE galley_test.billing_team_member ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.conversation_codes ( - key ascii, - scope int, - conversation uuid, - value ascii, - PRIMARY KEY (key, scope) -) WITH CLUSTERING ORDER BY (scope ASC) - AND bloom_filter_fp_chance = 0.1 +CREATE TABLE galley_test.clients ( + user uuid PRIMARY KEY, + clients set +) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} @@ -413,27 +447,13 @@ CREATE TABLE galley_test.conversation_codes ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.team_features ( - team_id uuid PRIMARY KEY, - app_lock_enforce int, - app_lock_inactivity_timeout_secs int, - app_lock_status int, - conference_calling int, - digital_signatures int, - file_sharing int, - file_sharing_lock_status int, - guest_links_lock_status int, - guest_links_status int, - legalhold_status int, - search_visibility_status int, - self_deleting_messages_lock_status int, - self_deleting_messages_status int, - self_deleting_messages_ttl int, - snd_factor_password_challenge_lock_status int, - snd_factor_password_challenge_status int, - sso_status int, - validate_saml_emails int -) WITH bloom_filter_fp_chance = 0.1 +CREATE TABLE galley_test.team_conv ( + team uuid, + conv uuid, + managed boolean, + PRIMARY KEY (team, conv) +) WITH CLUSTERING ORDER BY (conv ASC) + AND bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} @@ -448,12 +468,17 @@ CREATE TABLE galley_test.team_features ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.user_team ( - user uuid, - team uuid, - PRIMARY KEY (user, team) -) WITH CLUSTERING ORDER BY (team ASC) - AND bloom_filter_fp_chance = 0.1 +CREATE TABLE galley_test.team ( + team uuid PRIMARY KEY, + binding boolean, + creator uuid, + deleted boolean, + icon text, + icon_key text, + name text, + search_visibility int, + status int +) WITH bloom_filter_fp_chance = 0.1 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} @@ -468,19 +493,15 @@ CREATE TABLE galley_test.user_team ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE galley_test.team_member ( +CREATE TABLE galley_test.billing_team_member ( team uuid, user uuid, - invited_at timestamp, - invited_by uuid, - legalhold_status int, - perms frozen, PRIMARY KEY (team, user) ) WITH CLUSTERING ORDER BY (user ASC) - AND bloom_filter_fp_chance = 0.1 + AND bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' - AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} + AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 @@ -765,6 +786,28 @@ CREATE TABLE brig_test.mls_key_packages ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; +CREATE TABLE brig_test.mls_key_package_refs ( + ref blob PRIMARY KEY, + client text, + conv uuid, + conv_domain text, + domain text, + user uuid +) WITH bloom_filter_fp_chance = 0.1 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = '' + AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + CREATE TABLE brig_test.excluded_phones ( prefix text PRIMARY KEY, comment text @@ -955,6 +998,28 @@ CREATE TABLE brig_test.user_keys ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; +CREATE TABLE brig_test.mls_public_keys ( + user uuid, + client text, + sig_scheme text, + key blob, + PRIMARY KEY (user, client, sig_scheme) +) WITH CLUSTERING ORDER BY (client ASC, sig_scheme ASC) + AND bloom_filter_fp_chance = 0.1 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = '' + AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + CREATE TABLE brig_test.invitee_info ( invitee uuid PRIMARY KEY, conv uuid, diff --git a/hack/bin/serve-charts.sh b/hack/bin/serve-charts.sh index 09172c43f10..edbb443877e 100755 --- a/hack/bin/serve-charts.sh +++ b/hack/bin/serve-charts.sh @@ -14,4 +14,4 @@ for chart in $@; do helm package "$chart" done helm repo index . -python -m http.server $HELM_SERVER_PORT +python3 -m http.server $HELM_SERVER_PORT diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index c9d769841fb..267f3e4a982 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -54,6 +54,7 @@ brig: enableFederator: true # keep in sync with galley.config.enableFederator, cargohold.config.enableFederator and tags.federator! optSettings: setActivationTimeout: 5 + setVerificationTimeout: 5 # keep this in sync with brigSettingsTeamInvitationTimeout in spar/templates/tests/configmap.yaml setTeamInvitationTimeout: 10 setExpiredUserCleanupTimeout: 1 diff --git a/libs/api-bot/src/Network/Wire/Bot/Monad.hs b/libs/api-bot/src/Network/Wire/Bot/Monad.hs index da64c0cebe6..04dcd73c4fd 100644 --- a/libs/api-bot/src/Network/Wire/Bot/Monad.hs +++ b/libs/api-bot/src/Network/Wire/Bot/Monad.hs @@ -396,7 +396,9 @@ addBotClient self cty label = do newClientClass = Nothing, newClientCookie = Nothing, newClientModel = Nothing, - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } cid <- clientId <$> runBotSession self (registerClient nc) clt <- BotClient cid label box <$> liftIO Clients.empty diff --git a/libs/api-client/src/Network/Wire/Client/API/Conversation.hs b/libs/api-client/src/Network/Wire/Client/API/Conversation.hs index e034aa95069..0b72a34c360 100644 --- a/libs/api-client/src/Network/Wire/Client/API/Conversation.hs +++ b/libs/api-client/src/Network/Wire/Client/API/Conversation.hs @@ -140,6 +140,6 @@ createConv users name = sessionRequest req rsc readBody method POST . path "conversations" . acceptJson - . json (NewConv users [] name mempty Nothing Nothing Nothing Nothing roleNameWireAdmin) + . json (NewConv users [] name mempty Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolProteus) $ empty rsc = status201 :| [] diff --git a/libs/brig-types/src/Brig/Types/Intra.hs b/libs/brig-types/src/Brig/Types/Intra.hs index 0b5982a4c23..65e7e822f91 100644 --- a/libs/brig-types/src/Brig/Types/Intra.hs +++ b/libs/brig-types/src/Brig/Types/Intra.hs @@ -34,10 +34,12 @@ import Brig.Types.Connection import Brig.Types.User import Data.Aeson import qualified Data.Aeson.KeyMap as KeyMap +import Data.Code as Code import Data.Id (TeamId, UserId) import Data.Misc (PlainTextPassword (..)) import qualified Data.Text as Text import Imports +import Wire.API.User (VerificationAction (..)) ------------------------------------------------------------------------------- -- AccountStatus @@ -169,16 +171,21 @@ instance ToJSON UserSet where -- | Certain operations might require reauth of the user. These are available -- only for users that have already set a password. -newtype ReAuthUser = ReAuthUser - {reAuthPassword :: Maybe PlainTextPassword} +data ReAuthUser = ReAuthUser + { reAuthPassword :: Maybe PlainTextPassword, + reAuthCode :: Maybe Code.Value, + reAuthCodeAction :: Maybe VerificationAction + } deriving (Eq, Show, Generic) instance FromJSON ReAuthUser where parseJSON = withObject "reauth-user" $ \o -> - ReAuthUser <$> o .:? "password" + ReAuthUser <$> o .:? "password" <*> o .:? "verification_code" <*> o .:? "action" instance ToJSON ReAuthUser where toJSON ru = object - [ "password" .= reAuthPassword ru + [ "password" .= reAuthPassword ru, + "verification_code" .= reAuthCode ru, + "action" .= reAuthCodeAction ru ] diff --git a/libs/brig-types/test/unit/Test/Brig/Types/User.hs b/libs/brig-types/test/unit/Test/Brig/Types/User.hs index 5ca8600d467..b8cbbdaa90b 100644 --- a/libs/brig-types/test/unit/Test/Brig/Types/User.hs +++ b/libs/brig-types/test/unit/Test/Brig/Types/User.hs @@ -57,7 +57,7 @@ instance Arbitrary RichInfoUpdate where arbitrary = RichInfoUpdate <$> arbitrary instance Arbitrary ReAuthUser where - arbitrary = ReAuthUser <$> arbitrary + arbitrary = ReAuthUser <$> arbitrary <*> arbitrary <*> arbitrary instance Arbitrary NewUserScimInvitation where arbitrary = NewUserScimInvitation <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary diff --git a/libs/galley-types/src/Galley/Types.hs b/libs/galley-types/src/Galley/Types.hs index b6e94f6bbd0..95b1d8e8a5c 100644 --- a/libs/galley-types/src/Galley/Types.hs +++ b/libs/galley-types/src/Galley/Types.hs @@ -70,6 +70,8 @@ module Galley.Types OtherMemberUpdate (..), MutedStatus (..), ReceiptMode (..), + Protocol (..), + GroupId (..), TypingStatus (..), UserClientMap (..), UserClients (..), diff --git a/libs/galley-types/src/Galley/Types/Teams.hs b/libs/galley-types/src/Galley/Types/Teams.hs index 7c106a93d30..e9d3d8b0001 100644 --- a/libs/galley-types/src/Galley/Types/Teams.hs +++ b/libs/galley-types/src/Galley/Types/Teams.hs @@ -117,6 +117,7 @@ module Galley.Types.Teams EventData (..), TeamUpdateData, newTeamUpdateData, + newTeamDeleteDataWithCode, nameUpdate, iconUpdate, iconKeyUpdate, @@ -125,6 +126,7 @@ module Galley.Types.Teams newTeamMemberDeleteData, TeamDeleteData, tdAuthPassword, + tdVerificationCode, newTeamDeleteData, HardTruncationLimit, hardTruncationLimit, diff --git a/libs/wire-api-federation/package.yaml b/libs/wire-api-federation/package.yaml index c46fac77be4..4306425c788 100644 --- a/libs/wire-api-federation/package.yaml +++ b/libs/wire-api-federation/package.yaml @@ -34,6 +34,7 @@ dependencies: - servant-client - servant-client-core - servant-server +- singletons - sop-core - streaming-commons - template-haskell diff --git a/libs/wire-api-federation/src/Wire/API/Federation/API/Galley.hs b/libs/wire-api-federation/src/Wire/API/Federation/API/Galley.hs index e461862c980..d8a072cca09 100644 --- a/libs/wire-api-federation/src/Wire/API/Federation/API/Galley.hs +++ b/libs/wire-api-federation/src/Wire/API/Federation/API/Galley.hs @@ -28,14 +28,7 @@ import Imports import Servant.API import Wire.API.Arbitrary (Arbitrary, GenericUniform (..)) import Wire.API.Conversation - ( Access, - AccessRoleV2, - ConvType, - ConversationMetadata, - ReceiptMode, - ) import Wire.API.Conversation.Action -import Wire.API.Conversation.Member (OtherMember) import Wire.API.Conversation.Role (RoleName) import Wire.API.Federation.API.Common import Wire.API.Federation.Endpoint @@ -145,11 +138,13 @@ data ConversationUpdate = ConversationUpdate -- conversation to users. cuAlreadyPresentUsers :: [UserId], -- | Information on the specific action that caused the update. - cuAction :: ConversationAction + cuAction :: SomeConversationAction } - deriving stock (Eq, Show, Generic) - deriving (Arbitrary) via (GenericUniform ConversationUpdate) - deriving (ToJSON, FromJSON) via (CustomEncoded ConversationUpdate) + deriving (Eq, Show, Generic) + +instance ToJSON ConversationUpdate + +instance FromJSON ConversationUpdate data LeaveConversationRequest = LeaveConversationRequest { -- | The conversation is assumed to be owned by the target domain, which diff --git a/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/ConversationUpdate.hs b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/ConversationUpdate.hs index 45c87050e15..e19c3b6732c 100644 --- a/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/ConversationUpdate.hs +++ b/libs/wire-api-federation/test/Test/Wire/API/Federation/Golden/ConversationUpdate.hs @@ -25,8 +25,10 @@ import Data.Domain (Domain (Domain)) import Data.Id (Id (Id), UserId) import Data.List.NonEmpty (NonEmpty (..)) import Data.Qualified (Qualified (Qualified)) +import Data.Singletons (sing) import qualified Data.UUID as UUID import Imports +import Wire.API.Conversation import Wire.API.Conversation.Action import Wire.API.Conversation.Role (roleNameWireAdmin) import Wire.API.Federation.API.Galley (ConversationUpdate (..)) @@ -56,7 +58,7 @@ testObject_ConversationUpdate1 = cuConvId = Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000006")), cuAlreadyPresentUsers = [], - cuAction = ConversationActionAddMembers (qAlice :| [qBob]) roleNameWireAdmin + cuAction = SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (qAlice :| [qBob]) roleNameWireAdmin) } testObject_ConversationUpdate2 :: ConversationUpdate @@ -70,5 +72,5 @@ testObject_ConversationUpdate2 = cuConvId = Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000006")), cuAlreadyPresentUsers = [chad, dee], - cuAction = ConversationActionRemoveMembers (pure qAlice) + cuAction = SomeConversationAction (sing @'ConversationLeaveTag) (pure qAlice) } diff --git a/libs/wire-api-federation/test/golden/testObject_ConversationUpdate1.json b/libs/wire-api-federation/test/golden/testObject_ConversationUpdate1.json index da957a74577..0c5ff9a27f2 100644 --- a/libs/wire-api-federation/test/golden/testObject_ConversationUpdate1.json +++ b/libs/wire-api-federation/test/golden/testObject_ConversationUpdate1.json @@ -1,14 +1,8 @@ { - "orig_user_id": { - "domain": "golden.example.com", - "id": "00000000-0000-0000-0000-000100000007" - }, - "already_present_users": [], - "time": "1864-04-12T12:22:43.673Z", - "action": { - "tag": "ConversationActionAddMembers", - "contents": [ - [ + "cuAction": { + "action": { + "role": "wire_admin", + "users": [ { "domain": "golden.example.com", "id": "00000000-0000-0000-0000-000100004007" @@ -17,9 +11,15 @@ "domain": "golden2.example.com", "id": "00000000-0000-0000-0000-000100005007" } - ], - "wire_admin" - ] + ] + }, + "tag": "ConversationJoinTag" + }, + "cuAlreadyPresentUsers": [], + "cuConvId": "00000000-0000-0000-0000-000100000006", + "cuOrigUserId": { + "domain": "golden.example.com", + "id": "00000000-0000-0000-0000-000100000007" }, - "conv_id": "00000000-0000-0000-0000-000100000006" + "cuTime": "1864-04-12T12:22:43.673Z" } \ No newline at end of file diff --git a/libs/wire-api-federation/test/golden/testObject_ConversationUpdate2.json b/libs/wire-api-federation/test/golden/testObject_ConversationUpdate2.json index e398d32ebce..21f5d72822b 100644 --- a/libs/wire-api-federation/test/golden/testObject_ConversationUpdate2.json +++ b/libs/wire-api-federation/test/golden/testObject_ConversationUpdate2.json @@ -1,21 +1,23 @@ { - "orig_user_id": { - "domain": "golden.example.com", - "id": "00000000-0000-0000-0000-000100000007" + "cuAction": { + "action": { + "users": [ + { + "domain": "golden.example.com", + "id": "00000000-0000-0000-0000-000100004007" + } + ] + }, + "tag": "ConversationLeaveTag" }, - "already_present_users": [ + "cuAlreadyPresentUsers": [ "00000fff-0000-0000-0000-000100005007", "00000fff-0000-aaaa-0000-000100005007" ], - "time": "1864-04-12T12:22:43.673Z", - "action": { - "tag": "ConversationActionRemoveMembers", - "contents": [ - { - "domain": "golden.example.com", - "id": "00000000-0000-0000-0000-000100004007" - } - ] + "cuConvId": "00000000-0000-0000-0000-000100000006", + "cuOrigUserId": { + "domain": "golden.example.com", + "id": "00000000-0000-0000-0000-000100000007" }, - "conv_id": "00000000-0000-0000-0000-000100000006" + "cuTime": "1864-04-12T12:22:43.673Z" } \ No newline at end of file diff --git a/libs/wire-api-federation/test/golden/testObject_NewRemoteConversation1.json b/libs/wire-api-federation/test/golden/testObject_NewRemoteConversation1.json index 1db7dd91ea0..312092a69a5 100644 --- a/libs/wire-api-federation/test/golden/testObject_NewRemoteConversation1.json +++ b/libs/wire-api-federation/test/golden/testObject_NewRemoteConversation1.json @@ -1,41 +1,41 @@ { - "orig_user_id": "eed9dea3-5468-45f8-b562-7ad5de2587d0", - "time": "1864-04-12T12:22:43.673Z", "cnv_access": [ "invite", "code" ], + "cnv_access_roles": [ + "team_member", + "non_team_member" + ], + "cnv_id": "d13dbe58-d4e3-450f-9c0c-1e632f548740", + "cnv_name": "gossip", + "cnv_type": 0, + "message_timer": 1000, "non_creator_members": [ { - "status": 0, "conversation_role": "wire_admin", + "id": "50e6fff1-ffbd-4235-bc73-19c093433beb", "qualified_id": { "domain": "golden.example.com", "id": "50e6fff1-ffbd-4235-bc73-19c093433beb" }, - "id": "50e6fff1-ffbd-4235-bc73-19c093433beb" + "status": 0 }, { - "status": 0, - "service": { - "id": "abfe2452-ed22-4f94-b4d4-765b989d7dbb", - "provider": "11b91f61-917e-489b-a268-60b881d08f06" - }, "conversation_role": "wire_member", + "id": "6801e49b-918c-4eef-baed-f18522152fca", "qualified_id": { "domain": "golden.example.com", "id": "6801e49b-918c-4eef-baed-f18522152fca" }, - "id": "6801e49b-918c-4eef-baed-f18522152fca" + "service": { + "id": "abfe2452-ed22-4f94-b4d4-765b989d7dbb", + "provider": "11b91f61-917e-489b-a268-60b881d08f06" + }, + "status": 0 } ], - "cnv_access_roles": [ - "team_member", - "non_team_member" - ], - "cnv_type": 0, + "orig_user_id": "eed9dea3-5468-45f8-b562-7ad5de2587d0", "receipt_mode": 42, - "message_timer": 1000, - "cnv_name": "gossip", - "cnv_id": "d13dbe58-d4e3-450f-9c0c-1e632f548740" + "time": "1864-04-12T12:22:43.673Z" } \ No newline at end of file diff --git a/libs/wire-api-federation/test/golden/testObject_NewRemoteConversation2.json b/libs/wire-api-federation/test/golden/testObject_NewRemoteConversation2.json index 338bca1e147..24d28af0f71 100644 --- a/libs/wire-api-federation/test/golden/testObject_NewRemoteConversation2.json +++ b/libs/wire-api-federation/test/golden/testObject_NewRemoteConversation2.json @@ -1,15 +1,15 @@ { - "orig_user_id": "eed9dea3-5468-45f8-b562-7ad5de2587d0", - "time": "1864-04-12T12:22:43.673Z", "cnv_access": [], - "non_creator_members": [], "cnv_access_roles": [ "team_member", "non_team_member" ], + "cnv_id": "d13dbe58-d4e3-450f-9c0c-1e632f548740", + "cnv_name": null, "cnv_type": 2, - "receipt_mode": null, "message_timer": null, - "cnv_name": null, - "cnv_id": "d13dbe58-d4e3-450f-9c0c-1e632f548740" + "non_creator_members": [], + "orig_user_id": "eed9dea3-5468-45f8-b562-7ad5de2587d0", + "receipt_mode": null, + "time": "1864-04-12T12:22:43.673Z" } \ No newline at end of file diff --git a/libs/wire-api-federation/wire-api-federation.cabal b/libs/wire-api-federation/wire-api-federation.cabal index 20d673c962c..94130b7bea1 100644 --- a/libs/wire-api-federation/wire-api-federation.cabal +++ b/libs/wire-api-federation/wire-api-federation.cabal @@ -97,6 +97,7 @@ library , servant-client , servant-client-core , servant-server + , singletons , sop-core , streaming-commons , template-haskell @@ -197,6 +198,7 @@ test-suite spec , servant-client , servant-client-core , servant-server + , singletons , sop-core , streaming-commons , template-haskell diff --git a/libs/wire-api/src/Wire/API/Asset.hs b/libs/wire-api/src/Wire/API/Asset.hs index a1c37b9c5cf..834a201dcd9 100644 --- a/libs/wire-api/src/Wire/API/Asset.hs +++ b/libs/wire-api/src/Wire/API/Asset.hs @@ -31,6 +31,7 @@ module Wire.API.Asset -- * AssetKey AssetKey (..), assetKeyToText, + nilAssetKey, -- * AssetToken AssetToken (..), @@ -176,7 +177,7 @@ assetKeyToText = T.decodeUtf8 . toByteString' instance ToSchema AssetKey where schema = - (T.decodeUtf8 . toByteString') + assetKeyToText .= parsedText "AssetKey" (runParser parser . T.encodeUtf8) & doc' . S.schema . S.example ?~ toJSON ("3-1-47de4580-ae51-4650-acbb-d10c028cb0ac" :: Text) @@ -186,6 +187,9 @@ instance S.ToParamSchema AssetKey where instance FromHttpApiData AssetKey where parseUrlPiece = first T.pack . runParser parser . T.encodeUtf8 +nilAssetKey :: AssetKey +nilAssetKey = AssetKeyV3 (Id UUID.nil) AssetVolatile + -------------------------------------------------------------------------------- -- AssetToken diff --git a/libs/wire-api/src/Wire/API/Conversation.hs b/libs/wire-api/src/Wire/API/Conversation.hs index f89fa2f1261..6ff85ae4fac 100644 --- a/libs/wire-api/src/Wire/API/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Conversation.hs @@ -42,6 +42,8 @@ module Wire.API.Conversation ConversationPagingState, pattern ConversationPagingState, ConversationsResponse (..), + Protocol (..), + GroupId (..), -- * Conversation properties Access (..), @@ -69,6 +71,8 @@ module Wire.API.Conversation ConversationAccessData (..), ConversationReceiptModeUpdate (..), ConversationMessageTimerUpdate (..), + ConversationJoin (..), + ConversationMemberUpdate (..), -- * re-exports module Wire.API.Conversation.Member, @@ -111,6 +115,7 @@ import System.Random (randomRIO) import Wire.API.Arbitrary import Wire.API.Conversation.Member import Wire.API.Conversation.Role (RoleName, roleNameWireAdmin) +import Wire.API.MLS.Group import Wire.API.Routes.MultiTablePaging -------------------------------------------------------------------------------- @@ -127,7 +132,11 @@ data ConversationMetadata = ConversationMetadata -- federation. cnvmTeam :: Maybe TeamId, cnvmMessageTimer :: Maybe Milliseconds, - cnvmReceiptMode :: Maybe ReceiptMode + cnvmReceiptMode :: Maybe ReceiptMode, + -- | The protocol of the conversation. It can be Proteus or MLS (1.0). + cnvmProtocol :: Protocol, + -- | The corresponding MLS group ID. This should only be set for MLS conversations. + cnvmGroupId :: Maybe GroupId } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform ConversationMetadata) @@ -180,6 +189,29 @@ conversationMetadataObjectSchema = (description ?~ "Per-conversation message timer (can be null)") (maybeWithDefault A.Null schema) <*> cnvmReceiptMode .= optField "receipt_mode" (maybeWithDefault A.Null schema) + <*> cnvmProtocol .= fmap (fromMaybe ProtocolProteus) (optField "protocol" (schema @Protocol)) + <*> cnvmGroupId + .= maybe_ + ( optFieldWithDocModifier + "group_id" + (description ?~ "An MLS group identifier (at most 256 bytes long)") + schema + ) + +data Protocol + = ProtocolProteus + | ProtocolMLS + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform Protocol) + deriving (FromJSON, ToJSON) via Schema Protocol + +instance ToSchema Protocol where + schema = + enum @Text "Protocol" $ + mconcat + [ element "proteus" ProtocolProteus, + element "mls" ProtocolMLS + ] instance ToSchema ConversationMetadata where schema = object "ConversationMetadata" conversationMetadataObjectSchema @@ -621,7 +653,9 @@ data NewConv = NewConv newConvMessageTimer :: Maybe Milliseconds, newConvReceiptMode :: Maybe ReceiptMode, -- | Every member except for the creator will have this role - newConvUsersRole :: RoleName + newConvUsersRole :: RoleName, + -- | The protocol of the conversation. It can be Proteus or MLS (1.0). + newConvProtocol :: Protocol } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform NewConv) @@ -670,6 +704,7 @@ instance ToSchema NewConv where .= ( fieldWithDocModifier "conversation_role" (description ?~ usersRoleDesc) schema <|> pure roleNameWireAdmin ) + <*> newConvProtocol .= fmap (fromMaybe ProtocolProteus) (optField "protocol" schema) where usersDesc = "List of user IDs (excluding the requestor) to be \ @@ -864,3 +899,37 @@ modelConversationMessageTimerUpdate = Doc.defineModel "ConversationMessageTimerU Doc.description "Contains conversation properties to update" Doc.property "message_timer" Doc.int64' $ Doc.description "Conversation message timer (in milliseconds); can be null" + +data ConversationJoin = ConversationJoin + { cjUsers :: NonEmpty (Qualified UserId), + cjRole :: RoleName + } + deriving stock (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform ConversationJoin) + deriving (FromJSON, ToJSON, S.ToSchema) via Schema ConversationJoin + +instance ToSchema ConversationJoin where + schema = + objectWithDocModifier + "ConversationJoin" + (description ?~ "The action of some users joining a conversation") + $ ConversationJoin + <$> cjUsers .= field "users" (nonEmptyArray schema) + <*> cjRole .= field "role" schema + +data ConversationMemberUpdate = ConversationMemberUpdate + { cmuTarget :: Qualified UserId, + cmuUpdate :: OtherMemberUpdate + } + deriving stock (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform ConversationMemberUpdate) + deriving (FromJSON, ToJSON, S.ToSchema) via Schema ConversationMemberUpdate + +instance ToSchema ConversationMemberUpdate where + schema = + objectWithDocModifier + "ConversationMemberUpdate" + (description ?~ "The action of promoting/demoting a member of a conversation") + $ ConversationMemberUpdate + <$> cmuTarget .= field "target" schema + <*> cmuUpdate .= field "update" schema diff --git a/libs/wire-api/src/Wire/API/Conversation/Action.hs b/libs/wire-api/src/Wire/API/Conversation/Action.hs index c89fb2db274..ecced87f7d9 100644 --- a/libs/wire-api/src/Wire/API/Conversation/Action.hs +++ b/libs/wire-api/src/Wire/API/Conversation/Action.hs @@ -14,74 +14,186 @@ -- -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . +{-# LANGUAGE StandaloneKindSignatures #-} +-- Ignore unused `genSingletons` Template Haskell results +{-# OPTIONS_GHC -Wno-unused-top-binds #-} module Wire.API.Conversation.Action - ( ConversationAction (..), + ( ConversationAction, + ConversationActionTag (..), + SConversationActionTag (..), + SomeConversationAction (..), conversationActionToEvent, - conversationActionTag, + conversationActionPermission, ) where +import Control.Lens ((?~)) import Data.Aeson (FromJSON (..), ToJSON (..)) +import qualified Data.Aeson as A +import qualified Data.Aeson.KeyMap as A import Data.Id -import Data.List.NonEmpty (NonEmpty) -import Data.Qualified +import qualified Data.List.NonEmpty as NonEmptyList +import Data.Qualified (Qualified) +import Data.Schema hiding (tag) +import Data.Singletons.TH +import qualified Data.Swagger as S import Data.Time.Clock import Imports -import Wire.API.Arbitrary (Arbitrary, GenericUniform (..)) +import Test.QuickCheck (elements) +import Wire.API.Arbitrary (Arbitrary (..)) import Wire.API.Conversation import Wire.API.Conversation.Role import Wire.API.Event.Conversation -import Wire.API.Util.Aeson (CustomEncoded (..)) - --- | A sum type consisting of all possible conversation actions. -data ConversationAction - = ConversationActionAddMembers (NonEmpty (Qualified UserId)) RoleName - | ConversationActionRemoveMembers (NonEmpty (Qualified UserId)) - | ConversationActionRename ConversationRename - | ConversationActionMessageTimerUpdate ConversationMessageTimerUpdate - | ConversationActionReceiptModeUpdate ConversationReceiptModeUpdate - | ConversationActionMemberUpdate (Qualified UserId) OtherMemberUpdate - | ConversationActionAccessUpdate ConversationAccessData - | ConversationActionDelete - deriving stock (Eq, Show, Generic) - deriving (Arbitrary) via (GenericUniform ConversationAction) - deriving (ToJSON, FromJSON) via (CustomEncoded ConversationAction) + +data ConversationActionTag + = ConversationJoinTag + | ConversationLeaveTag + | ConversationRemoveMembersTag + | ConversationMemberUpdateTag + | ConversationDeleteTag + | ConversationRenameTag + | ConversationMessageTimerUpdateTag + | ConversationReceiptModeUpdateTag + | ConversationAccessDataTag + deriving (Show, Eq, Generic, Bounded, Enum) + +instance Arbitrary ConversationActionTag where + arbitrary = elements [minBound .. maxBound] + +instance ToSchema ConversationActionTag where + schema = + enum @Text "ConversationActionTag" $ + mconcat + [ element "ConversationJoinTag" ConversationJoinTag, + element "ConversationLeaveTag" ConversationLeaveTag, + element "ConversationRemoveMembersTag" ConversationRemoveMembersTag, + element "ConversationMemberUpdateTag" ConversationMemberUpdateTag, + element "ConversationDeleteTag" ConversationDeleteTag, + element "ConversationRenameTag" ConversationRenameTag, + element "ConversationMessageTimerUpdateTag" ConversationMessageTimerUpdateTag, + element "ConversationReceiptModeUpdateTag" ConversationReceiptModeUpdateTag, + element "ConversationAccessDataTag" ConversationAccessDataTag + ] + +instance ToJSON ConversationActionTag where + toJSON = schemaToJSON + +instance FromJSON ConversationActionTag where + parseJSON = schemaParseJSON + +$(genSingletons [''ConversationActionTag]) + +$(singDecideInstance ''ConversationActionTag) + +-- | We use this type family instead of a sum type to be able to define +-- individual effects per conversation action. See 'HasConversationActionEffects'. +type family ConversationAction (tag :: ConversationActionTag) :: * where + ConversationAction 'ConversationJoinTag = ConversationJoin + ConversationAction 'ConversationLeaveTag = NonEmptyList.NonEmpty (Qualified UserId) + ConversationAction 'ConversationMemberUpdateTag = ConversationMemberUpdate + ConversationAction 'ConversationDeleteTag = () + ConversationAction 'ConversationRenameTag = ConversationRename + ConversationAction 'ConversationMessageTimerUpdateTag = ConversationMessageTimerUpdate + ConversationAction 'ConversationReceiptModeUpdateTag = ConversationReceiptModeUpdate + ConversationAction 'ConversationAccessDataTag = ConversationAccessData + ConversationAction 'ConversationRemoveMembersTag = NonEmptyList.NonEmpty (Qualified UserId) + +data SomeConversationAction where + SomeConversationAction :: Sing tag -> ConversationAction tag -> SomeConversationAction + +instance Show SomeConversationAction where + show (SomeConversationAction tag action) = + $(sCases ''ConversationActionTag [|tag|] [|show action|]) + +instance Eq SomeConversationAction where + (SomeConversationAction tag1 action1) == (SomeConversationAction tag2 action2) = + case tag1 %~ tag2 of + Proved Refl -> $(sCases ''ConversationActionTag [|tag1|] [|action1 == action2|]) + Disproved _ -> False + +instance ToJSON SomeConversationAction where + toJSON (SomeConversationAction sb action) = + let tag = fromSing sb + actionJSON = fromMaybe A.Null $ schemaOut (conversationActionSchema sb) action + in A.object ["tag" A..= tag, "action" A..= actionJSON] + +conversationActionSchema :: forall tag. Sing tag -> ValueSchema NamedSwaggerDoc (ConversationAction tag) +conversationActionSchema SConversationJoinTag = schema @ConversationJoin +conversationActionSchema SConversationLeaveTag = + objectWithDocModifier + "ConversationLeave" + (S.description ?~ "The action of some users leaving a conversation on their own") + $ field "users" (nonEmptyArray schema) +conversationActionSchema SConversationRemoveMembersTag = + objectWithDocModifier + "ConversationRemoveMembers" + (S.description ?~ "The action of some users being removed from a conversation") + $ field "targets" (nonEmptyArray schema) +conversationActionSchema SConversationMemberUpdateTag = schema @ConversationMemberUpdate +conversationActionSchema SConversationDeleteTag = + objectWithDocModifier + "ConversationDelete" + (S.description ?~ "The action of deleting a conversation") + (pure ()) +conversationActionSchema SConversationRenameTag = schema +conversationActionSchema SConversationMessageTimerUpdateTag = schema +conversationActionSchema SConversationReceiptModeUpdateTag = schema +conversationActionSchema SConversationAccessDataTag = schema + +instance FromJSON SomeConversationAction where + parseJSON = A.withObject "SomeConversationAction" $ \ob -> do + tag <- ob A..: "tag" + case A.lookup "action" ob of + Nothing -> fail "'action' property missing" + Just actionValue -> + case toSing tag of + SomeSing sb -> do + action <- schemaIn (conversationActionSchema sb) actionValue + return $ SomeConversationAction sb action + +instance Arbitrary SomeConversationAction where + arbitrary = do + tag <- arbitrary + case toSing tag of + SomeSing sb -> do + $(sCases ''ConversationActionTag [|sb|] [|SomeConversationAction sb <$> arbitrary|]) + +conversationActionPermission :: ConversationActionTag -> Action +conversationActionPermission ConversationJoinTag = AddConversationMember +conversationActionPermission ConversationLeaveTag = LeaveConversation +conversationActionPermission ConversationRemoveMembersTag = RemoveConversationMember +conversationActionPermission ConversationMemberUpdateTag = ModifyOtherConversationMember +conversationActionPermission ConversationDeleteTag = DeleteConversation +conversationActionPermission ConversationRenameTag = ModifyConversationName +conversationActionPermission ConversationMessageTimerUpdateTag = ModifyConversationMessageTimer +conversationActionPermission ConversationReceiptModeUpdateTag = ModifyConversationReceiptMode +conversationActionPermission ConversationAccessDataTag = ModifyConversationAccess conversationActionToEvent :: + forall tag. + Sing tag -> UTCTime -> Qualified UserId -> Qualified ConvId -> - ConversationAction -> + ConversationAction tag -> Event -conversationActionToEvent now quid qcnv (ConversationActionAddMembers newMembers role) = - Event qcnv quid now $ - EdMembersJoin $ SimpleMembers (map (`SimpleMember` role) (toList newMembers)) -conversationActionToEvent now quid qcnv (ConversationActionRemoveMembers removedMembers) = - Event qcnv quid now $ - EdMembersLeave (QualifiedUserIdList (toList removedMembers)) -conversationActionToEvent now quid qcnv (ConversationActionRename rename) = - Event qcnv quid now (EdConvRename rename) -conversationActionToEvent now quid qcnv (ConversationActionMessageTimerUpdate update) = - Event qcnv quid now (EdConvMessageTimerUpdate update) -conversationActionToEvent now quid qcnv (ConversationActionReceiptModeUpdate update) = - Event qcnv quid now (EdConvReceiptModeUpdate update) -conversationActionToEvent now quid qcnv (ConversationActionMemberUpdate target (OtherMemberUpdate role)) = - let update = MemberUpdateData target Nothing Nothing Nothing Nothing Nothing Nothing role - in Event qcnv quid now (EdMemberUpdate update) -conversationActionToEvent now quid qcnv (ConversationActionAccessUpdate update) = - Event qcnv quid now (EdConvAccessUpdate update) -conversationActionToEvent now quid qcnv ConversationActionDelete = - Event qcnv quid now EdConvDelete - -conversationActionTag :: Qualified UserId -> ConversationAction -> Action -conversationActionTag _ (ConversationActionAddMembers _ _) = AddConversationMember -conversationActionTag qusr (ConversationActionRemoveMembers victims) - | pure qusr == victims = LeaveConversation - | otherwise = RemoveConversationMember -conversationActionTag _ (ConversationActionRename _) = ModifyConversationName -conversationActionTag _ (ConversationActionMessageTimerUpdate _) = ModifyConversationMessageTimer -conversationActionTag _ (ConversationActionReceiptModeUpdate _) = ModifyConversationReceiptMode -conversationActionTag _ (ConversationActionMemberUpdate _ _) = ModifyOtherConversationMember -conversationActionTag _ (ConversationActionAccessUpdate _) = ModifyConversationAccess -conversationActionTag _ ConversationActionDelete = DeleteConversation +conversationActionToEvent tag now quid qcnv action = + let edata = case tag of + SConversationJoinTag -> + let ConversationJoin newMembers role = action + in EdMembersJoin $ SimpleMembers (map (`SimpleMember` role) (toList newMembers)) + SConversationLeaveTag -> + EdMembersLeave (QualifiedUserIdList (toList action)) + SConversationRemoveMembersTag -> + EdMembersLeave (QualifiedUserIdList (toList action)) + SConversationMemberUpdateTag -> + let ConversationMemberUpdate target (OtherMemberUpdate role) = action + update = MemberUpdateData target Nothing Nothing Nothing Nothing Nothing Nothing role + in EdMemberUpdate update + SConversationDeleteTag -> EdConvDelete + SConversationRenameTag -> EdConvRename action + SConversationMessageTimerUpdateTag -> EdConvMessageTimerUpdate action + SConversationReceiptModeUpdateTag -> EdConvReceiptModeUpdate action + SConversationAccessDataTag -> EdConvAccessUpdate action + in Event qcnv quid now edata diff --git a/libs/wire-api/src/Wire/API/ErrorDescription.hs b/libs/wire-api/src/Wire/API/ErrorDescription.hs index 73301bda8e5..0c3862dbe1c 100644 --- a/libs/wire-api/src/Wire/API/ErrorDescription.hs +++ b/libs/wire-api/src/Wire/API/ErrorDescription.hs @@ -213,6 +213,8 @@ type ConvMemberNotFound = ErrorDescription 404 "no-conversation-member" "Convers type TooManyMembers = ErrorDescription 403 "too-many-members" "Maximum number of members per conversation reached." +type MLSNonEmptyMemberList = ErrorDescription 400 "non-empty-member-list" "Attempting to add group members outside MLS" + type UnknownClient = ErrorDescription 403 "unknown-client" "Unknown Client" type ClientNotFound = ErrorDescription 404 "client-not-found" "Client not found" @@ -377,6 +379,8 @@ type PasswordAuthenticationFailed = ErrorDescription 403 "password-authenticatio type CodeAuthenticationFailed = ErrorDescription 403 "code-authentication-failed" "Code authentication failed." +type CodeAuthenticationRequired = ErrorDescription 403 "code-authentication-required" "Code authentication is required." + type MLSProtocolError = ErrorDescription 400 "mls-protocol-error" "MLS protocol error" mlsProtocolError :: Text -> MLSProtocolError @@ -413,3 +417,9 @@ type TooManyTeamMembers = ErrorDescription 403 "too-many-team-members" "Too many -- | docs/reference/user/registration.md {#RefRestrictRegistration}. type UserCreationRestricted = ErrorDescription 403 "user-creation-restricted" "This instance does not allow creation of personal users or teams." + +type DuplicateMLSPublicKey = + ErrorDescription + 400 + "mls-duplicate-public-key" + "MLS public key for the given signature scheme already exists" diff --git a/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs b/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs index 161edf3119c..0524136838b 100644 --- a/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs +++ b/libs/wire-api/src/Wire/API/MLS/CipherSuite.hs @@ -27,6 +27,7 @@ import qualified Crypto.PubKey.Ed25519 as Ed25519 import Data.Word import Imports import Wire.API.Arbitrary +import Wire.API.MLS.Credential import Wire.API.MLS.Serialisation newtype CipherSuite = CipherSuite {cipherSuiteNumber :: Word16} @@ -51,3 +52,6 @@ csVerifySignature MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 pub x sig = fromM pub' <- Ed25519.publicKey pub sig' <- Ed25519.signature sig pure $ Ed25519.verify pub' x sig' + +csSignatureScheme :: CipherSuiteTag -> SignatureSchemeTag +csSignatureScheme MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 = Ed25519 diff --git a/libs/wire-api/src/Wire/API/MLS/Credential.hs b/libs/wire-api/src/Wire/API/MLS/Credential.hs index 2db0616336e..1d2e98969d8 100644 --- a/libs/wire-api/src/Wire/API/MLS/Credential.hs +++ b/libs/wire-api/src/Wire/API/MLS/Credential.hs @@ -19,6 +19,9 @@ module Wire.API.MLS.Credential where +import Data.Aeson (FromJSON (..), FromJSONKey (..), ToJSON (..), ToJSONKey (..)) +import qualified Data.Aeson as Aeson +import qualified Data.Aeson.Types as Aeson import Data.Binary import Data.Binary.Get import Data.Binary.Parser @@ -26,6 +29,8 @@ import Data.Binary.Parser.Char8 import Data.Domain import Data.Id import Data.Qualified +import Data.Schema +import qualified Data.Swagger as S import qualified Data.Text as T import Data.UUID import Imports @@ -64,16 +69,67 @@ credentialTag (BasicCredential _ _ _) = BasicCredentialTag -- | A TLS signature scheme. -- -- See . -newtype SignatureScheme = SignatureScheme {signatureSchemeNumber :: Word16} +newtype SignatureScheme = SignatureScheme {unSignatureScheme :: Word16} deriving stock (Eq, Show) deriving newtype (ParseMLS, Arbitrary) +signatureScheme :: SignatureSchemeTag -> SignatureScheme +signatureScheme = SignatureScheme . signatureSchemeNumber + +data SignatureSchemeTag = Ed25519 + deriving stock (Bounded, Enum, Eq, Ord, Show, Generic) + deriving (Arbitrary) via GenericUniform SignatureSchemeTag + +signatureSchemeNumber :: SignatureSchemeTag -> Word16 +signatureSchemeNumber Ed25519 = 0x807 + +signatureSchemeName :: SignatureSchemeTag -> Text +signatureSchemeName Ed25519 = "ed25519" + +signatureSchemeTag :: SignatureScheme -> Maybe SignatureSchemeTag +signatureSchemeTag (SignatureScheme n) = getAlt $ + flip foldMap [minBound .. maxBound] $ \s -> + guard (signatureSchemeNumber s == n) $> s + +signatureSchemeFromName :: Text -> Maybe SignatureSchemeTag +signatureSchemeFromName name = getAlt $ + flip foldMap [minBound .. maxBound] $ \s -> + guard (signatureSchemeName s == name) $> s + +parseSignatureScheme :: MonadFail f => Text -> f SignatureSchemeTag +parseSignatureScheme name = + maybe + (fail ("Unsupported signature scheme " <> T.unpack name)) + pure + (signatureSchemeFromName name) + +instance FromJSON SignatureSchemeTag where + parseJSON = Aeson.withText "SignatureScheme" parseSignatureScheme + +instance FromJSONKey SignatureSchemeTag where + fromJSONKey = Aeson.FromJSONKeyTextParser parseSignatureScheme + +instance ToJSON SignatureSchemeTag where + toJSON = Aeson.String . signatureSchemeName + +instance ToJSONKey SignatureSchemeTag where + toJSONKey = Aeson.toJSONKeyText signatureSchemeName + data ClientIdentity = ClientIdentity { ciDomain :: Domain, ciUser :: UserId, ciClient :: ClientId } deriving stock (Eq, Show, Generic) + deriving (FromJSON, ToJSON, S.ToSchema) via Schema ClientIdentity + +instance ToSchema ClientIdentity where + schema = + object "ClientIdentity" $ + ClientIdentity + <$> ciDomain .= field "domain" schema + <*> ciUser .= field "user_id" schema + <*> ciClient .= field "client_id" schema instance ParseMLS ClientIdentity where parseMLS = do diff --git a/libs/wire-api/src/Wire/API/MLS/Group.hs b/libs/wire-api/src/Wire/API/MLS/Group.hs index f7a5d9d8245..d58accfd7ca 100644 --- a/libs/wire-api/src/Wire/API/MLS/Group.hs +++ b/libs/wire-api/src/Wire/API/MLS/Group.hs @@ -17,14 +17,27 @@ module Wire.API.MLS.Group where +import qualified Data.Aeson as A +import Data.Json.Util +import Data.Schema +import qualified Data.Swagger as S import Imports +import Wire.API.Arbitrary import Wire.API.MLS.Serialisation newtype GroupId = GroupId {unGroupId :: ByteString} - deriving (Eq, Show) + deriving (Eq, Show, Generic) + deriving (Arbitrary) via (GenericUniform GroupId) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema GroupId) instance IsString GroupId where fromString = GroupId . fromString instance ParseMLS GroupId where parseMLS = GroupId <$> parseMLSBytes @Word8 + +instance ToSchema GroupId where + schema = + GroupId + <$> unGroupId + .= named "GroupId" (Base64ByteString .= fmap fromBase64ByteString (unnamed schema)) diff --git a/libs/wire-api/src/Wire/API/MLS/KeyPackage.hs b/libs/wire-api/src/Wire/API/MLS/KeyPackage.hs index c8e7a88347d..96183bafb59 100644 --- a/libs/wire-api/src/Wire/API/MLS/KeyPackage.hs +++ b/libs/wire-api/src/Wire/API/MLS/KeyPackage.hs @@ -69,6 +69,7 @@ import Data.Singletons.TH import qualified Data.Swagger as S import Data.Time.Clock.POSIX import Imports +import Web.HttpApiData import Wire.API.Arbitrary import Wire.API.MLS.CipherSuite import Wire.API.MLS.Credential @@ -97,6 +98,7 @@ instance ToSchema KeyPackageData where data KeyPackageBundleEntry = KeyPackageBundleEntry { kpbeUser :: Qualified UserId, kpbeClient :: ClientId, + kpbeRef :: KeyPackageRef, kpbeKeyPackage :: KeyPackageData } deriving stock (Eq, Ord) @@ -107,6 +109,7 @@ instance ToSchema KeyPackageBundleEntry where KeyPackageBundleEntry <$> kpbeUser .= qualifiedObjectSchema "user" schema <*> kpbeClient .= field "client" schema + <*> kpbeRef .= field "key_package_ref" schema <*> kpbeKeyPackage .= field "key_package" schema newtype KeyPackageBundle = KeyPackageBundle {kpbEntries :: Set KeyPackageBundleEntry} @@ -127,6 +130,27 @@ instance ToSchema KeyPackageCount where object "OwnKeyPackages" $ KeyPackageCount <$> unKeyPackageCount .= field "count" schema +newtype KeyPackageRef = KeyPackageRef {unKeyPackageRef :: ByteString} + deriving stock (Eq, Ord, Show) + deriving (FromHttpApiData, ToHttpApiData, S.ToParamSchema) via Base64ByteString + +instance ToSchema KeyPackageRef where + schema = named "KeyPackageRef" $ unKeyPackageRef .= fmap KeyPackageRef base64Schema + +instance ParseMLS KeyPackageRef where + parseMLS = KeyPackageRef <$> getByteString 16 + +kpRef :: CipherSuiteTag -> KeyPackageData -> KeyPackageRef +kpRef cs = + KeyPackageRef + -- Warning: the "context" string here is different from the one mandated by + -- the spec, but it is the one that happens to be used by openmls. Until + -- openmls is patched and we switch to a fixed version, we will have to use + -- the "wrong" string here as well. + . csHash cs "MLS 1.0 ref" + . LBS.toStrict + . kpData + -------------------------------------------------------------------------------- newtype ProtocolVersion = ProtocolVersion {pvNumber :: Word8} @@ -247,19 +271,6 @@ data KeyPackage = KeyPackage } deriving stock (Eq, Show) -newtype KeyPackageRef = KeyPackageRef {unKeyPackageRef :: ByteString} - deriving stock (Eq, Show) - -instance ParseMLS KeyPackageRef where - parseMLS = KeyPackageRef <$> getByteString 16 - -kpRef :: CipherSuiteTag -> KeyPackageData -> KeyPackageRef -kpRef cs = - KeyPackageRef - . csHash cs "MLS 1.0 KeyPackage Reference" - . LBS.toStrict - . kpData - instance ParseMLS KeyPackage where parseMLS = fst <$> kpSigOffset diff --git a/libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs b/libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs index e8747fb173f..88a77c440e2 100644 --- a/libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs +++ b/libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs @@ -19,6 +19,7 @@ module Wire.API.Routes.Internal.Brig ( API, EJPD_API, AccountAPI, + MLSAPI, EJPDRequest, GetAccountFeatureConfig, PutAccountFeatureConfig, @@ -30,6 +31,7 @@ module Wire.API.Routes.Internal.Brig where import Control.Lens ((.~)) +import qualified Data.Code as Code import Data.Id as Id import Data.Swagger (HasInfo (info), HasTitle (title), Swagger) import Imports hiding (head) @@ -39,6 +41,8 @@ import Servant.Swagger (HasSwagger (toSwagger)) import Servant.Swagger.Internal.Orphans () import Servant.Swagger.UI import Wire.API.Connection +import Wire.API.MLS.Credential +import Wire.API.MLS.KeyPackage import Wire.API.Routes.Internal.Brig.Connection import Wire.API.Routes.Internal.Brig.EJPD import Wire.API.Routes.MultiVerb @@ -134,9 +138,32 @@ type AccountAPI = :> MultiVerb 'POST '[Servant.JSON] RegisterInternalResponses (Either RegisterError SelfProfile) ) +type MLSAPI = GetClientByKeyPackageRef + +type GetClientByKeyPackageRef = + Summary "Resolve an MLS key package ref to a qualified client ID" + :> "mls" + :> "key-packages" + :> Capture "ref" KeyPackageRef + :> MultiVerb + 'GET + '[Servant.JSON] + '[ RespondEmpty 404 "Key package ref not found", + Respond 200 "Key package ref found" ClientIdentity + ] + (Maybe ClientIdentity) + +type GetVerificationCode = + Summary "Get verification code for a given email and action" + :> "users" + :> Capture "uid" UserId + :> "verification-code" + :> Capture "action" VerificationAction + :> Get '[Servant.JSON] (Maybe Code.Value) + type API = "i" - :> (EJPD_API :<|> AccountAPI) + :> (EJPD_API :<|> AccountAPI :<|> MLSAPI :<|> GetVerificationCode) type SwaggerDocsAPI = "api" :> "internal" :> SwaggerSchemaUI "swagger-ui" "swagger.json" diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs index 83463b2133e..f1175fb6f4a 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs @@ -406,6 +406,8 @@ type UserClientAPI = :> CanThrow TooManyClients :> CanThrow MissingAuth :> CanThrow MalformedPrekeys + :> CanThrow CodeAuthenticationFailed + :> CanThrow CodeAuthenticationRequired :> ZUser :> ZConn :> "clients" diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs index b1c5b1f97fb..e42503355f8 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs @@ -816,6 +816,8 @@ type TeamAPI = :> CanThrow NotATeamMember :> CanThrow DeleteQueueFull :> CanThrow ReAuthFailed + :> CanThrow CodeAuthenticationFailed + :> CanThrow CodeAuthenticationRequired :> "teams" :> Capture "tid" TeamId :> ReqBody '[Servant.JSON] TeamDeleteData diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Spar.hs b/libs/wire-api/src/Wire/API/Routes/Public/Spar.hs index ecf42e21d24..cac53e1c892 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Spar.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Spar.hs @@ -33,7 +33,7 @@ import Web.Scim.Capabilities.MetaSchema as Scim.Meta import Web.Scim.Class.Auth as Scim.Auth import Web.Scim.Class.User as Scim.User import Wire.API.Cookie -import Wire.API.ErrorDescription (CanThrow, CodeAuthenticationFailed, PasswordAuthenticationFailed) +import Wire.API.ErrorDescription import Wire.API.Routes.Public import Wire.API.User.IdentityProvider import Wire.API.User.Saml @@ -207,6 +207,7 @@ type APIScim = :<|> "auth-tokens" :> CanThrow PasswordAuthenticationFailed :> CanThrow CodeAuthenticationFailed + :> CanThrow CodeAuthenticationRequired :> APIScimToken type ScimSiteAPI tag = ToServantApi (ScimSite tag) diff --git a/libs/wire-api/src/Wire/API/Team.hs b/libs/wire-api/src/Wire/API/Team.hs index 8fb331559cc..b74566377ab 100644 --- a/libs/wire-api/src/Wire/API/Team.hs +++ b/libs/wire-api/src/Wire/API/Team.hs @@ -30,6 +30,7 @@ module Wire.API.Team teamIconKey, teamBinding, TeamBinding (..), + Icon (..), -- * TeamList TeamList (..), @@ -51,6 +52,7 @@ module Wire.API.Team -- * TeamUpdateData TeamUpdateData (..), newTeamUpdateData, + newTeamDeleteDataWithCode, nameUpdate, iconUpdate, iconKeyUpdate, @@ -59,6 +61,7 @@ module Wire.API.Team TeamDeleteData (..), newTeamDeleteData, tdAuthPassword, + tdVerificationCode, -- * Swagger modelTeam, @@ -75,6 +78,7 @@ import Data.Aeson.Types (Parser) import qualified Data.Attoparsec.ByteString as Atto (Parser, string) import Data.Attoparsec.Combinator (choice) import Data.ByteString.Conversion +import qualified Data.Code as Code import Data.Id (TeamId, UserId) import Data.Misc (PlainTextPassword (..)) import Data.Range @@ -95,7 +99,7 @@ data Team = Team { _teamId :: TeamId, _teamCreator :: UserId, _teamName :: Text, - _teamIcon :: Text, + _teamIcon :: Icon, _teamIconKey :: Maybe Text, _teamBinding :: TeamBinding } @@ -103,7 +107,7 @@ data Team = Team deriving (Arbitrary) via (GenericUniform Team) deriving (ToJSON, FromJSON, S.ToSchema) via (Schema Team) -newTeam :: TeamId -> UserId -> Text -> Text -> TeamBinding -> Team +newTeam :: TeamId -> UserId -> Text -> Icon -> TeamBinding -> Team newTeam tid uid nme ico = Team tid uid nme ico Nothing modelTeam :: Doc.Model @@ -230,14 +234,14 @@ modelNewNonBindingTeam = Doc.defineModel "newNonBindingTeam" $ do data NewTeam a = NewTeam { _newTeamName :: Range 1 256 Text, - _newTeamIcon :: Range 1 256 Text, + _newTeamIcon :: Icon, _newTeamIconKey :: Maybe (Range 1 256 Text), _newTeamMembers :: Maybe a } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform (NewTeam a)) -newNewTeam :: Range 1 256 Text -> Range 1 256 Text -> NewTeam a +newNewTeam :: Range 1 256 Text -> Icon -> NewTeam a newNewTeam nme ico = NewTeam nme ico Nothing Nothing newTeamObjectSchema :: ValueSchema SwaggerDoc a -> ObjectSchema SwaggerDoc (NewTeam a) @@ -251,30 +255,30 @@ newTeamObjectSchema sch = -------------------------------------------------------------------------------- -- TeamUpdateData -data IconUpdate = IconUpdate AssetKey | DefaultIcon +data Icon = Icon AssetKey | DefaultIcon deriving stock (Eq, Show, Generic) - deriving (Arbitrary) via (GenericUniform IconUpdate) - deriving (ToJSON, FromJSON, S.ToSchema) via Schema IconUpdate + deriving (Arbitrary) via (GenericUniform Icon) + deriving (ToJSON, FromJSON, S.ToSchema) via Schema Icon -instance FromByteString IconUpdate where +instance FromByteString Icon where parser = choice - [ IconUpdate <$> (parser :: Atto.Parser AssetKey), + [ Icon <$> (parser :: Atto.Parser AssetKey), DefaultIcon <$ Atto.string "default" ] -instance ToByteString IconUpdate where - builder (IconUpdate key) = builder key +instance ToByteString Icon where + builder (Icon key) = builder key builder DefaultIcon = "default" -instance ToSchema IconUpdate where +instance ToSchema Icon where schema = (T.decodeUtf8 . toByteString') - .= parsedText "IconUpdate" (runParser parser . T.encodeUtf8) + .= parsedText "Icon" (runParser parser . T.encodeUtf8) data TeamUpdateData = TeamUpdateData { _nameUpdate :: Maybe (Range 1 256 Text), - _iconUpdate :: Maybe IconUpdate, + _iconUpdate :: Maybe Icon, _iconKeyUpdate :: Maybe (Range 1 256 Text) } deriving stock (Eq, Show, Generic) @@ -322,15 +326,21 @@ instance ToSchema TeamUpdateData where -------------------------------------------------------------------------------- -- TeamDeleteData -newtype TeamDeleteData = TeamDeleteData - { _tdAuthPassword :: Maybe PlainTextPassword +data TeamDeleteData = TeamDeleteData + { _tdAuthPassword :: Maybe PlainTextPassword, + _tdVerificationCode :: Maybe Code.Value } deriving stock (Eq, Show) - deriving newtype (Arbitrary) deriving (ToJSON, FromJSON, S.ToSchema) via (Schema TeamDeleteData) +instance Arbitrary TeamDeleteData where + arbitrary = TeamDeleteData <$> arbitrary <*> arbitrary + newTeamDeleteData :: Maybe PlainTextPassword -> TeamDeleteData -newTeamDeleteData = TeamDeleteData +newTeamDeleteData = flip TeamDeleteData Nothing + +newTeamDeleteDataWithCode :: Maybe PlainTextPassword -> Maybe Code.Value -> TeamDeleteData +newTeamDeleteDataWithCode = TeamDeleteData -- FUTUREWORK: fix name of model? (upper case) modelTeamDelete :: Doc.Model @@ -338,11 +348,15 @@ modelTeamDelete = Doc.defineModel "teamDeleteData" $ do Doc.description "Data for a team deletion request in case of binding teams." Doc.property "password" Doc.string' $ Doc.description "The account password to authorise the deletion." + Doc.property "verification_code" Doc.string' $ + Doc.description "The verification code to authorise the deletion." instance ToSchema TeamDeleteData where schema = object "TeamDeleteData" $ - TeamDeleteData <$> _tdAuthPassword .= optField "password" (maybeWithDefault Null schema) + TeamDeleteData + <$> _tdAuthPassword .= optField "password" (maybeWithDefault Null schema) + <*> _tdVerificationCode .= maybe_ (optField "verification_code" schema) makeLenses ''Team makeLenses ''TeamList diff --git a/libs/wire-api/src/Wire/API/Team/Feature.hs b/libs/wire-api/src/Wire/API/Team/Feature.hs index 2c7b97a8808..80c996be84e 100644 --- a/libs/wire-api/src/Wire/API/Team/Feature.hs +++ b/libs/wire-api/src/Wire/API/Team/Feature.hs @@ -637,7 +637,7 @@ defaultTeamFeatureValidateSAMLEmailsStatus = TeamFeatureStatusNoConfig TeamFeatu -- TeamFeatureSndFactorPasswordChallenge defaultTeamFeatureSndFactorPasswordChallengeStatus :: TeamFeatureStatusNoConfigAndLockStatus -defaultTeamFeatureSndFactorPasswordChallengeStatus = TeamFeatureStatusNoConfigAndLockStatus TeamFeatureDisabled Unlocked +defaultTeamFeatureSndFactorPasswordChallengeStatus = TeamFeatureStatusNoConfigAndLockStatus TeamFeatureDisabled Locked ---------------------------------------------------------------------- -- internal diff --git a/libs/wire-api/src/Wire/API/User.hs b/libs/wire-api/src/Wire/API/User.hs index 148fd4a7029..cb80899db44 100644 --- a/libs/wire-api/src/Wire/API/User.hs +++ b/libs/wire-api/src/Wire/API/User.hs @@ -1,6 +1,6 @@ +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE StrictData #-} -- This file is part of the Wire Server implementation. -- @@ -99,9 +99,8 @@ module Wire.API.User modelVerifyDelete, -- * 2nd factor auth - SndFactorPasswordChallengeAction (..), + VerificationAction (..), SendVerificationCode (..), - TeamFeatureSndFPasswordChallengeNotImplemented (..), ) where @@ -110,11 +109,13 @@ import Control.Error.Safe (rightMay) import Control.Lens (over, (.~), (?~)) import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson.Types as A +import qualified Data.Attoparsec.ByteString as Parser import Data.ByteString.Conversion import qualified Data.CaseInsensitive as CI import qualified Data.Code as Code import qualified Data.Currency as Currency import Data.Domain (Domain (Domain)) +import Data.Either.Extra (maybeToEither) import Data.Handle (Handle) import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap import Data.Id @@ -125,9 +126,12 @@ import Data.Qualified import Data.Range import Data.SOP import Data.Schema +import Data.String.Conversions (cs) import qualified Data.Swagger as S import qualified Data.Swagger.Build.Api as Doc +import qualified Data.Text as T import Data.Text.Ascii +import qualified Data.Text.Encoding as T import Data.UUID (UUID, nil) import qualified Data.UUID as UUID import Deriving.Swagger @@ -135,7 +139,7 @@ import GHC.TypeLits (KnownNat, Nat) import qualified Generics.SOP as GSOP import Imports import qualified SAML2.WebSSO as SAML -import Servant (type (.++)) +import Servant (FromHttpApiData (..), ToHttpApiData (..), type (.++)) import qualified Test.QuickCheck as QC import qualified Web.Cookie as Web import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) @@ -1196,25 +1200,53 @@ instance S.ToSchema ListUsersQuery where ----------------------------------------------------------------------------- -- SndFactorPasswordChallenge --- | remove this type once we have an implementation in order to find all the places where we need to touch code. -data TeamFeatureSndFPasswordChallengeNotImplemented - = TeamFeatureSndFPasswordChallengeNotImplemented - -data SndFactorPasswordChallengeAction = GenerateScimToken | Login +data VerificationAction + = CreateScimToken + | Login + | DeleteTeam deriving stock (Eq, Show, Enum, Bounded, Generic) - deriving (Arbitrary) via (GenericUniform SndFactorPasswordChallengeAction) - deriving (FromJSON, ToJSON, S.ToSchema) via (Schema SndFactorPasswordChallengeAction) + deriving (Arbitrary) via (GenericUniform VerificationAction) + deriving (FromJSON, ToJSON, S.ToSchema) via (Schema VerificationAction) -instance ToSchema SndFactorPasswordChallengeAction where +instance ToSchema VerificationAction where schema = - enum @Text "SndFactorPasswordChallengeAction" $ + enum @Text "VerificationAction" $ mconcat - [ element "generate_scim_token" GenerateScimToken, - element "login" Login + [ element "create_scim_token" CreateScimToken, + element "login" Login, + element "delete_team" DeleteTeam ] +instance ToByteString VerificationAction where + builder CreateScimToken = "create_scim_token" + builder Login = "login" + builder DeleteTeam = "delete_team" + +instance FromByteString VerificationAction where + parser = + Parser.takeByteString >>= \b -> + case T.decodeUtf8' b of + Right "login" -> pure Login + Right "create_scim_token" -> pure CreateScimToken + Right "delete_team" -> pure DeleteTeam + Right t -> fail $ "Invalid VerificationAction: " <> T.unpack t + Left e -> fail $ "Invalid VerificationAction: " <> show e + +instance S.ToParamSchema VerificationAction where + toParamSchema _ = + mempty + { S._paramSchemaType = Just S.SwaggerString, + S._paramSchemaEnum = Just (A.String . toQueryParam <$> [(minBound :: VerificationAction) ..]) + } + +instance FromHttpApiData VerificationAction where + parseUrlPiece = maybeToEither "Invalid verification action" . fromByteString . cs + +instance ToHttpApiData VerificationAction where + toQueryParam a = cs (toByteString' a) + data SendVerificationCode = SendVerificationCode - { svcAction :: SndFactorPasswordChallengeAction, + { svcAction :: VerificationAction, svcEmail :: Email } deriving stock (Eq, Show, Generic) diff --git a/libs/wire-api/src/Wire/API/User/Auth.hs b/libs/wire-api/src/Wire/API/User/Auth.hs index e8fac171d23..d4df1fbd6d2 100644 --- a/libs/wire-api/src/Wire/API/User/Auth.hs +++ b/libs/wire-api/src/Wire/API/User/Auth.hs @@ -55,7 +55,7 @@ where import Data.Aeson import qualified Data.Aeson.Types as Aeson import Data.ByteString.Conversion -import qualified Data.Code as Code +import Data.Code as Code import Data.Handle (Handle) import Data.Id (UserId) import Data.Misc (PlainTextPassword (..)) @@ -66,7 +66,6 @@ import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8) import Data.Time.Clock (UTCTime) import Imports import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) -import Wire.API.User.Activation import Wire.API.User.Identity (Email, Phone) -------------------------------------------------------------------------------- @@ -74,7 +73,7 @@ import Wire.API.User.Identity (Email, Phone) -- | Different kinds of logins. data Login - = PasswordLogin LoginId PlainTextPassword (Maybe CookieLabel) (Maybe ActivationCode) + = PasswordLogin LoginId PlainTextPassword (Maybe CookieLabel) (Maybe Code.Value) | SmsLogin Phone LoginCode (Maybe CookieLabel) deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform Login) diff --git a/libs/wire-api/src/Wire/API/User/Client.hs b/libs/wire-api/src/Wire/API/User/Client.hs index 76a9543db2e..58016933d9f 100644 --- a/libs/wire-api/src/Wire/API/User/Client.hs +++ b/libs/wire-api/src/Wire/API/User/Client.hs @@ -49,6 +49,7 @@ module Wire.API.User.Client NewClient (..), newClient, UpdateClient (..), + defUpdateClient, RmClient (..), -- * re-exports @@ -81,6 +82,7 @@ import qualified Data.Aeson as A import qualified Data.Aeson.Key as Key import qualified Data.Aeson.KeyMap as KeyMap import Data.Bifunctor (second) +import qualified Data.Code as Code import Data.Coerce import Data.Domain (Domain) import Data.Id @@ -103,6 +105,7 @@ import Deriving.Swagger ) import Imports import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..), generateExample, mapOf', setOf') +import Wire.API.MLS.Credential import Wire.API.User.Auth (CookieLabel) import Wire.API.User.Client.Prekey as Prekey @@ -441,12 +444,15 @@ data Client = Client clientCookie :: Maybe CookieLabel, clientLocation :: Maybe Location, clientModel :: Maybe Text, - clientCapabilities :: ClientCapabilityList + clientCapabilities :: ClientCapabilityList, + clientMLSPublicKeys :: MLSPublicKeys } deriving stock (Eq, Show, Generic, Ord) deriving (Arbitrary) via (GenericUniform Client) deriving (FromJSON, ToJSON, Swagger.ToSchema) via Schema Client +type MLSPublicKeys = Map SignatureSchemeTag ByteString + instance ToSchema Client where schema = object "Client" $ @@ -460,6 +466,16 @@ instance ToSchema Client where <*> clientLocation .= maybe_ (optField "location" schema) <*> clientModel .= maybe_ (optField "model" schema) <*> clientCapabilities .= (fromMaybe mempty <$> optField "capabilities" schema) + <*> clientMLSPublicKeys .= mlsPublicKeysSchema + +mlsPublicKeysSchema :: ObjectSchema SwaggerDoc MLSPublicKeys +mlsPublicKeysSchema = + fmap + (fromMaybe mempty) + ( optField + "mls_public_keys" + (map_ base64Schema) + ) modelClient :: Doc.Model modelClient = Doc.defineModel "Client" $ do @@ -594,7 +610,9 @@ data NewClient = NewClient newClientCookie :: Maybe CookieLabel, newClientPassword :: Maybe PlainTextPassword, newClientModel :: Maybe Text, - newClientCapabilities :: Maybe (Set ClientCapability) + newClientCapabilities :: Maybe (Set ClientCapability), + newClientMLSPublicKeys :: MLSPublicKeys, + newClientVerificationCode :: Maybe Code.Value } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform NewClient) @@ -696,6 +714,8 @@ instance ToSchema NewClient where ) <*> newClientModel .= maybe_ (optField "model" schema) <*> newClientCapabilities .= maybe_ capabilitiesFieldSchema + <*> newClientMLSPublicKeys .= mlsPublicKeysSchema + <*> newClientVerificationCode .= maybe_ (optField "verification_code" schema) newClient :: ClientType -> LastPrekey -> NewClient newClient t k = @@ -708,7 +728,9 @@ newClient t k = newClientCookie = Nothing, newClientPassword = Nothing, newClientModel = Nothing, - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } -------------------------------------------------------------------------------- @@ -719,12 +741,23 @@ data UpdateClient = UpdateClient updateClientLastKey :: Maybe LastPrekey, updateClientLabel :: Maybe Text, -- | see haddocks for 'ClientCapability' - updateClientCapabilities :: Maybe (Set ClientCapability) + updateClientCapabilities :: Maybe (Set ClientCapability), + updateClientMLSPublicKeys :: MLSPublicKeys } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform UpdateClient) deriving (FromJSON, ToJSON, Swagger.ToSchema) via Schema UpdateClient +defUpdateClient :: UpdateClient +defUpdateClient = + UpdateClient + { updateClientPrekeys = [], + updateClientLastKey = Nothing, + updateClientLabel = Nothing, + updateClientCapabilities = Nothing, + updateClientMLSPublicKeys = mempty + } + instance ToSchema UpdateClient where schema = object "UpdateClient" $ @@ -751,6 +784,7 @@ instance ToSchema UpdateClient where schema ) <*> updateClientCapabilities .= maybe_ capabilitiesFieldSchema + <*> updateClientMLSPublicKeys .= mlsPublicKeysSchema modelUpdateClient :: Doc.Model modelUpdateClient = Doc.defineModel "UpdateClient" $ do diff --git a/libs/wire-api/src/Wire/API/User/Profile.hs b/libs/wire-api/src/Wire/API/User/Profile.hs index 5327fce2975..692220636aa 100644 --- a/libs/wire-api/src/Wire/API/User/Profile.hs +++ b/libs/wire-api/src/Wire/API/User/Profile.hs @@ -69,6 +69,7 @@ import qualified Data.Swagger.Build.Api as Doc import qualified Data.Text as Text import Imports import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) +import Wire.API.Asset (AssetKey (..)) import Wire.API.User.Orphans () -------------------------------------------------------------------------------- @@ -106,7 +107,7 @@ defaultAccentId = ColourId 0 -- Note: Intended to be turned into a sum type to add further asset types. data Asset = ImageAsset - { assetKey :: Text, + { assetKey :: AssetKey, assetSize :: Maybe AssetSize } deriving stock (Eq, Show, Generic) diff --git a/libs/wire-api/src/Wire/API/User/Scim.hs b/libs/wire-api/src/Wire/API/User/Scim.hs index 45b3597f31d..817a8ca252e 100644 --- a/libs/wire-api/src/Wire/API/User/Scim.hs +++ b/libs/wire-api/src/Wire/API/User/Scim.hs @@ -54,6 +54,7 @@ import qualified Data.Binary.Builder as BB import Data.ByteArray.Encoding (Base (..), convertToBase) import Data.ByteString.Conversion (FromByteString (..), ToByteString (..)) import qualified Data.CaseInsensitive as CI +import Data.Code as Code import Data.Handle (Handle) import Data.Id (ScimTokenId, TeamId, UserId) import Data.Json.Util ((#)) @@ -84,7 +85,6 @@ import qualified Web.Scim.Schema.Schema as Scim import qualified Web.Scim.Schema.User as Scim import qualified Web.Scim.Schema.User as Scim.User import Wire.API.Arbitrary (Arbitrary, GenericUniform (..)) -import Wire.API.User.Activation import Wire.API.User.Identity (Email) import Wire.API.User.Profile as BT import qualified Wire.API.User.RichInfo as RI @@ -335,13 +335,21 @@ data ValidExternalId | EmailOnly Email deriving (Eq, Show, Generic) --- | Take apart a 'ValidExternalId', using 'SAML.UserRef' if available, otehrwise 'Email'. -runValidExternalId :: (SAML.UserRef -> a) -> (Email -> a) -> ValidExternalId -> a -runValidExternalId doUref doEmail = \case +-- | Take apart a 'ValidExternalId', using 'SAML.UserRef' if available, otherwise 'Email'. +runValidExternalIdEither :: (SAML.UserRef -> a) -> (Email -> a) -> ValidExternalId -> a +runValidExternalIdEither doUref doEmail = \case EmailAndUref _ uref -> doUref uref UrefOnly uref -> doUref uref EmailOnly em -> doEmail em +-- | Take apart a 'ValidExternalId', use both 'SAML.UserRef', 'Email' if applicable, and +-- merge the result with a given function. +runValidExternalIdBoth :: (a -> a -> a) -> (SAML.UserRef -> a) -> (Email -> a) -> ValidExternalId -> a +runValidExternalIdBoth merge doUref doEmail = \case + EmailAndUref eml uref -> doUref uref `merge` doEmail eml + UrefOnly uref -> doUref uref + EmailOnly em -> doEmail em + veidUref :: Prism' ValidExternalId SAML.UserRef veidUref = prism' UrefOnly $ \case @@ -369,7 +377,7 @@ data CreateScimToken = CreateScimToken -- | User password, which we ask for because creating a token is a "powerful" operation createScimTokenPassword :: !(Maybe PlainTextPassword), -- | User code (sent by email), for 2nd factor to 'createScimTokenPassword' - createScimTokenCode :: !(Maybe ActivationCode) + createScimTokenCode :: !(Maybe Code.Value) } deriving (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform CreateScimToken) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs index 798a88c877d..53e023875f9 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs @@ -189,7 +189,6 @@ import qualified Test.Wire.API.Golden.Generated.ServiceToken_provider import qualified Test.Wire.API.Golden.Generated.Service_provider import qualified Test.Wire.API.Golden.Generated.SimpleMember_user import qualified Test.Wire.API.Golden.Generated.SimpleMembers_user -import qualified Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user import qualified Test.Wire.API.Golden.Generated.TeamBinding_team import qualified Test.Wire.API.Golden.Generated.TeamContact_user import qualified Test.Wire.API.Golden.Generated.TeamConversationList_team @@ -231,6 +230,7 @@ import qualified Test.Wire.API.Golden.Generated.UserSSOId_user import qualified Test.Wire.API.Golden.Generated.UserUpdate_user import qualified Test.Wire.API.Golden.Generated.User_2eProfile_2eAsset_user import qualified Test.Wire.API.Golden.Generated.User_user +import qualified Test.Wire.API.Golden.Generated.VerificationAction_user import qualified Test.Wire.API.Golden.Generated.VerifyDeleteUser_user import qualified Test.Wire.API.Golden.Generated.ViewLegalHoldServiceInfo_team import qualified Test.Wire.API.Golden.Generated.ViewLegalHoldService_team @@ -1205,9 +1205,10 @@ tests = testObjects [(Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_1, "testObject_TeamSearchVisibility_team_1.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_2, "testObject_TeamSearchVisibility_team_2.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_3, "testObject_TeamSearchVisibility_team_3.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_4, "testObject_TeamSearchVisibility_team_4.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_5, "testObject_TeamSearchVisibility_team_5.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_6, "testObject_TeamSearchVisibility_team_6.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_7, "testObject_TeamSearchVisibility_team_7.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_8, "testObject_TeamSearchVisibility_team_8.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_9, "testObject_TeamSearchVisibility_team_9.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_10, "testObject_TeamSearchVisibility_team_10.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_11, "testObject_TeamSearchVisibility_team_11.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_12, "testObject_TeamSearchVisibility_team_12.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_13, "testObject_TeamSearchVisibility_team_13.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_14, "testObject_TeamSearchVisibility_team_14.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_15, "testObject_TeamSearchVisibility_team_15.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_16, "testObject_TeamSearchVisibility_team_16.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_17, "testObject_TeamSearchVisibility_team_17.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_18, "testObject_TeamSearchVisibility_team_18.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_19, "testObject_TeamSearchVisibility_team_19.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibility_team.testObject_TeamSearchVisibility_team_20, "testObject_TeamSearchVisibility_team_20.json")], testGroup "Golden: TeamSearchVisibilityView_team" $ testObjects [(Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_1, "testObject_TeamSearchVisibilityView_team_1.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_2, "testObject_TeamSearchVisibilityView_team_2.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_3, "testObject_TeamSearchVisibilityView_team_3.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_4, "testObject_TeamSearchVisibilityView_team_4.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_5, "testObject_TeamSearchVisibilityView_team_5.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_6, "testObject_TeamSearchVisibilityView_team_6.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_7, "testObject_TeamSearchVisibilityView_team_7.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_8, "testObject_TeamSearchVisibilityView_team_8.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_9, "testObject_TeamSearchVisibilityView_team_9.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_10, "testObject_TeamSearchVisibilityView_team_10.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_11, "testObject_TeamSearchVisibilityView_team_11.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_12, "testObject_TeamSearchVisibilityView_team_12.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_13, "testObject_TeamSearchVisibilityView_team_13.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_14, "testObject_TeamSearchVisibilityView_team_14.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_15, "testObject_TeamSearchVisibilityView_team_15.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_16, "testObject_TeamSearchVisibilityView_team_16.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_17, "testObject_TeamSearchVisibilityView_team_17.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_18, "testObject_TeamSearchVisibilityView_team_18.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_19, "testObject_TeamSearchVisibilityView_team_19.json"), (Test.Wire.API.Golden.Generated.TeamSearchVisibilityView_team.testObject_TeamSearchVisibilityView_team_20, "testObject_TeamSearchVisibilityView_team_20.json")], - testGroup "Golden: SndFactorPasswordChallengeAction_user" $ + testGroup "Golden: VerificationAction_user" $ testObjects - [ (Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user.testObject_SndFactorPasswordChallengeAction_user_1, "testObject_SndFactorPasswordChallengeAction_user_1"), - (Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user.testObject_SndFactorPasswordChallengeAction_user_2, "testObject_SndFactorPasswordChallengeAction_user_2") + [ (Test.Wire.API.Golden.Generated.VerificationAction_user.testObject_VerificationAction_user_1, "testObject_VerificationAction_user_1"), + (Test.Wire.API.Golden.Generated.VerificationAction_user.testObject_VerificationAction_user_2, "testObject_VerificationAction_user_2"), + (Test.Wire.API.Golden.Generated.VerificationAction_user.testObject_VerificationAction_user_3, "testObject_VerificationAction_user_3") ] ] diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/AddBotResponse_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/AddBotResponse_user.hs index 93f6f0ade00..25ab06033c6 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/AddBotResponse_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/AddBotResponse_user.hs @@ -24,6 +24,7 @@ import Data.Id (BotId (BotId), ClientId (ClientId, client), Id (Id)) import Data.Qualified import qualified Data.UUID as UUID (fromString) import Imports (Maybe (Just, Nothing), fromJust, read, (.)) +import Wire.API.Asset import Wire.API.Conversation import Wire.API.Conversation.Bot import Wire.API.Conversation.Typing @@ -41,7 +42,14 @@ testObject_AddBotResponse_user_1 = "\77844\129468A\1061088\30365\142096\40918\USc\DC3~0g\ENQr\v\29872\f\154305\1077132u\175940.\1018427v\v-/\bi\bJ\ETXE3\ESC8\53613\1073036\&0@\14466\51733;\27113\SYN\153289\b&\ae]\1042471H\1024555k7\EMJ\1083646[;\140668;J^`0,B\STX\95353N.@Z\v\ENQ\r\19858|'w-\b\157432V\STX \GSW|N\1072850\&3=\22550K245\DC1\142803\168718\7168\147365\ETX" }, rsAddBotColour = ColourId {fromColourId = -3}, - rsAddBotAssets = [ImageAsset "7" Nothing, ImageAsset "" (Just AssetPreview)], + rsAddBotAssets = + [ ImageAsset + (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) + Nothing, + ImageAsset + (AssetKeyV3 (Id (fromJust (UUID.fromString "034efa97-f628-450e-b212-009801b1470b"))) AssetExpiring) + (Just AssetPreview) + ], rsAddBotEvent = Event (Qualified (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000200000003"))) (Domain "faraway.example.com")) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/BindingNewTeamUser_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/BindingNewTeamUser_user.hs index 1eb13e0c5ca..37dc8807bf7 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/BindingNewTeamUser_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/BindingNewTeamUser_user.hs @@ -18,10 +18,14 @@ module Test.Wire.API.Golden.Generated.BindingNewTeamUser_user where import Data.Currency (Alpha (XUA)) +import Data.Id (Id (..)) import Data.Range (unsafeRange) -import Imports (Maybe (Just, Nothing)) +import Data.UUID as UUID +import Imports (Maybe (Just, Nothing), fromJust) +import Wire.API.Asset import Wire.API.Team ( BindingNewTeam (BindingNewTeam), + Icon (..), NewTeam ( NewTeam, _newTeamIcon, @@ -41,9 +45,7 @@ testObject_BindingNewTeamUser_user_1 = { _newTeamName = unsafeRange "\fe\ENQ\1011760zm\166331\&6+)g;5\989956Z\8196\&41\DC1\n\STX\ETX%|\NULM\996272S=`I\59956UK1\1003466]X\r\SUBa\EM!\74407+\ETXepRw\ACK\ENQ#\127835\1061771\1036174\1018930UX\66821]>i&r\137805\1055913Z\1070413\&6\DC4\DC4\1024114\1058863\1044802\ESC\SYNa4\NUL\1059602\1015948\123628\tLZ\ACKw$=\SYNu\ETXE1\63200C'\ENQ\151764\47003\134542$\100516\1112326\&9;#\1044763\1015439&\ESC\1026916k/\tu\\pk\NUL\STX\1083510)\FS/Lni]Q\NUL\SIZ|=\DC1V]]\FS5\156475U6>(\17233'\CAN\179678%'I1-D\"\1098303\n\78699\npkHY#\NUL\1014868u]\1078674\147414\STX\USj'\993967'\CAN\1042144&\35396E\37802=\135058Da\STX\v\1100351=\1083565V#\993183\RS\FSN#`uny\1003178\1094898\&53#\DEL/|,+\243pW\44721i4j", - _newTeamIcon = - unsafeRange - "Coq\52427\v\182208\&7\SYN\\N\134130\8419h3 \30278;X\STX\a\a$|D\NUL\SOHh'\62853\&3-m7\1078900\SOp\22214`\1093812\&6QF\CAN\SOH9\1062958\ETB\15747FP;lm\1075533\173111\134845\22570n:\rf\1044997\\:\35041\GS\GS\26754\EM\22764i\991235\ETXjj}\1010340~\989348{; \119085\1006542\SUBL&%2\170880;@\\2`gA\984195\&0\162341\&2\163058h\FSuF\DC4\17376\ESC\GS\SO\vYnKy?v\129546H\fcLdBy\170730\&4I\1108995i\1017125\ETBc6f\v\SOH\DC3\1018708ce\1083597\SOs3L&", + _newTeamIcon = DefaultIcon, _newTeamIconKey = Just ( unsafeRange @@ -64,7 +66,7 @@ testObject_BindingNewTeamUser_user_2 = { _newTeamName = unsafeRange "G\EOT\DC47\1030077bCy\83226&5\"\96437B$\STX\DC2QJb_\15727\1104659Y \156055\1044397Y\1004994g\v\991186xkJUi\1028168.=-\1054839\&2\1113630U\ESC]\SUB\1091929\DLE}R\157290\DC1\1111740\1096562+R/\1083774\170894p(M\ENQ5Fw<\144133E\1005699R\DLE44\1060383\SO%@FPG\986135JJ\vE\GSz\RS_\tb]0t_Ax}\rt\1057458h\DC3O\ACK\991050`\1038022vm-?$!)~\152722bh\RS\1011653\1007510\&0x \1092001\1078327+)A&mRfL\1109449\ENQ\1049319>K@\US\1006511\ab\vPDWG,\1062888/J~)%7?aRr\989765\&4*^\1035118K*\996771\EM\"\SO\987994\186383l\n\tE\136474\1037228\NAK\a\n\78251c?\\\ENQj\"\ESCpe\98450\NUL=\EM>J", - _newTeamIcon = unsafeRange "\SUB4\NAKF", + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal), _newTeamIconKey = Just ( unsafeRange diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/BindingNewTeam_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/BindingNewTeam_team.hs index 95ec210ac87..5b2eae3c44a 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/BindingNewTeam_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/BindingNewTeam_team.hs @@ -19,10 +19,14 @@ module Test.Wire.API.Golden.Generated.BindingNewTeam_team where +import Data.Id (Id (Id)) import Data.Range (unsafeRange) -import Imports (Maybe (Just, Nothing)) +import qualified Data.UUID as UUID (fromString) +import Imports (Maybe (Just, Nothing), fromJust) +import Wire.API.Asset (AssetKey (..), AssetRetention (..)) import Wire.API.Team ( BindingNewTeam (..), + Icon (..), NewTeam ( NewTeam, _newTeamIcon, @@ -40,10 +44,7 @@ testObject_BindingNewTeam_team_1 = ( unsafeRange ("UivH&\54922\98185p\USz\11724\r$\DC4j9P\r\"\1070851\3254\986624aF>E\1078807\139041B\EM&\1088459\DC4\174923+'\1103890R;!\GS\1017122\SIvv|\rmbGHz\1005234\95057\&3h\120904\\U|'\ETX;^&G\CAN\f\41076\&42\teq\1049559\SOV1}\RSaT\1014212aO7<;o\179606\f\1111896m)$PC\ESC7;f{\STXt\9533>\EOTX@4|/\tH\ENQ/D\144082\EM\121436C\99696Q\ENQT\1096609?d\ACK\1073806#H\127523\139127*\166004jo\1079041\74636t\n)1/% hL\DC2Ad\SOHXq6\DC1)\NUL\f6\fV\DC4r\1097128\DC1n\1107359,@\171217\118996\n\SUB%N\176824\ACK\33856Xv)\SYNz?\DC4\EMY\162050\&2\95792um8}\51420\DC2yW\NULHQ\ENQD[Fe\nk\999106\EM\25079Yk@##u}j\169850\153342\STXq\ESCir7) \27756%\1016104~\993971\&8\1085984je\1099724\&0*Gi3\120829je\CANQr>\1033571k1\63774c\1031586L\1015084\93833t\EOTW\999363\SUBo\fgh\ACK\172057C2\38697c\SUB)uW\r\fB\1042942Sf\SUB\SOH*5l\38586\SI\25991\EMB(\ENQ\133758/)!{\1006380\&9\STXA\DEL\16077fx&\180089T&\187029\DC4\52222[\r\v\n\1071241j2\166180/\1086576\ENQQo\fj\134496\129296\nb6\CAN3\RS9\EM\1000086ub\ETB3CY\GSsIz") ), - _newTeamIcon = - ( unsafeRange - (":b6\99159L\1054777K<\1108399\94965C^\SOY8LuXS\156657F\SOH\ETB\49985\1023127\US\1042782J]\ETBA2R$P6\\\ENQ\168330-J\NULt\SI\1047279*\1030368r\1078088M\NAKPL)\94751\SUBJ\72794S\1103633~'kRYj\71198{P{V\SOH!\986239Y~\vV\t\150530{5%i[$7X:ZV\NULy\a\49289\SYN\ENQ\f;!k?O\1029799\1082505K:}\1097011\174391#>\\|(n\STX\165884\ETXD\150060\52125{|\151751\987438+\7022J") - ), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "3d4b563b-016c-49da-bad2-876ad0a5ecd2"))) AssetExpiring), _newTeamIconKey = Just ( unsafeRange @@ -97,10 +92,7 @@ testObject_BindingNewTeam_team_4 = BindingNewTeam ( NewTeam { _newTeamName = (unsafeRange ("\ETX\1006830k;")), - _newTeamIcon = - ( unsafeRange - ("tmIV\999157\&3\1101401\1048401HKE{\1076066\58364R9\EOT\ETBE2\68432L\1009801|a\b|C\41601\1054883}\GS\1031260\CANO9$)\NUL\STX\153170\32951\NUL\50288K\1045065\1057248d\25051\GS\vq\1025447B\ETX\aT\SOPt\DELY\NAK\1047285\28492z#k`P\1056790\STX_7LXC]`#-`\ACK\2234\DC2pw59\n\SUB\DLEj\41795\184841F\ETB\1071805\t;\DC2r*\22318\EMS\182587E8e\FSx\1091470A\SUB|j\ad\DLE0RDI\rQ\NAK`\1072156\EM)j{\1062530\142892\1100137f@\t\DC1\f\"\f\1043821\1099467\1105685\6302J`.\110675*\bF%4\1009644\EM-3g\100340\1103476\1101833\STX\n\DLE\DC3]s\178911_\a\1041598\162772\DC2\v\515\DLE\15612v|\b\1052391%\165476g~0\EOTWp\t \173242C\FS\149826x\ENQ\ACK\DC2-DIYa\1004333:\189759'{\1083009\143586\1041756\&6\7690\1000132=xfo\ACKU'\DC4\1102469t\n\DC2\1095515\&6\119535/\1103539tR\SYN#\1109833") - ), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "18fef3b3-323c-4c27-bb62-6026d62f77c3"))) AssetExpiring), _newTeamIconKey = Nothing, _newTeamMembers = Nothing } @@ -114,10 +106,7 @@ testObject_BindingNewTeam_team_5 = ( unsafeRange ("\SIB\63473\70352\&3\158300\\#\1061243I\USK\1023384h\DLE\DC3\RS\134858*\998726L\STXw1p\"4\EOT\188995#u\144798\SYN\FS\37122\41625$\376wu\174004\45347\1008628\SUB\a)\DC2?T \49542\&8\39359.\t\NAK1\f\EOTY\128585%\1929\64376\15282 &Z4\1085196\NUL@\SOH\EM\154601\1078303\1098851xtj\182717`\SOH\59489r\r\94098\EOT0\EM\NULLyc D\FS\1085075\1054974)\SUB-\SO\1085196\vl\984336\59601\1114081\CAN\61540\186940f\SIrb/[F\NUL\1099974<1\1027888P\GSxl\"!11E\ESC0\ESC\f$u\1093437N\GSV^\1017313q\170667\n`\1047440\163252:iLXn\CAN\988958\26427O}8!Y\NAK,^X\63902*\\`\38784\DC3\a]\144700\1021165{FKA\b\1102136\178719\DC3\US=UC\179701KQb\1054842\1015593\FSX7@\996116C~-[Db $cx\ETXCzvq ,g\rD\1032170") - ), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal), _newTeamIconKey = Just ( unsafeRange @@ -135,10 +124,7 @@ testObject_BindingNewTeam_team_6 = ( unsafeRange ("v\188076hEWefuu\1006804jPx\158137k#\SOH\986725\STX\ETX^\ESC\n\CAN\8325p1D|S1\1064991\1102106\29079\SYN`\t0g\1034469,t\FSw\fDT\RS#H\SOH\145176\US{\1091499\1025650\984364lW\a,uil\SIN`5e:\SYN Y!\SYN\1025115tb\1085213") ), - _newTeamIcon = - ( unsafeRange - ("l\145301\30725\US@/D\t\145930\1019910^f\NAK\DC4[\127098\1002727)\1097841W\1035417\&2\58863\111330\n#\138666|/I\rI\NUL\US;\n\v\b\ESC\SO\b}\1060666Q\168368\&45^mI\10814\138738\1110238k\SO\1071853d0iA7\DC3\rW\985090\1041247Z\166371T\46392u\EOT\SOX\"\996646") - ), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "d7a467c6-8cd4-40cb-9e30-99b64bb11307"))) AssetEternal), _newTeamIconKey = Just ( unsafeRange @@ -156,7 +142,7 @@ testObject_BindingNewTeam_team_7 = ( unsafeRange ("\145552\1042892iz\1057971FT\14964;\1108369}\188917\1113471\&9\SO\991633\&7>hAC\NULH2O\177259m\187711\&2R(?W,=,\990725M\992456\aM\194790\SUB\47600q\SOlj\EOTj^.s~\rY%5lM,\26492=\ACK\1016899\188843>{\CAN\DLE\15878f=X9\SYN9\51145\159419TI4\17599\v\NAK6\1014936/\DLE\NAK\ACK\23564H<\ENQ\1029703e\ENQz\1017528:\6137\"rS\a\167660\FS\ETX\1059289\1031786\49012\DC4\DC4Q\"\1065200\&1:\1097556\UST.;\1042663\18380}") ), - _newTeamIcon = (unsafeRange ("_5\63791\aQ\1109163W\8411\&9\43942*\1003379")), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "b199431c-e2ee-48c6-8f1b-56726626b493"))) AssetEternal), _newTeamIconKey = Just ( unsafeRange @@ -174,10 +160,7 @@ testObject_BindingNewTeam_team_8 = ( unsafeRange ("YwD\1023517r\NAK}\1083947\ACK\1047823\29742\EOT\1071030iI5g\1012255\t\"r\150087O\DC4?\53005\1100290\1108960\NUL\1060304qgg\DC1X)\NULL\1054528\CAN{\v4\NUL\93999\bvD#\1035811$aYFk\b\1102040\1089491\1042733\47133:1\179810S7\66745V)\1072087\v\96989\&3#\b\1104899c\27119Q/jPy\1015620P@Df\997914\51756H\1113361Xr\SO\ETB3%\1108760aF@3A\SI\ETB\STX mj9T=\DC3'XI\DC2?0\1093231\156858VHp?\1066163YU\42092\33083\72810,)\1113424\ETX96\153338z\42445/4T\136162\ESC\60427\1086321&\ETBS\1098748\14578z[\54638Z\DC2\"e\SUB\173931&rQ\fJG\100066\180037\155435s$\SUB$\50544S\162554E\ETX*\t+\63443WU*\144654\1042128\&8\NAK\999184a\t\EM\1097907_\DELOD\1006385/\23998\1100140SmfX") ), - _newTeamIcon = - ( unsafeRange - ("\9989IT\RS\DEL\1102950\49046%mE\985354\1051208M\a:[fgw\ESC?ye\70096\1096815K\ETBl\1054491\1013010\1037243Qg?\151299{\f!uXA}H)v$\160467\1093098mGkC9\1084965\GS\1092746\1034992\&1DH\166476s\42306\&7[.\187974v+\1091910O^SGjv\1069550Y\1003173n\154396Z\78347\SO{\1060523Q\ETX\RS1I\FS5\ACK\1035980\1083718\1031418_\1105769?gD\4285\fg\15008:g\FS.J<\DC2\ETB\1016548}/\97909\DELOBLa\1051864\a{\1011185-\1064859\v\SI\DLE\145744V \\Ns0t\1081561frR!Qe!$\140066\1007621\SYN$\1077998!SZ\1052672\184571a\DLEB3") - ), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal), _newTeamIconKey = Just ( unsafeRange @@ -195,10 +178,7 @@ testObject_BindingNewTeam_team_9 = ( unsafeRange ("\SOLN\GSr\144312\1070871k\181829#\153181lTD[Jh\SOH\1029451\34144\&6\1034706\1062880\NAK}\adb\171356-\\-1\DC42\1046344\DC2\78894\&1/\33084b:\ENQ\1038950;Mw\FS\183866\1113547ITuy\1050264`SP\SOH\SO\GS\NAK\a\r7M\1069326\1064150\18615\n\SYN3V\ETXR\n1$e.\1096261B~yd_z\1047817\rV\1091351\RS\SYN\165050l\DC3\47200u\1058674u\"\aTc|sEw\1011190wTC|F\4735B\t\DC4&\bUEN(+M\SOF;\1099746\134573\EM20\nrPW\1017058$\1064809") ), - _newTeamIcon = - ( unsafeRange - ("i\DLEi|\7196\ETB\1025523\NUL\7866\NULD'\94469Zby\1039174\8663Y\r\1019969\\n\21729\168347>\999971\&6\140502\1039443\37325\1016989|`dN \25830\987680i_\1055665p\1033233J\26000ngp@#\1105667A\988593\&7l{;\180315g4EX\45324\ESC\1068230\151220#z\176275\SYNy\EOT\t\60992G\\\35606\22388#s\1005062Ad\1094868Obh\1008737\DC2\1086579\57353)3R\SUB\FS\97513aw]\1075127\1039396\4324\118950kC\26815\SOH\1002881\1005712d8\46181\DC1\SYN\64570f9") - ), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal), _newTeamIconKey = Just ( unsafeRange @@ -213,10 +193,7 @@ testObject_BindingNewTeam_team_10 = BindingNewTeam ( NewTeam { _newTeamName = (unsafeRange ("\b \SOH+\1056054;\t095\42390\n\STX2J\1002251\DC1UzD_\1110746\FS")), - _newTeamIcon = - ( unsafeRange - ("L\1093530j^Xe\DC1\1012858z1{\ENQ\1040620,\1021288\DELB;&\b54f>8+\168322\n\NUL5L[r7_.\DLEl1\176221\US6[EhD\USh\a\1095183\NAKC\139765,\177058,@?y+;\US`/^d\137830\991658HeCd\39028\DC3\1050220\143767\ENQ2O-Hijo\1007768\1045537\1013046X}x\1112190\ETB7\146170\53218C\30013\DLE`k*Gl\1046094\t\77870\1003758\rb}\b\NUL\33414h\SYNynr6d(") - ), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal), _newTeamIconKey = Just ( unsafeRange @@ -234,10 +211,7 @@ testObject_BindingNewTeam_team_11 = ( unsafeRange ("\48005H\1082536\132304\157763\&5\RS\986337-\NAK\ESCR\nL\63954&bD\139428\SUBH\US\1040918\f\t;e\1064224\47101\tc\1087740e\1099415\DLE\ETX\DELI\65746\ETB\133884\SUB \SI\43795~FE\CAN6\162836\DEL\46062u\"\135684\1041611\FSFYI\t/{\ENQ\RS]j\1076782\US22\15884l\42366$\ETB\US\180023kL{\STX*\131382RMj\ESC\1091332W3H\1020399\FS\NAK^\"5\29653\32539*\1099111") ), - _newTeamIcon = - ( unsafeRange - ("r\53037\22164\72341u*33{8G\1084742aO\GS%\NUL[\1003678U\ENQ\985387iKL\GSNF\163860\1098258`\EOTT$`i5\92401\ESC\356\&98>") - ), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal), _newTeamIconKey = Just ( unsafeRange @@ -255,10 +229,7 @@ testObject_BindingNewTeam_team_12 = ( unsafeRange (";\110872M\EOT\164161P]'\1041089\1094514\4118\1054714iFnRQV\43238@\992926\59902l\1099067\aKZ{\51124S\190890\fg*\n,`!V\STX\991695e'\1039967\SO0\37019p4d\STXs\1020471uK(c'\52929hjB\144953\SOt'h^\SYN\SYN0\1009487_\12064\166805thH\SI\1073479:\1019934l; n4c\1101781D[\1014388\&8Y+\1092407\EOTE\1058506\\0\168273KKTc)P1K\1042475\990753W\ETX<|\24888\&0|5{Y\986771M\DC4\vK\DLE\1089150\SOH\DC4\1013653.\ETBg\991717\DLE\"W\NUL9&0yYZ\1094524\v\11606\58174") ), - _newTeamIcon = - ( unsafeRange - ("\1060773b\EOTch1a iuB\1011795\ESC\DC1\187488\STXV\1046981@1\145144 iY\"m\1036630\SO\\O*(\DC3\178375w7w\1105638\US2ECk\99276\&4\ETX\2892%\EOT@\"k\1013020\993468*\1031577\DC1\ESCYC?\177240\&2>\52504\SYNo8[\aV@9E\1085704\9349\1018050!k0b\t\n+6\\Ki{\DC1\984398\SOH\SYN\1037253{0H\1017165\DC4\ENQ\992581|\CAN\1023332F\37419\1012389d\1045535\n\119327\1054972X\1056743\1052679*\bC\DC2\99475Wz\1070783\37601QWt\ETBAj>eO}Ae\DEL\DC4\94565 \STX\39311\SIF\tAH\160873:\167531\&8\16727Q\1045659\DLE-A\EOT\156588\ESCHOc\v\1007234\DC39\SYN\917845{ \NUL]\1008784I\1054187,\162782f\DC2\1088424q\SOH\129571GA\GST\EOTS\DLEMmnLy\1113365=\131110\&2k\1010420\r~\1059431\&1! \ESCJy\9820J|zgf1}ILN5\1024553Xi\6845Be.\1085513\RS\SYN\95106\1090727\RS+Y;Z$\SO5") - ), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal), _newTeamIconKey = Just ( unsafeRange @@ -297,10 +265,7 @@ testObject_BindingNewTeam_team_14 = ( unsafeRange ("2#\DC2N\b9&A\1030886ZL{f\1011542M\1101172\23517\a\DELv\164961\32470\ACKT7\DC3\DC4\1009557O\1103393C\152202\t\DC4l\RS\SOH]\ESC\ACK\95718X;\149660* &\97401}\1111236T\ESCCLkx,\DLE\63803\nbT\1049269fWJ\992800\136973a\US`\DC3\139728\28948\&8r2']\NAK\DC2\133094\nl\DC2NXB\ENQia\1068046]B\989632\DLE\ENQdf#\64677\t6g\FS\SOH\1029760Fp(\GSQTZ\1015396\8630\153801dUJt\SI\EM\194705`\\#g0Qed@a${=Q.\1048388Ld`\35027 \173216sV\SUB\SO5\150360\41997\1107813i\EM\DC3\988956\1049486\SOH\1030355>\1044179\DC3w\1001979Y}\21603\&1q\NAKY:\25626q \ETB=*#\74975\EM\61277\\\21887y9Tfc\DC1\49327k\1096646\\Oxxn&6NtaZ?k:5G@\46350\DC3H\1097149hu4\178807\995883\USR\161801\1024517v\26381\23905\72161\12881\ACKD\985152[bb<\1111873") ), - _newTeamIcon = - ( unsafeRange - ("(r'`\1072313Gd\ETX)u\fq*4\1076827\1031167i\EMM.Ru\1079085\t\148161\1088689m\58073;\f\1001367\1114096\135422m\a\t\1066705wRhv\ar6\64764\";&4\SYNX\GS\SYNw5{%P&\26997\&98\ETX\STXyL\t\1022691XMu(\187856\&6)\SUB\42223Wc\38363#\STX\22842l\1064183Vy\59354\&1_\SUB\\\CAN\1103008\44889Ek>\1005303\ACK]$\EOT\181821\1076265w\r\164187@&V8\a\DLE\DC1\DEL\\\1010644\1053996\NAK)X\nE)\1071775y*%1\NAK\DC2^4hKf\995698|EY`^\DC3\173547\58719\39061\"pX\bg\49396>YR(W\\eS\GS\27701(bn\SYNu\1001078\156831h\41346\FSG\1090155\DLE\34585a\70001n2)\984812\"<\1032188YUq\1049152\1035363\186759\r7\61891\4004\v\1049233\24817\r\1027960|]\ETB\SOh9f\ACK1\bZQ7fmQOQ\986711l!\DC3\44018\27476*\43689*1\f\1097293\&8nk|\NAK\1005998~\fO\162989\100863!:3\ETXn{%\6663\182700if/!\29917] <\1056176Y\1078680\b\DC4~\t\EM\SOH<*\NAK\143397bx4 {\96203\CANVs;g\98929\144388\STXqkI!QJ\1072302J\189512\DC4\64545?_\STX\t\1082190iB3YdKA7@>Q\995699\987049]\1094644\133325>D\1026819wD\ESC|\SI'^\136789\120874Q#q,\"") ), - _newTeamIcon = - ( unsafeRange - ("UZ0\990600t+\167739\12589k|\DEL\DC1\SYN\EM_ch\t\1089768\13109\n\ETB\DC2,x\1106295,RE(,G\GS\"gA\NAKncdJ\162831+\t\1100097FP{v=\45227'e\STX9B#1\1075471\t.\1085742\140286\11416\FSUK\SOH\a;\STX\1078860\143696\&7B\ESC\NULD+\1036884\1094020`\SO\DEL>\ESC\DC1,\171231d\SOH(5\EOTP9iR]\RSNws\1029644\1003476{[H\128669,\DEL5J!\136454\1036165j\1053405eA\1046358\tbj\EMk\DC1l\n\988481H~]u\42907\1029099!kjVS{42\NULE?\EMh\61474\35112B!:\DLEX\DC1T\DEL3W\avimhK\1078443\DC1to*P*\DC1}\986362\1081249H\r\1034017B") ), - _newTeamIcon = - ( unsafeRange - ("_'C\1004343\EOT9\nC\92250f\a\118853Pe\58352\127931\185310\134048\98557\&5\1051407m<\r}\139827@\1053024#.\1058183\STX6%\1111877H%\1030001\60185n\EOTUk\US/\FS\DELpS\156872\DEL\1098001[1np\ACKmD\1011808U\GSI\1083494]I\49341\171558AT\ETX\USOl{\DC1\tYt7U +Q\1073293\CAN\US[\STXJV\EOT`\CANrF\tN\67855\95873\169991\SYN\166546*A\DLEA]4ku\1003232W\1111354\54858I\DC4\58521\SO\94687") - ), + _newTeamIcon = Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal), _newTeamIconKey = Nothing, _newTeamMembers = Nothing } @@ -352,10 +311,7 @@ testObject_BindingNewTeam_team_17 = ( unsafeRange ("|\36324P\US\1040589\159812Y\SOHj\RSYrr\49743\&0m\ENQ\1027954*'\72098\1105368P6\SYN\15236\f\DC2\125109e\1031690\RS\1026891\1003083\69946\rA'\GSA\NAK\53778\1067566J\1016490'T\1037603R2? \FS\US\1032454$\NAKGr(\1008673{\ENQ\62451\&0mJ\SID\STX-\CAN_I\132366\f\147665\FSR\1080205hp\143954B6W2\b\f6\1104867\DC2\180998\b1'7-T-#\3953D\1076345\1082129T]v$Gl\1042148\1032818\&5yg\1025280\nQc.`i\14819\24538}\FS&k4\99627\ACK>#\32013\1036954\EM\131987[vBOPu\1108963@\ACK\NUL\1087882\147841\SO\NAK\98755\31702\EOT\ETX&\1032348?z\989374i\fz\n\1029119\ETB3\a\1108955W\1113557E^\1043345\986117S3'4\ACK\74144*m-\ESC4\USj\ETX__6\1046371\6580M\48069\ESC]\EOTDq\DLEuo\28030$\vUWp1=/o\ETBY\173686\&9\DC2\nQ\177317\1051037)\1102455\"br\SOH\NUL\158808+\47718c^\1003405<`\1111751\149060\STX\986585\ETX\162139D\ENQ\30356nqp\1095539\988368c\RSt\1081319G") ), - _newTeamIcon = - ( unsafeRange - ("Zz]|\b>\NUL\163291\1063651\95675\SOH,q\SOH`\1050846P\987757\1069132\n\EOTIm\1067057\DC4\1013555\1075331HB\GS`\1022663\142641U\162403G\DC3m+Lw\DLE\1023911\"0\119625?-T\ESC\ETXj\DC3|Lq\ETX.\99032hgq\ESCy\99463\&5\ay\ACK\1111685w^(\ETB+\175870\&3\f\113727x\1007957i*E\DC3\179834rTM\CAN\1093029$\160784\190686\EOT\\p\1053146\&1\a\131095\1050682zv\1034137\EMrj\a\1043282u\NAKn\SO\164246\&6\ESCs\GSm9Y\FS%\DLE?6\1039335\STX\f\1063550)RST\r\DLE\1107040\DC1\1078521h\nTS\DC2\US\1097415\STXn\ESC\149926r2\73911\984427hrr-$*j\1110522\35321]@\1003869\15025J\b\"8N\68412e,\1005358\DC4\rSO8FXy\166833a\EM\170017\SUBNF\158145L\RS$5\NULk\RSz*s\148780\157980\v\175417\"SY\DEL\STX\994691\1103514ub5q\ENQ\1014299\vN.\t\183536:l\1105396\RS\1027721\a\168001\SO\vt\1098704W\SYN\1042396\1109979\a'v\ETB\64211\NAK\59538\STX \NAK\STX\49684,\1111630x\1047668^\1067127\27366I;\NAKb\1092049o\162763_\190546MME\1022528\SI\1096252H;\SO\ETBs\SO\1065937{Knlrd;\35750\DC4\SI\1075008TO\1090529\999639U\48787\1099927t\1068680^y\17268u$\DC1Jp\1054308\164905\164446\STX\"\1095399*\SO\1004302\32166\990924X\1098844\ETXsK}\b\143918\NUL0\988724\&12\171116\tM052\189551\EOT0\RS\986138\1084688{ji\ESC\1020800\27259&t \SI\ESCy\aL\136111\131558\994027\r\1054821ga,\DC4do,tx[I&\DC4h\DLE\ETX\DLEBpm\1002292-\a]/ZI\1033117q]w3n\46911e\23692kYo5\1090844'K\1089820}v\146759;\1018792\\=\41264\&8g\DLEg*has\44159\1006118\DC3\USYg?I\19462\NAKaW2\150415m\t}h\155161RbU\STX\ETBlz2!\DC3JW5\ESC\1026156U\SOg,rpO\5857]0\ESC\479\1005443F\SI\1045994\RS\SO\11908rl\1104306~\ACK+Mn{5\993784a\EM2\v{jM\ETBT\1058105$\DC1\1099974\GSj_~Z\1007141P\SOH\EOTo@TJhk\EOT\ETBk:-\96583[p\DLE\DC1\RS'\r\STXQ,,\1016866?H\rh\30225\rj\147982\DC2\\(u\ESCu\154705\1002696o\DC4\988492\1103465\1052034\DC1q\GS-\b\40807\DC1qW>\fys\8130,'\159954<" ) (Just (CookieLabel {cookieLabelText = "\1082362\66362>XC"})) - (Just (ActivationCode "123456")) + (Just (Value {asciiValue = (unsafeRange ((fromRight undefined (validate ("RcplMOQiGa-JY")))))})) testObject_Login_user_4 :: Login testObject_Login_user_4 = @@ -79,7 +81,7 @@ testObject_Login_user_5 = "\120347\184756DU\1035832hp\1006715t~\DC2\SOH\STX*\1053210y1\1078382H\173223{e\\S\SO?c_7\t\DC4X\135187\&6\172722E\100168j\SUB\t\SYN\1088511>HO]60\990035\ETX\"+w,t\1066040\ak(b%u\151197`>b\1028272e\ACKc\151393\1107996)\12375\&7\1082464`\186313yO+v%\1033664\rc<\65764\&2>8u\1094258\1080669\1113623\75033a\179193\NAK=\EOT\1077021\&8R&j\1042630\ESC\t4sj-\991835\40404n\136765\1064089N\GS\\\1026123\72288\&5\r\97004(P!\DEL\29235\26855\b\1067772Mr~\65123\EMjt>Z\GS~\140732A\1031358\SO\\>\DC16\">%\45860\1084751I@u5\187891\vrY\r;7\1071052#\1078407\1016286\CAN'\63315\1041397\EM_I_zY\987300\149441\EMd\1039844cd\DEL\1061999\136326Cp3\26325\GSXj\n\46305jy\44050\58825\t-\19065\43336d\1046547L\SUBYF\ACKPOL\54766\DC2\DC1\DC1\DC2*\rH\DLE(?\DC3F\25820\DLE\r]\1069451j\170177 @\ENQT\1100685s\FSF2\NAK]8\a\DC3!\NAKW\176469\1110834K\1025058\1112222_%\1001818\1113069'\1098149\70360(#\SOHky\t\ETB!\17570\NAK\DC4\ESC{\119317U2LS'" ) (Just (CookieLabel {cookieLabelText = "LGz%\119949j\f\RS/\SOH"})) - (Just (ActivationCode "123456")) + (Just (Value {asciiValue = (unsafeRange ((fromRight undefined (validate ("RcplMOQiGa-JY")))))})) testObject_Login_user_6 :: Login testObject_Login_user_6 = @@ -89,7 +91,7 @@ testObject_Login_user_6 = "K?)V\148106}_\185335\1060952\fJ3!\986581\1062221\51615\166583\1071064\a\1015675\SOH7\\#z9\133503\1081163\985690\1041362\EM\DC3\156174'\r)~Ke9+\175606\175778\994126M\1099049\"h\SOHTh\EOT`;\ACK\1093024\ENQ\1026474'e{\FSv\40757\US\143355*\16236\1076902\52767:E]:R\1093823K}l\1111648Y\51665\1049318S~\EOT#T\1029316\&1hIWn\v`\45455Kb~\ESC\DLEdT\FS\SI\1092141f\ETBY7\DEL\RS\131804\t\998971\13414\48242\GSG\DC3BH#\DEL\\RAd\166099g\1072356\1054332\SIk&\STXE\22217\FS\FS\FS$t\1001957:O\1098769q}_\1039296.\SOH\DC4\STX\157262c`L>\1050744l\1086722m'BtB5\1003280,t\"\1066340\&9(#\ENQ4\SIIy>\1031158\1100542\GSbf\"i\ETB\14367a\1086113C@\1078844\1092137\32415\NAK\999161\23344*N\SYN\ESC:iXibA\136851\169508q\1048663]:9r\63027\73801\NUL\1050763\USCN\US\147710\1048697\1016861eR\RSZbD5!8N\ESCV\7344\ACK\173064\SUBuz\1053950\188308~\ESC\SI%{3I/F\25232/DMS\US>o\187199\63000Z\1108766\GS[K\184801\94661\1088369\995346\ESCO-4\CAN\US\FSZp" ) (Just (CookieLabel {cookieLabelText = "\1014596'\998013KW\\\NUL\DC4"})) - (Just (ActivationCode "123456")) + (Just (Value {asciiValue = (unsafeRange ((fromRight undefined (validate ("RcplMOQiGa-JY")))))})) testObject_Login_user_7 :: Login testObject_Login_user_7 = @@ -99,7 +101,7 @@ testObject_Login_user_7 = "&\991818\1023244\83352\STXJ<-~\STX>\v\74228\151871\&5QN\53968\166184ql\NAK\74290\&3}{\DC3\173242S\22739;\t7\183958_F~D*f\1049940)\1067330-9\20699\&7GK= %\RS@kOF#\179945\1094401\124994\&8_\42309\GSL\37698\ETX\1047946\&0Wl1A`LYz\USy\20728\SUBo\ESC[\DC4\bt\66640a\ETXs~\USF\175140G`$\vG\DC1\1044421\128611/\1014458C>\SI" ) (Just (CookieLabel {cookieLabelText = "\SO\NAKeC/"})) - (Just (ActivationCode "123456")) + (Just (Value {asciiValue = (unsafeRange ((fromRight undefined (validate ("RcplMOQiGa-JY")))))})) testObject_Login_user_8 :: Login testObject_Login_user_8 = @@ -109,7 +111,7 @@ testObject_Login_user_8 = "z>\1088515\1024903/\137135\1092812\b%$\1037736\143620:}\t\CAN\1058585\1044157)\12957\1005180s\1006270\CAN}\40034\EM[\41342\vX#VG,df4\141493\&8m5\46365OTK\144460\37582\DEL\44719\9670Z\"ZS\ESCms|[Q%\1088673\ENQW\\\1000857C\185096+\1070458\4114\17825v\180321\41886){\1028513\DEL\143570f\187156}:X-\b2N\EM\USl\127906\49608Y\1071393\1012763r2.1\49912\EOT+\137561\DC3\145480]'\1028275s\997684\42805.}\185059o\992118X\132901\11013\r\SUBNq6\1019605'\fd\RS\14503\1097628,:%\t\151916\73955QD\1086880\ESC(q4KDQ2zcI\DLE>\EM5\993596\&1\fBkd\DC3\ACK:F:\EOT\100901\11650O N\FS,N\1054390\1000247[h\DEL9\5932:xZ=\f\1085312\DC3u\RS\fe#\SUB^$lkx\32804 \rr\SUBJ\1013606\1017057\FSR][_5\NAK\58351\11748\35779\&5\24821\1055669\996852\37445K!\1052768eRR%\32108+h~1\993198\35871lTzS$\DLE\1060275\"*\1086839pmRE\DC3(\US^\8047Jc\10129\1071815i\n+G$|\993993\156283g\FS\fgU3Y\119068\ACKf)\1093562\SYN\78340\1100638/\NULPi\43622{\1048095j\1083269\FS9\132797\1024684\32713w$\45599\126246)Si\167172\29311FX\1057490j{`\44452`\999383\159809\&4u%\1070378P*\1057403\25422\DELC\RSR\SYN-\51098\1011541g\68666:S>c\15266\132940\DLEY\1066831~a)YW_J\1063076P\a+ U\1084883j\EMk\SOH\1096984\DC1\18679e\172760\175328,\5135g@\DC2\GSHXl.\ETB\153793\&2\DC3mY\1054891\tv?L8L\1074044N\133565\nb1j\1044024\148213xfQ=\\\ENQe\995818\1023862U\DC2p{\SO\1099404jd^@U\994269tP.\DC2Y%R`a\r\160622\&7}HnUf\132856m^7:\NAK=\52348>l\95313hwp27\149950jE\fx=!.\DC3]Ar\tw\DC4&\SUBk\194572s\1042820\4498I\146071\61461\1060645dsY\DLE\181922dX.\146295i]\151113\1028288\rWS\USU\1098732\SUB\49884\1083906\DLE\STXN~-\SO6\190031\1110322\\O\185165Jc\1052359\1071278\NULHSo\DLE-W\DC36\170321I\1068712)\99800={\99796h\27961\61707M\1022570FwJQ\1111976ck\SUB\CAN|UV-\NAK\SOH|\DC4;\f\156907\145795\ENQS\NAK.B\"D\163007#o*\126577\32988m\RS\1049834B3Gg;\DC1\\\180659\1098926\ENQ B^\SI\152630$e\39220\170037>fMgC\187276,o\128488\\?\1033955~/s\SOH?MMc;D18Ne\EOT\CAN)*\STX\GS\16268\f\RSUc\EOTV9&c\3517\a\986228a'PPG\100445\179638>[\3453\&2\64964Xc\131306[0\1002646\b\99652B\DC1[\1029237\GS\19515\US\EMs-u\ETBs\1067133\1005008\161663n\1072320?\1045643ck\DC48XC\174289\RSI2\2862\STX\DLEM\ESC\n?<\\\DC3E\72219\GS\n$cyS\136198!,\v9\ETB/\DC1\62324?P\ETB\41758\DC2\999537~\1058761W-W4K8.\DC27\EML\1078049h\SI}t+H\SUB\ESCX\120523s\EOTt\177703taa\GS\f\152365(v\1024552M\ESCvg3P1\1032835\57603]g\3933\&4T\NAK$\38212);\\8\1109165\nK\NAK}D'^fJ'\143205e\174052\39597!\EM.\DC2{\\CEp\1045384\ETBk_\1083904\18397\164138\1063468]MG$\187650[E\1112126\b\1073487{b\50650\ESC^b@W\NAK$\FS<\1023895&\155992R\ACKJ\SI\1093108\1101041\41438n\1007134\&8]\148288\ENQ}|k\STX\CANQ\USI\a\CANDZ\1062877\NUL\50197rb\18947\&3G%\FS\162081\EOT\NAK4YB0-i\1018065IM\1073908[\1111554:Cr$\99636)L\136837W\40897.x;\41461\1030711\995525\USkb\CANY9)\SYN4\SI\1103461Av.\r\f\1061861\&9{\SO\ETBP\f\33538u\r-9cB4\1016091G\RS\22817\1014740r\128247HcsPm\59419s\120987!|J<\DLE8\FS[\NAKWYAK\75011^\987050c3\1042176\aC\ETX\ETB\1053739Y\DC4f\ACK\1060945!\1032209:RlQ!BX\f=\1070694f\151362\DEL\113727O\ETX\\\"\53275B<\RSLV4g%3\1098063\ACK`\NAK>\n\44626kp\986102\171479\DEL\60526H\20888lyJ\DC2)\1055149(\1027099A\FSh\EOTj\35251\DC4M\ESCP-q\bn\CAN\143310~\GS\EM\"o\21512%*e2\165597L\1023807sy\152913\&2m\GS\1049046{EG]\DC16B+{\983622IYa\1008153\&5,<\ESCX\f\SI\186613\153744E\134407\1011088L<\EMdUO\ETB\SUBZYm\ACK\1086320R\SUB\991954\DC3^\60967s\fu_g\EM?i~}\DELV2\148681R\FS\EOT3j\45841m\1542\1100884\n7S\SIT5j\170914\SI\1015133\141587h\182480Q\146618\59914\DEL\NAKZM\1110574\&02f\129340l!*\SOH\1027033\SOH\1070384\1094775\t\72805\ESCa:q UKEN\RS-\n\ETXH\22365a\1074707\b\37494\"\1035508\149695\1033139R4\ETX\DLE\FS\STX\1004750%\"@\1009369\&6=/x\NULP\EOT\174871/\190041\f\f\1005146?*\fIcKW\DELQ\"\1001726P*\1095849\&6=d\n\157680\RS\1087962\EOT\DC2I\47501U\b=Pc\DLE" ) (Just (CookieLabel {cookieLabelText = "\SI\128787-\125004:\136001\39864\ACK\SO"})) - (Just (ActivationCode "123456")) + (Just (Value {asciiValue = (unsafeRange ((fromRight undefined (validate ("RcplMOQiGa-JY")))))})) testObject_Login_user_13 :: Login testObject_Login_user_13 = @@ -196,4 +198,4 @@ testObject_Login_user_20 = "ryzP\DC39\11027-1A)\b,u\8457j~0\1090580\1033743\fI\170254er\DC4V|}'kzG%A;3H\amD\STXU1\NUL^\1043764\DLEO&5u\EOT\SUB\167046\&0A\996223X\DC2\FS7fEt\97366rPvytT\136915!\100713$Q|BI+EM5\NAK\t\DELRKrE\DLE\US\r?.\STX|@1v^\vycpu\n$\DC2\186675\131718-Q\151081\n\r\1033981\68381O\ENQ*\68660Z\USo\EOTn\188565%&\DC3Me*\STX;\DLE034\nv\NAK\140398(\1075494\990138n@\1108345|\48421d\n*\SI\NUL}\NAKA!\1045882\1036527Hx\ETB3\STX{#T|5|GC\1089070z.\USN\1080851\22324\vu\SYN~LP\147583CV\SO q\151952\DC2e8h\USg\1019358;\f\996107\1108688At\1022346)\USG\DC3\166541\39337|\1042043\SI\134073\EOTc~6\DLE:u\165393##^\nn{d\CAN\ng\16237\ESC\US\US~A8};T\RS\NAK)&\b\ACK\1106044\GS(\DC3u;\1094683;=e\1051162\"\40669vCt)o\987006m\43912\78088l1+\1036284[\STXFLx\1080932:\1031973\992752\&71/kE\93787p\DC4Ij\ETB\194985&\SUB^\FSl1\ACK\1019548\ETXW,+3\128058\95671\DLE7\59727\&7rG'\1078914JC9M\1053804\SYN\DC2\44350>~\1016308Y\1062059=i-\fS\172440\156520K2-@\ENQ\f\1108851_1D-&\128386lR\187248/\993988$:\31415:\52267Dg\1015243O\1010173\170117\SO\179807\&2z\NAKq\141547c\FSliJ{\1055925\1060070'BL\168670;\STX\1046844\18443B\NUL\7839b\1072569:w\1108016Ad\SUB6\NAKo\55279\nsPWM{\ETXfW\1018373JT\1021361$\989069\54608\190318\173259u4\1103286\t\34021\1039458\"\153264UM\1084148\1095406\34105\1105325\t\nIn'\1070532\21097\16091\EM\DC1<\v\bW\SI}\141807\b\1072339\1035283\GS`\1094467x\NUL\986937K\FSj\1079287\DC1\SI\168992d\991620k4\SUB\1009876\49943^\58464\1052547\1016875i2=$:[f\1064579\DC2n\NAKJ<=\2028\SI!z\1105364\SON\NAK\EM\180748V\1024876CQ_G\nY#ky\132779k\DC3\ENQ}OC\96566}~M\EMp\ETX\RSx\b\183962\1073008\b8/\DC4?\1081654B\1025870\EOT\SO\DELU\1020905\ESC=%\51062J\168855\ETB\992593\990312\985186\to\1101036X_@@\45111\43952$" ) (Just (CookieLabel {cookieLabelText = "\1055424\r9\998420`\NAKx"})) - (Just (ActivationCode "123456")) + (Just (Value {asciiValue = (unsafeRange ((fromRight undefined (validate ("RcplMOQiGa-JY")))))})) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewBotResponse_provider.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewBotResponse_provider.hs index 5fb8047bd82..918c681ceca 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewBotResponse_provider.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewBotResponse_provider.hs @@ -19,7 +19,10 @@ module Test.Wire.API.Golden.Generated.NewBotResponse_provider where -import Imports (Maybe (Just, Nothing)) +import Data.Id +import qualified Data.UUID as UUID (fromString) +import Imports (Maybe (Just, Nothing), fromJust) +import Wire.API.Asset import Wire.API.Provider.External (NewBotResponse (..)) import Wire.API.User.Client.Prekey (Prekey (Prekey, prekeyId, prekeyKey), PrekeyId (PrekeyId, keyId), lastPrekey) import Wire.API.User.Profile @@ -71,10 +74,10 @@ testObject_NewBotResponse_provider_2 = rsNewBotColour = Nothing, rsNewBotAssets = Just - [ (ImageAsset "C#\1056358" (Just AssetComplete)), - (ImageAsset "\DC4\n" (Just AssetComplete)), - (ImageAsset "V" (Just AssetComplete)), - (ImageAsset "Y+_" (Just AssetComplete)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)) ] } @@ -99,9 +102,9 @@ testObject_NewBotResponse_provider_3 = rsNewBotColour = Just (ColourId {fromColourId = 0}), rsNewBotAssets = Just - [ (ImageAsset "'\DC2" (Nothing)), - (ImageAsset "`" (Just AssetPreview)), - (ImageAsset "?\1084357\ESC" (Just AssetPreview)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)) ] } @@ -180,10 +183,10 @@ testObject_NewBotResponse_provider_8 = rsNewBotColour = Nothing, rsNewBotAssets = Just - [ (ImageAsset "\5691~" (Just AssetPreview)), - (ImageAsset "i\97515" (Just AssetPreview)), - (ImageAsset "\\" (Nothing)), - (ImageAsset "%o" (Just AssetPreview)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)) ] } @@ -220,7 +223,7 @@ testObject_NewBotResponse_provider_10 = rsNewBotLastPrekey = (lastPrekey ("R\165465")), rsNewBotName = Nothing, rsNewBotColour = Just (ColourId {fromColourId = -5}), - rsNewBotAssets = Just [(ImageAsset "" (Nothing))] + rsNewBotAssets = Just [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))] } testObject_NewBotResponse_provider_11 :: NewBotResponse @@ -260,12 +263,12 @@ testObject_NewBotResponse_provider_13 = rsNewBotColour = Just (ColourId {fromColourId = -6}), rsNewBotAssets = Just - [ (ImageAsset "\DLEk" (Just AssetPreview)), - (ImageAsset "/" (Nothing)), - (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Nothing)), - (ImageAsset "\21612\188425" (Just AssetComplete)), - (ImageAsset "\f\1077258E" (Just AssetPreview)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)) ] } @@ -333,7 +336,7 @@ testObject_NewBotResponse_provider_16 = rsNewBotLastPrekey = (lastPrekey ("e!D*j")), rsNewBotName = Just (Name {fromName = "\174414\&4?rvqg%\DC2\167142\DC1t\CAN\62298\SI_\92287F"}), rsNewBotColour = Just (ColourId {fromColourId = -5}), - rsNewBotAssets = Just [(ImageAsset "\"\63981\1047766" (Just AssetComplete)), (ImageAsset "" (Just AssetComplete))] + rsNewBotAssets = Just [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))] } testObject_NewBotResponse_provider_17 :: NewBotResponse @@ -358,9 +361,9 @@ testObject_NewBotResponse_provider_18 = rsNewBotColour = Just (ColourId {fromColourId = 8}), rsNewBotAssets = Just - [ (ImageAsset "\FS" (Just AssetComplete)), - (ImageAsset "\92915\984145" (Just AssetPreview)), - (ImageAsset "" (Just AssetPreview)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)) ] } @@ -380,11 +383,11 @@ testObject_NewBotResponse_provider_19 = rsNewBotColour = Nothing, rsNewBotAssets = Just - [ (ImageAsset "\158941\DC1" (Just AssetComplete)), - (ImageAsset "\t" (Just AssetPreview)), - (ImageAsset "V#" (Just AssetComplete)), - (ImageAsset "" (Just AssetPreview)), - (ImageAsset "(\ETX" (Just AssetComplete)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)) ] } @@ -401,8 +404,8 @@ testObject_NewBotResponse_provider_20 = rsNewBotColour = Nothing, rsNewBotAssets = Just - [ (ImageAsset "\"" (Just AssetPreview)), - (ImageAsset "\1076571d" (Just AssetPreview)), - (ImageAsset "8" (Just AssetComplete)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)) ] } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewClient_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewClient_user.hs index 84219041567..456198d46b3 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewClient_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewClient_user.hs @@ -19,27 +19,17 @@ module Test.Wire.API.Golden.Generated.NewClient_user where +import Data.Code +import qualified Data.Map as Map import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Range (unsafeRange) import qualified Data.Set as Set -import Imports (Maybe (Just, Nothing)) +import Data.Text.Ascii (AsciiChars (validate)) +import Imports (Maybe (Just, Nothing), fromRight, mempty, undefined) +import Wire.API.MLS.Credential import Wire.API.User.Auth (CookieLabel (CookieLabel, cookieLabelText)) import Wire.API.User.Client - ( ClientCapability (ClientSupportsLegalholdImplicitConsent), - ClientClass - ( DesktopClient, - LegalHoldClient, - PhoneClient, - TabletClient - ), - ClientType - ( LegalHoldClientType, - PermanentClientType, - TemporaryClientType - ), - NewClient (..), - newClientCapabilities, - ) -import Wire.API.User.Client.Prekey (Prekey (Prekey, prekeyId, prekeyKey), PrekeyId (PrekeyId, keyId), lastPrekey) +import Wire.API.User.Client.Prekey testObject_NewClient_user_1 :: NewClient testObject_NewClient_user_1 = @@ -56,7 +46,9 @@ testObject_NewClient_user_1 = "]?S`bO\DEL,%\foV\1058249\\+\1085138\&1\188945Hc\a\STX\34506\vH\STX<^e\1069270(\FS(UsaJ\74984\EOT\ETXJ\NUL~K\174557\53413\1102650\CANB\21894wP\RSb?^\152318!\ACK\989862z\EOTD\57439\DLEV\1014962n\5537/W\93061\"\ESC\44216mnq_t\ESCCR\1099860\SICq\1032826\1113138\DC3\a\CAN\n\14612\177521\GSr'\tA\1105228\DC2\1008406\1015229\28799VW\ETBK&#~\SYN>\DC1J\SO;UFO\ACKd\DC4\ESC\SO\992594\53528\ACK\SI`\156081RxHJVQ]:s\a\188379IY\54049~H6,80\1061902\1030969\SYNwx_\34144\97145q\ENQ:N\150078\1101779\1108114\157366\NULF\ETXAs\156773\993458bf\t\151541|\ACK\1027577\DC2{\"\DLE\9157\1046357>\187020,i.L\SOH\146676>\131459g\32728\1027802\188157\1055365\&6\983541\t+\166512\&8\SOv\f\NUL\b\RSv)\38280\DC1J\n\STX\1076642\36253\DC4&R\ESC:~\92995\nO\37177\&5\GS!3V\1080913\147100,\1069133n\CAN\986564\1045245^\881\CAN\17703\GS!\SUB\30722\172478\1013987;8{\ttB[A\GSH\1092438\nv\24007\1006563z|\CANS^.@\FS\140955#P\1108759V6:%7|\DC2o\99270\1060103)V\161010H\37080e\NAK\43917/\1084962\aSv\121274?\CANW7\ETBIOc~E\1063561\1028266b\ETB\155485\92278?\26363\1039530e\EM.|DXlNA-\1081928\178499E\ESC+\1041616SZ\97929\989365hRm/^{\64076\1090370\1036645=*)\1087615\1080072\1018318HjR\1015837\14527Vg\1041636!g\169151\147136\121037\DC3PY[=2\DC2!0\1005141\SYN\"\1113389\1021455-<2]`6:\1044580'&8\ETX\"LL\97181*^:\ENQ\EM\1047774 T\1036505e[8%J#~u\1104342\32511j=hX66)+\SYN\aU\SO\SOH\1070386j\1085132\1090312\166954Td^\1078796\983350\ESC:\GS\98354\1075395@\1100827X\DC3\SO\49895\EM%`\38791\1108632\65179\1086075\STX\a-\EM+_\163560:H4o\DC3HS6`\ENQ\181063M\61286\ETBV?.>\1007053CO-\182201\1034506bf}\127013ZM\1034664\rNp\1095318\1036312\DEL\172193%\ACK/WNZ\62756t\39633HnO\1078611=xm\1095149\44536@$Z\1041048\DELdEZ_1\NUL\fw\983252\DC1Iq\EMa&\1105414\1092447\ESCe\DC2:\1104318#\STXe\STX\993655\&1S\1082850v=A\994881\NUL.\3929\1107033<1 \135839\132170$>M6\23716\1096562\1004254&t_6\EM\138609q\NAK\44158ZTX\\\DC3\1051437\19070a:ip\\]\145930y\135361\32810:b\DC2\28839\txB;!\1036389\1094377\57346HmV\ETBv\95479v\SOH6(/ph\186487.\18345\ETB\NULC\140113\1063283)R5\1107380a\DLE%^\1081815X\164772$D9i\1069264\DC26wM\1045336\SOI%x\1101120+\DELYT\145956\170673\142731H\185687L\rU3\v.+<\rT\1009564\996048_v\94297]H(\"\NULJ\21218C\ru\DC1-\1096638Q\EM\"\"\169384r\EOT7P\164640 \FS\1054047R\1032087\1053518\133154dz\1019877\1061911D\148027Z\ETB\1045958\54215Sb7\152587CL\\" ), newClientModel = Just "", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_2 :: NewClient @@ -74,7 +66,9 @@ testObject_NewClient_user_2 = "I\1065423\995547oIC\by\1045956\&1\13659&w>S~z\35967\a{2Dj\v|Z\"\f\1060612*[\65357V\1086491kS\145031A\1106044\1056321(2\DLE\48205\SOi\SI(\1032525\168748f?q\SO5\146557d\1068952^nI\1103535_?\1019210H\119099\SUBf\995865\n\1004095x\ACKdZ\1053945^N\fa\SYN\SUBb=\1112183SP\128516aTd\EM\186127\DC3\ACK\ETB!\1011808\142127o{uoN\CANqL\NAK\ESCc=\v@o2\1043826\EOT\142486\US\1079334\&5v\STX\GS_k,\DC3mAV>$\1029013\1061276\RS\1089843\n\8980-\60552ea}G`r? \DEL\1004551\SOH\US\132757\&9\brl\155069}u\120967\1080794\1062392@M6M\155107\98552\167588|E5Ud\1051152tLjQ\1022837\6734\RS\v\DC1jE\ACK'~f\SIR\1010717\NAKd}}\1059960q\1031766\DC1\151174\&9\160469\RS\100592\ETX\186780\DEL\r\FS\US\36812\14285\NAK/\GS\25526\1090814\61061\NUL(:\1054313n#m9x \1078109\183480}\1052622\54486\GS\991929\b`\1087609G#T\DC2-8\NAK\18310\134655\tp/!\STX4C\SUB'DP'.\a\1110090\&8<9\SYN\NAKEq\168018Ep]\ajZ%\1025589\4170O\35069>\CAN\ACKw*f<\1102303\SOjzpjY\US\SUB\19086\DC1\DC1\ACK|\SO\1064500;\135633F!f\19971b%\1048714t9\DC2\f\121106X! \133247C\RS\1029038\162320C!\20923H(/\GSV)e\SYN2\NUL#H$BAJy\ETB\162654X\137014\FS\SUB\DEL~\f\ESC;\n<\GSf~{\b_" ), newClientModel = Just "om", - newClientCapabilities = Just (Set.fromList [ClientSupportsLegalholdImplicitConsent]) + newClientCapabilities = Just (Set.fromList [ClientSupportsLegalholdImplicitConsent]), + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_3 :: NewClient @@ -92,7 +86,9 @@ testObject_NewClient_user_3 = "\1052368\1027374`7\1059315\1007093\1113589ml\140833\"\SOH\SO\DC1*\DC3Q\RS\14805.I\f\1049837\DLEXHyy\155377%\NAK\STXygG`H;\95385\SOH}U\177626\137231\EOTcX\1040316X\34265!|v@\SO\CAN\fp\996755\127817d\1054362e\135350\vr`c\1007164\1023752\142611 \DC2\58317h\ENQOS\FS_X$\181753\GS\74118g\1066468x%\b\1015412YzZ\1069885\&2`^h^R\1101423\62761b\1095153BOyj\1040477!|E \58547\US\62210!Bu`5I$\EM,Jt\DC37\78371L)\70459Z\SYN/pl\172834Xb\DC3\a\STX\1091299\SO!\1078114\SUB#\170440G6\162069m\CAN\1029459RI\187903\983334_\996859\1000036W^\ACKj\1070150\172043x\EM\1040352\&5BV\bVU\1001763\142747\rPt\1108970\36507C\78096\f\158701F>\vkqOC\n-_q\DLEc" ), newClientModel = Just "\1016506\DC3\134041", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_4 :: NewClient @@ -113,7 +109,9 @@ testObject_NewClient_user_4 = "\1039017\EM\188473SyAf\RS\1015867X\64605~RZ}\1062576\&6}\1040947\DELke\1039536v$By\137108\1063620,\STXy\138845~\1099492\&9sr\1072529\r_\1069502\ACK\50260\1078888\1006226\&7\1100806l\1078895\50212|\1009874\163356\n\1052573\ETB)u\1101000\1104322%\32265\ETB\991708P)e\DELE:sR\1084361\SOH@\1038853y\US\20921v\165504\1017085\DC1[9b|\ETXHNQ|=\1029089\&3Q7\40623\1103209\DLEnoh9\59006)J\FS\186747P\54437cN\SOV\41980\160251O4S\SO\GSR\SOH\USBn\"D\156034\&3c]Npy\94751\70717\174343k\157787<\ETB\1110191H\t\1061523\164945\DC1o\f/w\EM>%\119897\SOH\144896,\92174i\vZ[I\45897\SUBN1mZ\29435\32128H\1059019Imv#JR/;\ACKK\DLE\22913\9036{ZR\a96G\ENQy\152237+\1076894\65774=!\EM\v\DC4\GS\1027536\1041750<\1050841\78770\186899\146197$\1025274\CAN-;\ETX4v\RS\1084606\r\1075881q\1082834\&4_C\160530\NAKU.PD\DC4NYI\DLEv\b\r\FSxH\1013670%mxC\1046045\SUB\1048930\CAN#cE\1084200;j`\136585I[ABy\1009976f0\138223t\1014439\SI\153598?\SYN~(Z\v\DC3E!\EOT\1028681[TW\993095\USn\SO\21854+&\STX\183016\FS+\131838\FSv\RS\1069777D\1037076$<@\ETB\fCEb\152380&\1024980e\DC1\CANgN\DC2]\RS\\A:*\ACKtV\ajD3\1109955zBA4\EM5\SUB\SYN\993240\CAN:%\1006879\51029\SO\184148{\vT_~0\GS\DC2\EOTV0\1066531K7xI/\1034400\DEL\54219\STX\1060450\EM\132009\8421.m\75044\DLE\1020963a\44058|\SUB\6647R\ESCr~\191257\EOT\DLEFrvb\59948\111197\ETXz\bI\\\DELo+'Q\EOTl :?\71309r\US\146650\1086696o+\169525Jn\1042987.*\162403_7:\1042422\NAK3T\ENQ\t\155471b/q\SYN@OmA\97170xZ\52646\CAN\RSh\USg\aE_K\SI{\1052531\1053068&\1112242N )\157576zB\ETB\SI\1048564F\1082705\"\ACKB\66649\ENQ\182827S\GSX\27274\1060500yHAi\156511 \DC3\23109F8A/e.8$\99020\DLEOu-Mw\1008926\&4zb\165386BC\1078129\1070383\STXY%Bg\RS37v~2\ESC\CAN8\CAN\DC1\FSz#q?ADm4\vBK&Z\CANK\1112860f!*/\ACK\DC2iVTs\141472\1059086\1089418\154356OQ\154020-\46297[J\1041865\18234\1047221H1K\1102038\US\121032\1065005\&1\SIk+EB?\997749\1030694\1110639y,\CAN\NUL\USfT\DLE{\NAK\1032571\SYN\35342E,\SOHy\35156?\1031354$ll%\1046167\SOH`)\74445\1078638u\152196\1102759\148712A\SYNpDoz]L\19874/\1019344\1067340&f`AX=RA\v\1103061%,O\1049934\NAKB\119918@\EM\ENQ\1047236\1081280-\997855T\998310l6\NAK'\172576^\1053906('2AP&?\US\DLE\SI0f\2012\ESC$\DC2\31866\&5v_\1051689\ENQ\153046> [3h)?\50999\1039306\137233m\983891cBD\2027\1016974\1014463\SUB%X\NUL:Pib\\s\2743t\156273\1005692{Sz+O\GS\DC4\DLE\DC3.\EM;2S\SIF\1102512\DC4ftF}3G\NUL$wR]\USYpY\f\SO\141464;\ETXe\28203\38494\57722$\DC3(\SIw\ETBBp\128488\ENQ\DLE\154640\1040605\v:\51261V\983830.\SOHN<\60444$\tj3\51990\US\18582E,$\a\n\SYN\SUB%\1022574P\983622,\1034053k\NUL\134010E\82971\ENQ'\CANwrhj?9\1090612\1017377\CAN#\181249\58693\ENQ\DC3I\f\"\ENQ\53481^\144621}T\164393", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_6 :: NewClient @@ -141,7 +141,9 @@ testObject_NewClient_user_6 = newClientCookie = Just (CookieLabel {cookieLabelText = ""}), newClientPassword = Nothing, newClientModel = Nothing, - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_7 :: NewClient @@ -159,7 +161,9 @@ testObject_NewClient_user_7 = "\60526\139445l\988963\ESCQ\DLE8SH\f\GS\ETB-\SYN5\FSPy\1061623r/=\986816N3\1035659&N\15316w\67714X/Q*\150730\13744X\1000479+9=U\1024020\SOH\ENQ\STX}i\b\1097422\1069734\SUB\ENQZ(\168168\1009772o#Xm\CANM\1023654\&3\172872^Pn\7760crQc\DEL\986307\SOp\tr\\\986121t}\EOT`\177281M\66289\"\4716S\SI\1037221\SIQA\1106589\RSb\DEL\1021792\73060\&0\990076\68641 +~\991677\SUB8,b\162479g\1077282%\CAN\10999\SUB\ENQ)e\SOH\1015562\f9\1094666!yW\1033160${\1059712}\1110303\1078145\144829+q{\f\164278\1032027|g\ACK\SOH/`L~\1047083\1084997\158108\ETX!\ETX8\n\1025179\984212p\RS&\RS\32948\1096125+\1052271uft \1037257D\1055020\160376'\SOH\999953\NAKV\SYNn8x\29075\DLE\40118\CANP~l$f\7823\DC1\ENQ\1000273\8729\RSLx<@\DC2\EM\1027640H\181765s\1072018\&18\152448\137948\ETB \DC1I\f=\CAN=[V\1045990'\SOH\46563\995907G`6'\SUB!*;\EMJ\1075591\STXy\DC1\\\"!\aE\28556\146573B\1033154\178164AL\RS!\ENQ]\1050549?_t/\137535a\64278O G\RS\1102157\1087399{=\DC2<<\1046051[o\1021278Lz\a\ACK\1108792\n>.\DEL\ENQI|hlT;h\US\SOI\NAK\SO\SYN\1102001\1016492\DC2\1038033\FS\\P\FS\171319\v{Y\DLE ' q!\1045478g\1015953T\1017164^Lg:\1043756)~\183956&R{\1077936\188232X\ETB\1096513\1012477z\5029s.8n\n\58208\DC2|\SYNRP&\b\ETX7\ENQ\aU\1003317{\DLE0%v5 \18048\SO\94341EU\RS\3357\&8\SUB/\4793W['`J\1067364uv\163012\1062281\DC3\DLE\EM\1083526+P7\161606P_X\100493\1036346\&2\1060605\&54\184580xB\1054288g\180383\"\DC1\SOHrC)0^\SOHrQ\fpd\46566\1106204\59787\18367B\NAK\162313\42281s\1005088\&6gi6m7\144814\EOT\ENQL\DEL\f\100019f?\CAN0$\145372\ESC]>CV\nVPb:^e6\7528\ENQ\1049417\1094717$!L\1052468v\60090v\SO/:\SUB\"\1052989o\170175\fe{:\1002279\6755\DC4\SOs\a\DELz~\62543%H.xF\1041929\165988h\ETB\985392\ESC\1061053E\ESC\170298\&7\DEL\SOH\v8\97125:PS: \1092223:\1039879\&6c0\167650\1088174$\1102815{)|\GS\1053757K\f\RSa#GK\1046896\ETXm\STXbr@\6481\r'bv\b\996462\SYN\1018321dv\1000550\"\NAK\171594\ESC\1034597\DLE>\70305\b\164202\9087S/[\1093372];\10230A$\n5S\156734_y\188649\ESCP]e\CAN\180901\1110553\160289-\GS\1078851\GS\1098241\1048062&P$\GST\120646TLX[\49486\129523)\ETBP;\45799\SOm\1113854D\1061956\1043723>EI\NUL\NAKI\CAN\1058606\t[AtgxM\SYN\1038293^\1101184\b^V\143698+l)n\1029632\NUL\"\1104565y\a.\DLE" ), newClientModel = Just "\150744", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_8 :: NewClient @@ -177,7 +181,9 @@ testObject_NewClient_user_8 = "\b\RS\1083911F\v\"p\184881\161833(\CAN\STX\SUB{1.\STXSh1\EOT\141013J\68124\159527\52361?\16802\DC4hg^h\1058009&\SOH\a^\9060\1109232V\74979\r\DEL~\nHD\"\ETBTC#\152775\57858\DC1\1029658\f\9672D\SI\ACKX\CAN?T\STXQa\v:}\ACK\1043380\FSv\ENQ\EOT;F{\69424\168419P\24246SNm9yl\ACK\1039741zm\r\\ym\GS\NULVYm&H\ESC*t\181898p\988616Sb\46454\1041292od\175159\&3L\58851\1048155\SO$3\1079098\RS\r\12927\fy\173674B]\aj\DC3g:\GS\147498\&7\ESC\NUL1y79R\ETX\124968\NAKo\1030373\GS:C\EOT&3g\ETXPonO#u\DC4A5=z#7>+R\181936\34025Qk1\42728\FSF\DC12MY\1045697\1056260oqk\t/\fWI+1!]}r\DC3\ESC\NAKK\DC1*M\STX\1071403\SOH\EOTqe\CAN\134249\&1]bj/t\127036,\DC1jk\EM\1096906\DC4\US/\1018712\2562\SO3\119865k\DC3\1089523\FS~\EOT3\1017007{\46838Hf(x\SI/Q\1052752\nzgdyT\1112482\&4CE\1061698\FS\b\f=v\EM\1052769+C\142840\642f\1012060x8b\DLE/\ENQ\RS\rQ;Ec\181947\984650\SYNQM.)\EMk\EOT^\"&~EQmQ[\1109601GR\SYNz,\1112207kdR\SUB\DC1M\EOT!Nfn)\994438,oFb~\ETB\DC1#\DLE\\%\FS\EOTC\997002\1005097\8330\60433.\171514G\1104943,{\EOTR\174171=\1108508\135699\1049776g\DLE\142921\\r\DLEF~V\35187\47168\ACKP\10702&hC#R#z\DLE_\DC2\1080508\1089113dK\156671\22090\&2:M\1003119t\DC4\120237\\\995393:\v\ENQX\\\174898,\"\1018585:\28181l\FS\145440X\1061109hv,V$'\EOT0zk[" ), newClientModel = Just "", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_9 :: NewClient @@ -196,7 +202,9 @@ testObject_NewClient_user_9 = newClientCookie = Just (CookieLabel {cookieLabelText = "\SUB"}), newClientPassword = Nothing, newClientModel = Just "m{", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_10 :: NewClient @@ -214,7 +222,9 @@ testObject_NewClient_user_10 = "\SIxXs\STX\1049513\&2\1102168\59074\54772\53296X\v\1090222\EOTIk\176588E?ZQ\132276O3\63616X\992817!#B_\152795}\1084694y\1054376PM\ENQ{\DC3\157254Ln\ACKF\DC2\NUL\11009\&6g\36110\991920\25715$S\DC4\SI\1004033\&4'?z\1081305\&0\39720A\21773\RS\DLE:pg\ETB,_V\34799\27560\1040999U=S^$\1041670u:X\63822~/*\50548\DEL2\ENQd7pJ-\DEL^\NUL\a\67607\&6a\158943:%\1062864$\1077297\&69R\SUB/G?C\ab\SOHl$/K\ETX\DC3\142946g 4J3\NUL#\31582\986721\DLE2\ETB\175407vV2\EM6\96746\v\36097W\54750\176689+4tk!+Y\152620\1104329\NAKQ\1023294\1105747GHQU\bx;c_\SUB^\34594]S7\ru]\r\1034086!\1067469\150060\&2\ESCMg\1074803QE\NULm\NUL\STX\DC1P\155051\v(H\1011822\1031529|g\1082510\SYNb\DEL\FS\ESC?=f\1018763\54754\DC3\NUL\ACKg\US6\172308Q\52355q\t\"\190130b\DC2\138877\&3K\138660\&9IxGW(\t:\f$9\74183\SIU?(\177077\fgI;4\EM\147733\ENQ\"RGbgCf9\DEL\64069Io\1105067\51831\&8wb(]\US\1061721!\1061150\1078273u)t\DLEkO\NUL_\DC4\ETB-\RS 4\a,\f\19767Qgd\1078960\22989\1062074\DC3;\986351~\1092523Ui\FSH\1047632\\\1040094\DC2vmU\983357D\134769\EM\984607\ETB2D\20960\40111\ACK\SUBJ\SOI,\1085675TgXb\SOH\FS\1094376\ETX\1096029C2\127781\&7\1032517P \SUB\CAN(8htD\DC1\30721\162821\1067602WzoM\GS8\v.[zIG`T\164362<$s\1076321\165910\SYN0=f(\ENQ?v^?_\7818X>7\21617|EQz\1039534q3\tr\1080514nq$yq\137821\&5\987933i\1096583h3\ENQ\1079332\19161Ac\1003179s\155333\34595X,\EM\1089910>\47776\DEL2\ENQ}\\M\nd3%`>\24917?\rv\1053845\35041d>\twE\188031G\1031126\&7\1023454\ESC{G\1085555\SO\SIv\1055004,cP\1060712\1019814\"4\DLEM\SYN\178587\1021477\&8u\1078424n\SOw\ETXV\US\DLE\DC2\fXf,\1036282b\1101047\">&0xFqd]^\DC2\ENQ4@9\NUL\94493B\73906}L\v\n9S\EOT\SOH5im\DC2-\95629}\SUB'\98821#,\1034274c\ACK\ETXP$xc\137033tB\DC3\ETB\35422\SYN0\ESCu\\\1048883b\23147\"\EOT\1006915bwE:\b\DC4\1065071vp8\1022622\SOH\DC178$E\DC2JE>{?\6021I\NAKJ*7sI\ESCeu\DEL\1077673\10421\&1a\NAK)R\185144|PJ\1111255at\SUB)\ai2\1009821mY\46293~gdde>\1010509yG5b\653 \132576\1001031zJ\984798\DEL\NAKT?i\f\1064196F,]9\GS\46035\DEL%hB\128429h\34802\&9\168858=K\NAKrFVWO\CANVc\65362\1065989\1092750\NAK\1021958\ETB83\ESC4\"\1035406\DC2R\1073182\113709\&1UD\1055011Fi\1059797\&8\EM?j5j\998683\SOHW)\1057415fJ*m\1074569d\127187l\DC42\1086873\"Xgp\95800v\"\ETB6U\22131w\SO2*\174690\21425vl\CAN0\ACK;jh\3871IC\SO\1026262L;55\CAN\1031309\ACK`m3*. C)\70284\156404x>y<\1104362+>*\ESC\vs\SOH\1088826NSChj9\49235\"#M`?\54746i\ENQ\US\STX\GSlcS\1047710\EMC\ENQ\SOH_/u?/\38071\997094g\67263\175233*\STX_v\1060509\DC2C\DC3\ETB\174166J~637\DLE\t\DC1P\DLEy\66836'Wz\SYN8\1008920j4\1374d^7\22270JF&,\SUB\47147" ), newClientModel = Just "9FO", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_11 :: NewClient @@ -236,7 +246,9 @@ testObject_NewClient_user_11 = "\ENQWU#*\b\169584\ETXp\3616\DC4c\1009690e\47037m\1102567\r\t\1012852\7852\NAKz7\ENQ\r?I~\1054524;>$]I\SO\42203\DC1\164569T\1112610\48142\ACK\FS\179049y71,h\12763{\31082`\n\r\173936+N\1096448\GSlqmi\1099779\SOH\EM=T#%\1022504\ACK\fZ_UCe\SIk\"\n'%\1076023I\US\RSZR.n%\NUL'`u5NI\ETBx\ACK+\65807j\170243(x\DC1;\181398zyUe\f\191269\37010\DC2ow\1044794?-Wz\992114Z+\SOH\n\1083159\ESCly\1047059\&6l\SINn\SIT\ESC\EM*\1083747\&8&E.\b\DEL\148620\1070806\EM_\n{\188784\RS\158747\SO`\1019622\46413\990554^1\139313=3X\DC1K\NAK\RSc\SOHB\1078866B\GSm1$\FSDve\142150g\SI\1087970D<\SUBKo~X\SOH\1094699\1105344-3\1058387\"\9645\39804e6G\ETB\1011308\1111485\53599\&3eM\EM31[L\SO\DC4@\132573F\US,b\199\999413\v\STX\986195D\DC2V\vX\SI~\1097246 nS/\57971\169887RTD7cMd\DC1\EOTOw\994792yO[8\1082860\60631\100663[\185643>\99795\nA<=PE]'\DC2\986494\&7I Q\1069408\17420E=\SYN\ETX;v\CAN\SUB\163809!ek\DC2B4\STX\EOT\1100350\178438\13392b\t@{0\1052374\181553\151609\"\ETX\ETX9/R\v\133430n\DEL\1028100\ACKA\DC3\n:\126546\46116\45354\189672%Z\US\r[EYo\DEL0N\RS\ENQKR\1033145\1099889~c?\EMJ?`\DC1\1024319\99332\t\1037670h.n|\SUB2\987386{'\144962\1081004/^ut\100480\134584--\DEL\1054725\SI\52605\1034550T?\1000376\177692\1007643?\DLE\1065650\&1\179681*6\SOti6\134585_Z7\DLE\NAK\ESC\SUB\a/M,\r\1013726\27638\1049228lg\GSjH.uC$\50698^1h)\b,\ETB\166565a\1092454R/\1035274_\SI.tx\ETX >\40781\984980%B\ETB\1009320\14711\&5\STX\153557\f\121255\136884\57749}-j\ESCyO\CAN\47414^\1051627F\51571J\EM\SUBE}&9w\78649u\r\19329nY\369q\195010\DEL*3\b\FS\140122\1051712~Rx\SI\n\ACK\n\62186a?S3\25573\bn\"gf\1113756\57969F&\16122V\ETB\1024468W\1015855\SYNN\ACK\SYN|r,;.w$\FS\b\DEL.ro[V\987558\1015707&\SO\17069+\16921=\ACK\\M\EMYpXZ|Jnnr\1052032\\\1004404\23525A}H\164247\ETB:_\DC3[\SOH2%\rh\FS\ESC]\92372\DC3\190770;\f\1105269#\r*0\92455Z\58095\141267\153723\1031644\1084692\&2\148115\\ 7\25709\176908\NAK\49305\37904\179314p\1099243]P\93039q\v\RSi\NAK3FX\74130\&8\SUB\33013J\DC3i\22841#\239K\1037511%5j\179793\&6\40198\175557\1113225\82980]B\US\1045028\&3\1024025-o;9>\1009214A3~\ACK\FSR\41975\140632\SOHa\fiFV\1002037e\b\1035521\96860n*\1008115\ENQ\149608\ETX\GSnp\DC4m,\r\1058496hb\DC3\1069244\34144,&d" ), newClientModel = Nothing, - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_12 :: NewClient @@ -261,7 +273,9 @@ testObject_NewClient_user_12 = "T\166327fq\DELdA\18130k3\1105137[\rK\n\f^\1090853\&0\98095\DC1\ETX'\97298M+\ETX\a\ETB\nw\SOH\1797K\161329\11176z~qp\153477\ETB\GSW4m@CYG(\49998ob:\CANP~My\ESC\175657\v\135887\"\34075&\71209X\1070912r\DC1\131932\1022615v-p+))\ENQ\STX9i {&p\164508[\r\US&;b>N\ESC~bu\DC3\v:W:S9\DLE1L|\\S+b0Q\STX\31885rs~;\USG;\986684o\1008621\3277%\STXm\DC25UTR\n\DC3X\ETB\1058418~\RS\62657\DLE;\SO/\US-\\mPc<:.HmK\175151o\f\SOH\1094091&B2\aR\24795\&51\GS&-\179688c\183928\988858t~rT\175316H?\60066\&6\155512~J\1111920q,\STX\RS#\1085689W.=\52405L\1073572-\1077186\ACK\173126\152012\&75*Oq!\33433{x\DLEt\1077477\993588X(w.\EOT\r\60577\1111873}kp/\th,z\\\27305\ACKn+=\23672jL~F\NAK\32287\&7\a\44660 ,0\517b\ESC\DC1\USoA/\ACK\v\1005276\EOT[o(z+c\SOH\35973s\35678C\31719\\W6\NAK'\STX\1065586\&6Xb~TO\1106854\1078560U\36772\SOH\"H2\1071196\DC1\1111474\1070801O#\SIu\SI\70804\198B,4\917939\1103645L\1098719Mt>I)U?p\SIrQ\991975\1024380\&7\170637\1058486\DC4\ESC\n\3616\n$\1019615\abhXT\DC3w\36477 x\7606\ESC?p\b\ACK\1019692N\1047942=j<\156592=)\n6\25048ZF=*`(\DC3\SI\1084532\ESC\FS\172082\ETXyR},\1088502r\GS\US hY_\1059030\1053557\31965eHsWj\1065305N\51163H6dd\DC1%g\1020583\&6\166285=KX:V5l\1011535\&0\r6K6?\1031425\CAN\1016917:,x\994043_{rgc\bI\SUB\65378\1094330`\\\EMl/`\t\US\DLE`r\1030112+E\2755\93034L\991483\EM\2608d\1049231\153892O\SYN\1095453.\GSom\28655v\1086471\DEL\154279d@\1076849.O,*\151457K\NUL[)H|Y\ape\ax\\P\77938 N\1012329\1083848|\f\1026650c\SUB\n\\+\SOH\SUBWCk\SOHe\ETXG.B\NUL&g@L\ETX?B\SOC\1035168\ESC.\r\GSv(uz)\EM\19888\1027956Y?/R\DLE`]g1o1\NAK:\US5\1018150\1027769\SYNKS\CAN?\110635\1069040\998938\1073365#bf\ESC\1047091mc\54492Q" ), newClientModel = Nothing, - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_14 :: NewClient @@ -301,7 +317,9 @@ testObject_NewClient_user_14 = newClientCookie = Just (CookieLabel {cookieLabelText = "\185625 "}), newClientPassword = Nothing, newClientModel = Just "\1108879", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_15 :: NewClient @@ -325,7 +343,9 @@ testObject_NewClient_user_15 = "0\EMM~K\162154\45005\164305\1004413\172280y\1003918*\174546\1042834\rE\DC1(\1088983\1005968+\176459\DC1\1007952\DC2\1078947\n:\1032500Ig\SUB\74639l\EM\43295p\EOTx\1094422#\b\1043804~\1003046f\SO\168069L\14137\&5\1072066x[#&\DC31qH\132531eA\b\36117\1000509\1112836U\DEL\tb'\125239\&0\DC4\1099298\46462iELNQ\ba\RSb\v=\7552G}s\GSN\1025484$L\DC1\ETB\\\1082834\98732\151628\"\998010\&4\CAN\DC1H\SUBt\1105766|\FS6\137171\SIqu/k/b\1002207e4/\FSD\1084290\v\1047428R\38606tL)\1018496\&6Q\60194\RSd\46542\146946qzqbC\f\EOTpS\DLEY/\990314\1108440T\1044302\21442,tw~)k6\b\USZc\1000679\ETB\1053814\FS\142098Z;6'\9971\DC4e8)4YT\1049079\&7gm@AD\178053\b{\175557\SUB'x5\US\n\DEL\nv\138741\1106818L[\35688\46936N\979N \61464\36923O\50044\&8\1023723\33945\DC1\GS\EM\STX\1011210\b\DEL\180649\990944yX)\139946WmBo\t\ETXFj=5n\1088694\US\170101*\NULWuDo7\vLt]\1041373ff\52366O\1109893\DLE4%SqD\136650][v \DLE\NAK\SUB\SI\1107671\171196\r.Z\RS\54261\US\FS\1032146" ), newClientModel = Nothing, - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_16 :: NewClient @@ -339,7 +359,9 @@ testObject_NewClient_user_16 = newClientCookie = Just (CookieLabel {cookieLabelText = "q\43234\185884"}), newClientPassword = Nothing, newClientModel = Just "\ACK;\143320", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_17 :: NewClient @@ -358,7 +380,9 @@ testObject_NewClient_user_17 = newClientCookie = Just (CookieLabel {cookieLabelText = "k"}), newClientPassword = Nothing, newClientModel = Just "\ESC\15411c", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_18 :: NewClient @@ -376,7 +400,9 @@ testObject_NewClient_user_18 = "\"5\RS\160225\60188\9236\1112830Y\1031107]\DLEqDD\1045338g7G\992982\75047\DEL\39442\133937ceE\189589 Nx}\172612\tF\21563a\ACKN\f\DLE\1040194)U\58059\1021976v\"\SO#\1063353Cb/Dc`;k:qA\USQcsw[uG~\29313\&2i\ETB\157089RB\t\1078526\r(\f\10102\1060258&\SOHd\1105089\155519f>e2~\999553u=\1059210\10136/\49832\1099898Q\65153qT\1092626\148106Sa@k#(\ETB\a>!m=5vN~_#\1060173B\184540N\a\94462\64032.K4\152591s\156905zT\172441vv7/\EM\"#1*'q| \157195\DC2R`\ACKO>P\152635Ga \rQ\DC1s\1083048nh+q\150871 N\GS\CAN\RS\1054551\\;\12308\vMyY\\w\ETB+8bJ&2?D\1069890Y\1008350#x\162454\&6A\1106082\&85\64170\183343\NAK\145787Chh\22172VJ^R\CAN'F\160371[\ACK\n:%\1087606~}\ESCEAS@Wn5@\1096877&a2\1088377`\120586\8472\&5\CAN^\EOT5W\EM\SUB\DC1R\DC1D\RSL2m5XofV?Y\155649\SOH\DC3\1019336g\ENQq;\1001052\12738oP\SYN,\DC1\1073001\994800>\14799\7333\165461\1051770W&lHVm\1008163B\vl@%\1039130U#\NUL\1093381$XbM%q\1045263\FS0z\1088980\DEL\ETXr%.\1073307\32341\SYNZ0\fB+_@\DC1tcO\185752\1093870|\186862&~\136183N\45192v\155081\137844`\44461\&1\1014483\1108323\1093550\9737vf\SYNEJb\984223\DC3\nG}\SUB#_$D\1066925\1020860/\RS\vf\DC1\160209\DC3 \v8'GP\rb\DC4\48192\984278\30001?r \135911\1100614\n\160781d\bVStN\"\SYN[G\1586\ESC\US\1011464g\135744_\SOHY1?\USdu\1059402\74951\995455=\1074262})J\1072682$X]Y\DC3\1061380h\DC1H0\STX\66830\1027029\19815\b\11503C\DC4\DEL\a\1074263\1024496\RS|oTc!eU\992641Pr\138670\NAK\RS4\185988DG\136753x\995479FG0P\50986/F\RS\STX\ETX\171779*\ETB\SYN\17831Gw]\1015631\1080624\1069774\1061830m}tg6\166134\SI*\173230\\Wi\EMOa'\FS\1063048\1000576sh\1107838\143056lE+&\ng`\1004945\&3kY\GSY=\DC14`\1062494l\RS\1086111!r\20132\NULw\120334\1077517?\ETB\STX\1082825I\SYN\ngWcrN_)7f+#\ESC\178930\1076598jt\DLE\71890hAx\\-{\92491^4rf\DLE\189136\&6MJb__\48310`n\70443\&4oZA\43460\92533H.-Y {\a\FS\USu[4@6\1045998\94370\3461\SYN\DC2/|,]\NAK\1012422E!\DLE\rZ\178451Q\181143\1090192\&8.X\fwZ\DC4\DEL-{KWit\53903 >\175517L_Y5\GS\SYNaE\40086\&0Bf'M\DC4\1015266\35448\ESC\1009511\&2tF)\1092249&xf9|>K\EM%z\a\1015215\fH\120884\RS\b\1063192\vJCmoJ\t$\r\40361S0\134532k\ETB\DEL\995717z\1023854\b7\59291\DC2ACx\1092749p\45751-e\128647\GS&? \SOHSt}S'\1027538qm\1092436\54380\EM\\c\ESC\40780;\1004615\31215Y8mz3\STXROJbY\\\6422\1067862w\147906 in\SI\NAKnm\189428r\1066116\"\DEL\1004774%*U\66603\DC1C\1106018h\a\DC1MIQWC\172322{%\SI\118975\54512<_$\\d=\SOH)-4~x\179098Ov\NUL\DC4\185898p\1052353\24161c\999358,\23825o\SI\1027216N\STX\1037726\158923B'd\1006564`\1061268W%\1067374\1106444@hx\SYN.\GS\164392cJ\10971M\1033250\983450,I\DC2\GS\ACK!\18551\163976\564\EOT\DC4:\t\DC4\v\\\1109669]\1075069\139170r\CAN\155314%\STX\SIp\96570r\3758\DELR\FS3X\fsW\23719]L\155502\&1`J\ESC\SI\19051\&9gM" ), newClientModel = Just "\1077465\1056032S", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_19 :: NewClient @@ -398,7 +424,9 @@ testObject_NewClient_user_19 = "#\1113416~\nk\1060135J\188666POZ\f\SUB\ACKJJ\RS$\11618\EOT\42121lL[\FS]h,Xf\1042350\CANd\165029\US\65203\1079326t\ETX]\195015\155339e\195099e\DC30q\44188\189121[WSGO{\b=Hs\1089312;6\SUB\19036\"\SO\134697&\1112323]Xmx\143526\SOHi\9442j\v\SYN\NAK\1040391.\f\ETB\SO8E\55091i\1044526P`o\1033279i\137855\SUB\ESC\71171\1104725}8\143576\1001544\39043\113800\ESC\1016284,v\1025287\t\169739^\n\190789\1032336yOX@F \EOT17uq\58778G\10534\SI\GS9?8+O(\1034807v4,[k \14191--Qk\159337\ETXA\EM\164962\&9\138800\984565@\18866\1099020\147346\134891Q\US4;\DLE\r\f0\92231fz\aDC\157549\&3\1045725|Sn&i\EOTj\1051374\128703KS7\146137\fAx\n\ACK\vx\1111954\&5\129488MWIp$m\1060695\118857m\991735\185401\118823m\16484\DC4\SO9SDb-b\a=F\78227\25535\DC47/{\1063538d#X9\SYN\44105\45323/{=\1020413Z\DLE\164180xh\38176A\t\STXJ\a\1022997\&9Bk_\1091831H^\166229g[38&\ACK\150410\1081436\&74\96537Dwdu~r\70508\1013335\1071132&\184387Z^\rl|\f\ETBA\STX~2r>\EOTsM\171720iX~`\ACKzK\EOTW\1099149\1028596ezCh6O\SO%\SUB\SUB=\149997:\1070391DQ\NULc`\SI\aDmG)M)Th\190731\NUL[u\ETB\SYN\"?\EM03\188014xi{h\fy~??)w\NULy-IaG\v\NUL\52252\157648J\1047554g(\n\DC1\SO\t+p\ENQSl\78666\1079369\1018476\"N=Q:/\9007[\161661\r\NAK\147739B\37332@kA\USwi\NAK,p=\40491l6\66318'\132639\120855t\RSWq\985403W\SUB\STX\ETX\r\74459\156671C!j\DLE\SI>\\W\1004807\ETX\152436q+\"\10840|o\990028\RS\1031899\162664$\1033161\43937B_\NUL\1076842DV\DC3\177090W?rH5Xi\v\SO\ACK\187975N-\164527PQ2`\174057r\ETBga\156\FShT#ay}^\ESCP\1089292yp{\DC2#^/5X-D\NUL\ACK\171851\RS\60072\1033438\"}Z`x{\1055488TT_ Z0\20103\1039639\30357k\DC3Q.NT2/\1095308O\SOHP^G\SYN\60717/%d.\172353\986193P\f2iYS@\t\t\DC4.o\11544'?-0a]\97289)f9w4\136279X\182987\181688\DC1\a=\1087084T\142663g\RSb7\GS@\1070005V/,\1029412K\189653\DC3MP\n?W\1008141\&5/z\1046740\ACK\62886u\98299nKB\US\36186\DC3U\NAKh\USr#\1078423\"\1072189-\142719(K\183599\SYN6\170953\1098238+b\1088379s\1111208\STXk\1077908\149688\1103504~Rl\SUB\RS\f\"X3\163579\SYNOQRpJ\1037359\SOHWvE~\48192I5\993324\22741j\v=PK\135321%#\ETB\fN2\19120\181456vz9\1012476nY\DLE\SYN=F\STX\EOT\3416\&9W\13458\DC2^C)ZX\ETBJN2P\1003841\SI|\\\1004102h}P1V\1113257\&7\ESC\v\DLEl\181234\FSz\EM\DC2\1093528IM\993293\SOH/NBiM\170360~;1x\49216Md\EM1\52727\14564e#\"\US\94465eV?i{\1112978\1104388\1010293\151662&\FS\SYN\3436\153277#" ), newClientModel = Just "\CAN\1030222g", - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = mempty, + newClientVerificationCode = Nothing } testObject_NewClient_user_20 :: NewClient @@ -412,5 +440,7 @@ testObject_NewClient_user_20 = newClientCookie = Just (CookieLabel {cookieLabelText = ""}), newClientPassword = Nothing, newClientModel = Nothing, - newClientCapabilities = Nothing + newClientCapabilities = Nothing, + newClientMLSPublicKeys = Map.fromList [(Ed25519, "GY+t1EQu0Zsm0r/zrm6zz9UpjPcAPyT5i8L1iaY3ypM=")], + newClientVerificationCode = Nothing } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs index e756106180a..dac5e3bd12a 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewConv_user.hs @@ -50,7 +50,8 @@ testObject_NewConv_user_1 = ), newConvMessageTimer = Just (Ms {ms = 3320987366258987}), newConvReceiptMode = Just (ReceiptMode {unReceiptMode = 1}), - newConvUsersRole = fromJust (parseRoleName "8tp2gs7b6") + newConvUsersRole = fromJust (parseRoleName "8tp2gs7b6"), + newConvProtocol = ProtocolProteus } testObject_NewConv_user_3 :: NewConv @@ -68,5 +69,6 @@ testObject_NewConv_user_3 = fromJust ( parseRoleName "y3otpiwu615lvvccxsq0315jj75jquw01flhtuf49t6mzfurvwe3_sh51f4s257e2x47zo85rif_xyiyfldpan3g4r6zr35rbwnzm0k" - ) + ), + newConvProtocol = ProtocolMLS } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewService_provider.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewService_provider.hs index 6185daea471..5e30ab7cf81 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewService_provider.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewService_provider.hs @@ -20,12 +20,14 @@ module Test.Wire.API.Golden.Generated.NewService_provider where import Data.Coerce (coerce) +import Data.Id (Id (Id)) import Data.Misc (HttpsUrl (HttpsUrl)) import Data.PEM (PEM (PEM, pemContent, pemHeader, pemName)) import Data.Range (unsafeRange) import Data.Text.Ascii (AsciiChars (validate)) +import qualified Data.UUID as UUID (fromString) import GHC.Exts (IsList (fromList)) -import Imports (Maybe (Just, Nothing), fromRight, undefined) +import Imports (Maybe (Just, Nothing), fromJust, fromRight, undefined) import URI.ByteString ( Authority ( Authority, @@ -45,6 +47,7 @@ import URI.ByteString uriScheme ), ) +import Wire.API.Asset import Wire.API.Provider ( ServiceTag ( AudioTag, @@ -256,7 +259,7 @@ testObject_NewService_provider_4 = }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("")))), newServiceAssets = - [(ImageAsset "" (Just AssetComplete)), (ImageAsset "" (Just AssetPreview)), (ImageAsset "" (Just AssetPreview))], + [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], newServiceTags = (unsafeRange (fromList [MoviesTag])) } @@ -304,12 +307,12 @@ testObject_NewService_provider_5 = }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("Hg==")))), newServiceAssets = - [ (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Nothing)), - (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Just AssetComplete)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)) ], newServiceTags = (unsafeRange (fromList [BooksTag, PhotographyTag, RatingTag])) } @@ -353,7 +356,7 @@ testObject_NewService_provider_6 = } }, newServiceToken = Nothing, - newServiceAssets = [(ImageAsset "" (Nothing))], + newServiceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], newServiceTags = (unsafeRange (fromList [SocialTag])) } @@ -400,7 +403,7 @@ testObject_NewService_provider_7 = } }, newServiceToken = Nothing, - newServiceAssets = [(ImageAsset "\ACK" (Just AssetComplete))], + newServiceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], newServiceTags = (unsafeRange (fromList [ShoppingTag, TutorialTag, VideoTag])) } @@ -444,7 +447,7 @@ testObject_NewService_provider_8 = } }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("tw==")))), - newServiceAssets = [(ImageAsset "" (Just AssetPreview)), (ImageAsset "" (Just AssetComplete))], + newServiceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], newServiceTags = (unsafeRange (fromList [EducationTag, GraphicsTag])) } @@ -536,7 +539,7 @@ testObject_NewService_provider_10 = }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("")))), newServiceAssets = - [(ImageAsset "" (Just AssetComplete)), (ImageAsset "" (Nothing)), (ImageAsset "" (Just AssetComplete))], + [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], newServiceTags = (unsafeRange (fromList [MediaTag, MoviesTag])) } @@ -584,7 +587,7 @@ testObject_NewService_provider_11 = }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("")))), newServiceAssets = - [(ImageAsset "" (Nothing)), (ImageAsset "" (Just AssetPreview)), (ImageAsset "" (Just AssetComplete))], + [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], newServiceTags = (unsafeRange (fromList [GamesTag, IntegrationTag])) } @@ -627,7 +630,7 @@ testObject_NewService_provider_12 = } }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("aXY=")))), - newServiceAssets = [(ImageAsset "" (Nothing))], + newServiceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], newServiceTags = (unsafeRange (fromList [SportsTag, WeatherTag])) } @@ -670,7 +673,7 @@ testObject_NewService_provider_13 = } }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("qH0=")))), - newServiceAssets = [(ImageAsset "g" (Just AssetPreview))], + newServiceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], newServiceTags = (unsafeRange (fromList [FinanceTag])) } @@ -717,7 +720,7 @@ testObject_NewService_provider_14 = } }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("ukk=")))), - newServiceAssets = [(ImageAsset "" (Just AssetPreview))], + newServiceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], newServiceTags = (unsafeRange (fromList [PhotographyTag, SportsTag])) } @@ -764,7 +767,7 @@ testObject_NewService_provider_15 = } }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("")))), - newServiceAssets = [(ImageAsset "" (Just AssetComplete)), (ImageAsset "" (Just AssetPreview))], + newServiceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], newServiceTags = (unsafeRange (fromList [EntertainmentTag, ProductivityTag, VideoTag])) } @@ -812,10 +815,10 @@ testObject_NewService_provider_16 = }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("YA==")))), newServiceAssets = - [ (ImageAsset "" (Just AssetComplete)), - (ImageAsset "" (Nothing)), - (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Just AssetComplete)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)) ], newServiceTags = (unsafeRange (fromList [MedicalTag, PhotographyTag, SportsTag])) } @@ -910,7 +913,7 @@ testObject_NewService_provider_18 = } }, newServiceToken = Nothing, - newServiceAssets = [(ImageAsset "" (Nothing)), (ImageAsset "" (Nothing)), (ImageAsset "" (Nothing))], + newServiceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], newServiceTags = (unsafeRange (fromList [FoodDrinkTag, PollTag, QuizTag])) } @@ -957,7 +960,7 @@ testObject_NewService_provider_19 = } }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("")))), - newServiceAssets = [(ImageAsset "" (Nothing))], + newServiceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], newServiceTags = (unsafeRange (fromList [VideoTag])) } @@ -1004,6 +1007,6 @@ testObject_NewService_provider_20 = } }, newServiceToken = Just (ServiceToken (fromRight undefined (validate ("e4k=")))), - newServiceAssets = [(ImageAsset "" (Just AssetPreview))], + newServiceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], newServiceTags = (unsafeRange (fromList [VideoTag])) } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewUserPublic_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewUserPublic_user.hs index 8f07d130f02..6e2cb637490 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewUserPublic_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewUserPublic_user.hs @@ -19,10 +19,13 @@ module Test.Wire.API.Golden.Generated.NewUserPublic_user where +import Data.Id (Id (Id)) import qualified Data.LanguageCodes (ISO639_1 (SO)) import Data.Misc (PlainTextPassword (PlainTextPassword)) import Data.Text.Ascii (AsciiChars (validate)) -import Imports (Maybe (Just, Nothing), fromRight, undefined) +import qualified Data.UUID as UUID (fromString) +import Imports (Maybe (Just, Nothing), fromJust, fromRight, undefined) +import Wire.API.Asset import Wire.API.User ( Asset (ImageAsset), AssetSize (AssetComplete, AssetPreview), @@ -68,9 +71,9 @@ testObject_NewUserPublic_user_1 = newUserIdentity = Just (PhoneIdentity (Phone {fromPhone = "+35453839"})), newUserPict = Nothing, newUserAssets = - [ ImageAsset "" (Just AssetComplete), - ImageAsset "(\1032842\ESCp\997564u]'\1069371" Nothing, - ImageAsset "\1056720f" (Just AssetPreview) + [ ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete), + ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) Nothing, + ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview) ], newUserAccentId = Just (ColourId {fromColourId = 39125}), newUserEmailCode = diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewUser_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewUser_user.hs index 2b55d8861f7..909d49d59f4 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewUser_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/NewUser_user.hs @@ -36,7 +36,8 @@ import Data.Range (unsafeRange) import Data.Text.Ascii (AsciiChars (validate)) import qualified Data.UUID as UUID (fromString) import Imports (Maybe (Just, Nothing), fromJust, fromRight, undefined, (.)) -import Wire.API.Team (BindingNewTeam (..), NewTeam (..)) +import Wire.API.Asset +import Wire.API.Team (BindingNewTeam (..), Icon (..), NewTeam (..)) import Wire.API.User ( Asset (ImageAsset), AssetSize (..), @@ -76,9 +77,9 @@ testObject_NewUser_user_1 = newUserIdentity = Just (EmailIdentity (Email {emailLocal = "S\ENQX\1076723$\STX\"\1110507e\1015716\24831\1031964L\ETB", emailDomain = "P.b"})), newUserPict = Just (Pict {fromPict = []}), newUserAssets = - [ ImageAsset "\5112\&5\DC4\1019826\95472`\a\SUBG{?" (Just AssetPreview), - ImageAsset "something" (Just AssetComplete), - ImageAsset "KE" Nothing + [ ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview), + ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete), + ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) Nothing ], newUserAccentId = Just (ColourId {fromColourId = -7404}), newUserEmailCode = Just (ActivationCode {fromActivationCode = fromRight undefined (validate "1YgaHo0=")}), @@ -164,9 +165,7 @@ testObject_NewUser_user_7 = { _newTeamName = unsafeRange "\fe\ENQ\1011760zm", - _newTeamIcon = - unsafeRange - "Coq\52427\v\182208", + _newTeamIcon = DefaultIcon, _newTeamIconKey = Just ( unsafeRange diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ServiceProfilePage_provider.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ServiceProfilePage_provider.hs index 15166487c40..7f925083cde 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ServiceProfilePage_provider.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ServiceProfilePage_provider.hs @@ -23,6 +23,7 @@ import Data.Id (Id (Id)) import qualified Data.UUID as UUID (fromString) import GHC.Exts (IsList (fromList)) import Imports (Bool (False, True), Maybe (Just), fromJust) +import Wire.API.Asset import Wire.API.Provider (ServiceTag (BusinessTag, MusicTag)) import Wire.API.Provider.Service ( ServiceProfile @@ -787,7 +788,7 @@ testObject_ServiceProfilePage_provider_11 = }, serviceProfileSummary = "", serviceProfileDescr = "\42170", - serviceProfileAssets = [(ImageAsset "" (Just AssetPreview))], + serviceProfileAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], serviceProfileTags = fromList [], serviceProfileEnabled = True } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ServiceProfile_provider.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ServiceProfile_provider.hs index 5c63ef3d65f..16f340451bf 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ServiceProfile_provider.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/ServiceProfile_provider.hs @@ -23,6 +23,7 @@ import Data.Id (Id (Id)) import qualified Data.UUID as UUID (fromString) import GHC.Exts (IsList (fromList)) import Imports (Bool (False, True), Maybe (Just, Nothing), fromJust) +import Wire.API.Asset import Wire.API.Provider ( ServiceTag ( AudioTag, @@ -58,7 +59,7 @@ testObject_ServiceProfile_provider_1 = }, serviceProfileSummary = "\1008770\60807", serviceProfileDescr = "/Q", - serviceProfileAssets = [(ImageAsset "\ESC" (Nothing))], + serviceProfileAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], serviceProfileTags = fromList [], serviceProfileEnabled = True } @@ -76,10 +77,10 @@ testObject_ServiceProfile_provider_2 = serviceProfileSummary = ")/", serviceProfileDescr = "", serviceProfileAssets = - [ (ImageAsset "" (Just AssetComplete)), - (ImageAsset "" (Just AssetComplete)), - (ImageAsset "" (Just AssetComplete)), - (ImageAsset "" (Just AssetComplete)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)) ], serviceProfileTags = fromList [FoodDrinkTag, TravelTag], serviceProfileEnabled = True @@ -97,7 +98,7 @@ testObject_ServiceProfile_provider_3 = }, serviceProfileSummary = "\ETX* ", serviceProfileDescr = "\136788It", - serviceProfileAssets = [(ImageAsset "" (Nothing)), (ImageAsset "" (Just AssetPreview))], + serviceProfileAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], serviceProfileTags = fromList [], serviceProfileEnabled = True } @@ -114,7 +115,7 @@ testObject_ServiceProfile_provider_4 = }, serviceProfileSummary = "4E", serviceProfileDescr = "(", - serviceProfileAssets = [(ImageAsset "1" (Just AssetComplete))], + serviceProfileAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], serviceProfileTags = fromList [AudioTag, RatingTag], serviceProfileEnabled = True } @@ -145,11 +146,11 @@ testObject_ServiceProfile_provider_6 = serviceProfileSummary = "4>#", serviceProfileDescr = "D\DEL", serviceProfileAssets = - [ (ImageAsset "" (Nothing)), - (ImageAsset "" (Just AssetComplete)), - (ImageAsset "" (Just AssetComplete)), - (ImageAsset "" (Nothing)), - (ImageAsset "" (Just AssetComplete)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)) ], serviceProfileTags = fromList [], serviceProfileEnabled = False @@ -196,7 +197,7 @@ testObject_ServiceProfile_provider_9 = serviceProfileName = Name {fromName = "\EM\73877+\DC2\NUL!\USV\f\1025396\1106635_\1106841H#4\STX\1104704\DEL"}, serviceProfileSummary = "a\1088958", serviceProfileDescr = "AU", - serviceProfileAssets = [(ImageAsset "\DC1" (Nothing))], + serviceProfileAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], serviceProfileTags = fromList [BusinessTag, FinanceTag, PollTag], serviceProfileEnabled = False } @@ -209,7 +210,7 @@ testObject_ServiceProfile_provider_10 = serviceProfileName = Name {fromName = ":h[\1059282\1033090\913Y$\ENQ\NAKE\1086801\186280\STX\US\28752"}, serviceProfileSummary = ",AD", serviceProfileDescr = "s&\118974", - serviceProfileAssets = [(ImageAsset "" (Just AssetPreview)), (ImageAsset "" (Nothing))], + serviceProfileAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], serviceProfileTags = fromList [], serviceProfileEnabled = False } @@ -256,7 +257,7 @@ testObject_ServiceProfile_provider_13 = serviceProfileName = Name {fromName = ":[\".\152322\USvU\1055877"}, serviceProfileSummary = "", serviceProfileDescr = "A", - serviceProfileAssets = [(ImageAsset "B" (Nothing))], + serviceProfileAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], serviceProfileTags = fromList [ProductivityTag], serviceProfileEnabled = False } @@ -286,7 +287,7 @@ testObject_ServiceProfile_provider_15 = }, serviceProfileSummary = "*P`", serviceProfileDescr = "u`\ENQ", - serviceProfileAssets = [(ImageAsset "*" (Nothing))], + serviceProfileAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], serviceProfileTags = fromList [MusicTag, RatingTag], serviceProfileEnabled = False } @@ -303,7 +304,7 @@ testObject_ServiceProfile_provider_16 = }, serviceProfileSummary = "U,", serviceProfileDescr = "S\n", - serviceProfileAssets = [(ImageAsset "" (Just AssetPreview)), (ImageAsset "" (Just AssetPreview))], + serviceProfileAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], serviceProfileTags = fromList [], serviceProfileEnabled = False } @@ -320,7 +321,7 @@ testObject_ServiceProfile_provider_17 = }, serviceProfileSummary = "\SO4c", serviceProfileDescr = "\SI", - serviceProfileAssets = [(ImageAsset "" (Just AssetComplete)), (ImageAsset "" (Just AssetComplete))], + serviceProfileAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], serviceProfileTags = fromList [], serviceProfileEnabled = False } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Service_provider.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Service_provider.hs index 0ab9afa0e72..80a068190cb 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Service_provider.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Service_provider.hs @@ -48,6 +48,7 @@ import URI.ByteString uriScheme ), ) +import Wire.API.Asset import Wire.API.Provider ( ServiceTag ( AudioTag, @@ -238,7 +239,7 @@ testObject_Service_provider_2 = ] ) ), - serviceAssets = [(ImageAsset "" (Just AssetComplete)), (ImageAsset "" (Just AssetComplete))], + serviceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], serviceTags = fromList [], serviceEnabled = True } @@ -431,7 +432,7 @@ testObject_Service_provider_4 = ] ) ), - serviceAssets = [(ImageAsset "" (Just AssetComplete))], + serviceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], serviceTags = fromList [MediaTag], serviceEnabled = False } @@ -570,11 +571,11 @@ testObject_Service_provider_6 = ) ), serviceAssets = - [ (ImageAsset "" (Just AssetComplete)), - (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Nothing)), - (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Just AssetPreview)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)) ], serviceTags = fromList [FinanceTag, FitnessTag, MoviesTag], serviceEnabled = True @@ -677,7 +678,7 @@ testObject_Service_provider_7 = ] ) ), - serviceAssets = [(ImageAsset "" (Just AssetPreview))], + serviceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], serviceTags = fromList [MoviesTag], serviceEnabled = True } @@ -1017,7 +1018,7 @@ testObject_Service_provider_11 = ] ) ), - serviceAssets = [(ImageAsset "" (Just AssetComplete)), (ImageAsset "" (Just AssetComplete))], + serviceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], serviceTags = fromList [], serviceEnabled = False } @@ -1109,9 +1110,9 @@ testObject_Service_provider_12 = ) ), serviceAssets = - [ (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Just AssetComplete)), - (ImageAsset "" (Just AssetComplete)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)) ], serviceTags = fromList [MedicalTag, TravelTag, WeatherTag], serviceEnabled = False @@ -1184,7 +1185,7 @@ testObject_Service_provider_13 = ] ) ), - serviceAssets = [(ImageAsset "" (Just AssetComplete))], + serviceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], serviceTags = fromList [EducationTag, MoviesTag, ShoppingTag], serviceEnabled = False } @@ -1286,7 +1287,7 @@ testObject_Service_provider_14 = ] ) ), - serviceAssets = [(ImageAsset "A" (Just AssetPreview))], + serviceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], serviceTags = fromList [], serviceEnabled = True } @@ -1354,13 +1355,13 @@ testObject_Service_provider_15 = ) ), serviceAssets = - [ (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Just AssetComplete)), - (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Just AssetPreview)), - (ImageAsset "" (Nothing)), - (ImageAsset "" (Nothing)), - (ImageAsset "" (Just AssetComplete)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)) ], serviceTags = fromList [DesignTag, LifestyleTag, QuizTag], serviceEnabled = True @@ -1428,7 +1429,7 @@ testObject_Service_provider_16 = ] ) ), - serviceAssets = [(ImageAsset "" (Nothing)), (ImageAsset "" (Just AssetComplete))], + serviceAssets = [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], serviceTags = fromList [PollTag], serviceEnabled = False } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamDeleteData_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamDeleteData_team.hs index 70730a57c60..fa6633798c6 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamDeleteData_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamDeleteData_team.hs @@ -19,8 +19,11 @@ module Test.Wire.API.Golden.Generated.TeamDeleteData_team where +import Data.Code import Data.Misc (PlainTextPassword (PlainTextPassword)) -import Imports (Maybe (Just, Nothing)) +import Data.Range (unsafeRange) +import Data.Text.Ascii (AsciiChars (validate)) +import Imports (Maybe (Just, Nothing), fromRight, undefined) import Wire.API.Team (TeamDeleteData (..)) testObject_TeamDeleteData_team_1 :: TeamDeleteData @@ -30,7 +33,8 @@ testObject_TeamDeleteData_team_1 = Just ( PlainTextPassword "i>Lx}$\\\RS?\1032425k\142215\1026011`8\NULY\48212|\ESC?\58058S|\EM6X-XK\62237\178988(\SYN@]\57844_\175989\1000844b1\b\82971\992121\1066140\1104485\GSs\155708\6542\1073453C\1070329:e9]x\145287\ETBPxh\51703G\9182h\148850\131171\DC3\DC4hbY\SI\1046696\&3\120879B0\a\53167\173045\995949\US-\ETBO\983699\&9\174970%\GS\95540W\US\ENQ+\NAK\b\CAN!,h@\DLER\1015765\32217\1047195\DEL,\12610}.{^\1090133K*\\\996909_X|9T\rM~\b\SOHKsC=\1010484w\1057801=\FS\ESC\RS\NULwFM%CXf\r@/NU\1054989\&5v\v\SUB2U\1053859K4\7249r\138577Q\1105780[Z\DC4#)\SYN\ETXsR!\vt\EM\US\1036001{\NAKz\1048398\1084558\1043080|\DEL@\47085\\\164262[\45446\1035221D];s\70019c\EM\1088115q\NUL\39248]F};f\DC1mz\1089294$\t\r&SuI!,\v[,\SUBc\57707\&8=\1083051 \DC3\140071v\120412J]\ESC\CAN-\GSF\ETB\DC4\SOHFD@\137590\1101727\SUB\994474<\DC4\164367\DC2\DC2U\1082404\&9\141435MQjmAb+\RS\129179\ETB\ENQK\1085702\2790v;\nQ\40363\97861[uD\1052274{\189293\ACKO\14870@7\RS:+:1\4216\172234Mr\58280(\34625f\1091318\1067121S\39579b\1040841\1071547\vF\35601\990171\r\b\1088916 \1087477m\9195E\EOT\137371\159298{\b=\DELS\NAK=\1009056\7723\8867\DC1\NUL\1028454\SOHh\DLE\SUB\1024764RPt\v7\1113500\159388\vD\1104573S\997271.\DC2}zP\47237E^?\27842\161895S\\S\1098500`\SO&\tU\111019\129639\181462{jj\1096914\SI\DEL|1\SOHR\a\f9\SYN,\1010156b\t^\1035824?V\n\GSz\RS?=@\35005\1103831\DC39'\b\EM8y\ETX&\1044131\1065694\NULu\1061927\SO,)\US\59053b&|h~\36591X\ETXD\987729\\~'\EOT\GS%\FS\DLE|\ETX\1041203\&3s\EOT\SO\r]|7J\1065338Jhi\38217\5537\148956\NUL\SUB%\985637\SO\DC2vjc|m\44638VDN4kW\1034646\119020$\EM\DC1r\21603]@\1086358K\158685\185187:u\1003863\EMG\94717\&8qN2;\DC4)X[p\f\SOH1\1031984\1031232\46840\1082621x.P\165688l\"s#n0\ETX~\US\t\74408[\1051014\1046406\14852`\1087777\DC4\1103137\&1L\135864\994377b\98392\ESC,\ENQ#\STX@A\SO\178614\ETX{\SO\27565\&1sX\19404}|EZJ\RS\GSxoSe\26956l:0g3\ACK" - ) + ), + _tdVerificationCode = Just (Value {asciiValue = unsafeRange (fromRight undefined (validate "123456"))}) } testObject_TeamDeleteData_team_2 :: TeamDeleteData @@ -40,7 +44,8 @@ testObject_TeamDeleteData_team_2 = Just ( PlainTextPassword "?\166974\1059823u\166070V\156206uk\DEL\1014216W\FS\SOH\STX\8328CP6\1080415p\vr\42868F>nX9\ETBI\1033277H\187336WIt]\NUL\1024225!i11p\ESC+'\187602\ESC\SI\ETXz\128844\EM\30393S\1004509`\68342\35440\SOH\"\153382O%\EOT\DLEl\1081113#FO\STXN3K\ACK\61872\DEL>\EM\vq\49514f\1047157\162431\tG\1071802iK\SOH^\147861O'\RS\DC4\96736G.\1102472k:J\ENQ\1030914\SUBAM\ETX'\36945\"6y~4mI\158273(\CAN81cA\13746l\ETX\DC4\\\1025222\&4P\24624\1097175t?zd%s\NUL#I\44727\&31\141208\15975l+a7{\EOT<\1089133z\RS\1010747\161238/\48716\DLEjx\bB\DLE\US\15095&i\DC2\SOH\996246\DLEV\128680e\b~\1005062\1102177r\1006448sHW/L\6809xC2\153652M\1089024\ENQ,\SUB\118927\1041840\178027U\1057168\f\t\143120\ACK<\59653\n\DC1h\1058720#4N\CAN:\1044380\985702\&7\EOTP\1031894\SIu5\SYN\a\fuS\SYN\"Z\NUL?\1052908\99609\61972\DC2N\1072697\15914\v\EOTb^w\1063161tbt\35386uCg'\n\f\DEL\a\1047387x\GSSt\50443\1040666Zke$|~\1028617KixS\841\DC3\1095419j\995187A`Fa2\184680\41393\NAKuOy\DC2@\\fKr\vpnu[W\EOTvU\65546@\SOHx\19292V\"\143982\a\bsQl\DC3\CAN\97358D\1025141\ACK\tH\NAKQWPjJ^S+\986928\1014957\1050268\167552\1097122\129506\1072622\15892=\141574\EM\f\1045924\&6\ad'f\NUL\NUL\v\173465\26156%Vu4\1083260\1033045\&7\STX\DC4W\1069943\NUL\vY\166831f\53269\tb~W\161692\US\v\51528\44135\r*\DC4'{Js\1006163&6\95410c_9Yc\\z\187834\146677\SUB,\1028055\ETB\1051709\1072410\1036468\DC1fVI\NAK#i\1089557pi\1093510\&8\1080013\1050416\DC2\1081978L\1036631\t\74531\SUB<\1092486~;\50008\1055455{\1033009{3L\147152\SUBX[\EM\149325\\_\157906\EM\177995vv6\DEL\143831:xn@\100807X\1077293E)\r\985524q1\925>>t\14597say#\SIX\DC2\172468U9\180603Vc\996994{\DC1\SOH\1006305\STX^.\US`Ad\GS`n\"wI#}D|p)78+54$\a\13717\r\NAK\ETX1\138139H9JKpqyg;\984704\6818L@rY\4441\190106?W\f-S\SUBX\v\1079785TK\151860 " - ) + ), + _tdVerificationCode = Nothing } testObject_TeamDeleteData_team_4 :: TeamDeleteData @@ -60,7 +66,8 @@ testObject_TeamDeleteData_team_4 = Just ( PlainTextPassword "\167486>(=Oz\RS.&\FS6\DEL\170257\EM\1002928j\DC1\97893'HsG\DLE\20059\GSzaJ\NUL\1080467XF$ \1060690\145809\r:)\49151\1073952F\GS3]\a\185029\"\51411_w)d\128946\15212\EOT9\1111179Z\rFs\1003507\v&h\47156\&6JaT= \1044714cy\1050480\ENQ\111284P!'\ENQ@>;G-|SY\1021564\1098567wM\SOH,\1055552f\4883\1076600\155845\68127\&5\1046930+^\990917O\1005927:0(#x4\RSuj\1078909\182583\r@n\"#]\1009578n\1062717j\NUL\ao0P\1091887qu\160610+\\d,\STX\169726q\DELtN\SYN\1099445\53742DvD'\172716\SI\129299\v[\EOT\1013802\\%\990907QS\SUB)\78338\1035121pg4\184481\1016554}\181939\SYN\1036974\SI\FSHu|,/\147470\1038677c\1069053\r|\DLE\1111179\1085354\SO\182406\1007665\74397\1011061\140405\EOT(\988948\1058753'X<\990426`]\47830 ?c\994989\af\r?X\179138&F\1060816\66176M\42801\1016345\DC3]\187040\99798@\b\NUL8\DC2TOT\163647\v\SOHV\RS\STX\DC3\121266\987299<\DC1\191387b\184415P_\NULZ.\1103781'\21496\&1\149873\1086160\DC3\160655\1080705\36096\1072090[~\95381Q~\1003807\985791\EOTvd\1089936 \ETBh@.\ETB G4Q\1091026\&0I!?](\EM\1009092\rd\CAN\r\EM=\1046335\&8\1040668\8102\&5=\1105655\18286\35547>\983842E\DC3d&O\1155_\EOTDM\24125*\1011980<+9\f\111201\ENQ\DLE\ETBzU\DEL9Bq\bs9\188496MY5a\1040748!\45600H2o\999564\SYN\DC3P|\47367\182203\&2\\\ETXe\ENQK\1045299\&8?\SUBq\127482,\99522;%\DLE\174777\FS\ENQ]\ACK\174055F3\169125\ETX\178467\US3Ph\ESC\134497\SI\1043316lJRL\t\60741&\DLEnU$)\129495\1060894\1039833\f\ESCK\EOT4\1096716:\156752\1027507\1079518U\FS\119638zz{g^\DELH\1019515O\ba5bo\STX\DC3i;:\14212\37940\1027439\RSJW>\987912\EMV\1097994B<\1033079\1002491*'\SOH\1059824\&1d;\1060391a\41718H\24770M\78258:li=r~`\23933zC\1084262s\v\1027415T~2\1059089X+I\DC4^BQ\1109659\SOHX\n\142087w\ENQ\1069109C\184166\1073464P\122897\177772\US\FS>\7449\1054606LI|?\SOH\1057802IE\127992\1108354\11244+\998617\52417I\63090\1054253)\DLE\DC1\a\149244>\26994(\DC25X\1110682\68647i{8V[i\190144v\CANj\GSRq\RS\701E|\155116\&3B/;9\39419\DC10\DLE\\g\153395cD\63464G\985591i1\FS$\NUL\1033716\"a?N\\\1102565c$W:\983079L\1044273a\1100761J)]x\DC23\SOB\v\71683\1042847Q\DC1Gg\a\NAK3\23269rI\ETX\1064632>I\984011zIX\DLE\v\984948\&4u{X\1078053\155024\\Xv@\n\147547\ESC\RS13A\13457W !\f\1104523\1108909\64188\&3yr\SUBC26\rU7\SOH/ E\98829}\v\USV\DC2O2;Z|F\1040501\STX7\183792\1100376L\US\991426\1023339]K:/-\1044621\985412U1l\41354\FSRn ]\8766\DEL" - ) + ), + _tdVerificationCode = Just (Value {asciiValue = unsafeRange (fromRight undefined (validate "123456"))}) } testObject_TeamDeleteData_team_6 :: TeamDeleteData -testObject_TeamDeleteData_team_6 = TeamDeleteData {_tdAuthPassword = Nothing} +testObject_TeamDeleteData_team_6 = TeamDeleteData {_tdAuthPassword = Nothing, _tdVerificationCode = Nothing} testObject_TeamDeleteData_team_7 :: TeamDeleteData testObject_TeamDeleteData_team_7 = @@ -83,11 +91,12 @@ testObject_TeamDeleteData_team_7 = Just ( PlainTextPassword "|\SYN\1053350R\ACK\14029\1100974\141152!\1112498Xw\1043175a(\RSX\989391B3\1009126J;?'\989522glvrF\178434ci\1040055\DC2hh-:g\141765\SUB\48247\1051934\SI>$\144167\988649L$\996662\NAK2sq>c%{\1061771zXX]\1062375\bd\160314 C#Y\RSw\GS\RS\1038222\1081158\EOT}\ETB(F\SIg:\1083021\&4+i\1011266>b_\ESC\191314\1056764\ACKm\1013162~c1\143978z7BM\n\EOT\f}bo\1096197'\991291\1007734&<5\SOU\SUB\DC1\131235+\1050870A\FSS-D$N\190895w\49045S-L\144414\1093889i\167808}EC\1081955\"\1034844\98599bq\1037627\SOH\153279C\33744Jh\1020874e\78082\1083389\&1%\STX_BD\1109230\NUL\144134g<.\167270\CAN\ENQ9:\182574\US:\1034863\EMT\SUBSH\"\1103704\DC1\ETBV\DC4{!\FSW\a\13340{\182394@A4!yV\f\EOTVY\ETBP#\1059240\1003701\1106905sysSo\1098350h1B\5570\"\9350!\DC2\1031344\NUL\1099868\ENQ\CAN\nZUk\183853\986232\DC4\SUBG\1107741jv\1040544\&35F\178531" - ) + ), + _tdVerificationCode = Nothing } testObject_TeamDeleteData_team_8 :: TeamDeleteData -testObject_TeamDeleteData_team_8 = TeamDeleteData {_tdAuthPassword = Nothing} +testObject_TeamDeleteData_team_8 = TeamDeleteData {_tdAuthPassword = Nothing, _tdVerificationCode = Nothing} testObject_TeamDeleteData_team_9 :: TeamDeleteData testObject_TeamDeleteData_team_9 = @@ -96,21 +105,23 @@ testObject_TeamDeleteData_team_9 = Just ( PlainTextPassword "5\tt\US\STX.-:\150454\1008817\1108150\7302\180616!z\ACK)\1036966,\36158cH|\ESC`\983356,\1056228\DLE_jb\SYN\DLE\999616\SI(Y\52758@\STX$\33211m!;P\SO\165645t\FS?l\1084281}Ui\\\ENQb\155094RJ\1036671\ACK\39953*W\1019548\DC1\986051\DC3Q\1086809J\DELh\SOHY5i-\142840\DC3K@W\1038530i\14430p\ESC2[,J\DC3\ACK\RS\f\f/\1048120!\5751-\SOHXBq;V\98370\1018087\SO)u\SO>(\128175\138077k\1092224\STXR\35799\ACK?1\aw<\SOH3\rH:\RSYA\SI|IOV\CANX\NAK6\tM\985927Z\1083464*\986212\\\fl\134144\&9\1087151\DEL\SUB\CAN\DC1\\\a\DC1\1088970H0\nk\EOT\DC2\DC4\SUB\1002532XO\171906|!\160319 \1088766\40807\1100379W\NULXd\993779L\140128\GS\DLE\98366c#s=\DELg\155615\r0$\r\vD|\GS\993376:H4\STXMg\27349Qf\43148/,z\62636i\a\1048347#\95511h\57479mF\1063847]h\1089472Z\989287\SUB*\1099020\&6\CAN\DC3Bq\169694l\1090008\1034040\ETX=-8QP\ENQ3\1083969\29219i\52068\USXG:\DELE3(\ESC/\1037295 \188038\&3\ACK\1037819\29071Y\163233\nn\1008010\&7SN\SUB4<\1019928E\aUDeBUIJL\42492w_\1008912hGI\DC1w:nJ\ACKfW\52528\994039\a2v\ts`\119066\1004985\b1'j:\1063674\ETX<'\64040\FS91i>T:XD\CAN\a\1078993!M\EMwc@\174048\f_|\CAN\DELM\50126tm\1047367\SYN\26017:+Xt\1016079\1028901\15823\17821\1008174^ )!\SO\42711\1029362y&\992585\52874k\996506\\\1066493-&\DC1\SUB\1002828\154321\r\47583\vS\1095338c,\1104404\&4\NUL0E^i\1081545\1015786\8631\t\100419]p\1005291\137798\SOq{Z8\1085622K\15273\36480]\\\SUB`a|\64088\GS\138266uk#E,z=+!/\SUB\162983\SI$b\4525\&5\tio\99777\SYN/\3242\140303<\1090896o\GS\ETX\EM\44779$U'\1037588\999481vuQr07\19473\t5\128968\CAN\983281\994806\CAND\131278\ENQI\1044258DXL6I\ESCB.P\3930\1090709\SI7\ETBPu\SIK:\RS\8017}S>\GS\997008\v\ENQr;~`Co,/,\148290\USU\32073\\ngr\997268i\149277\GS\1075609Y\1110379\v5FajE#!\aF\50300z+\GS~Ly\986342\1095807\SUBExbmgxU[\22230]b\ENQmo\983838k^h\992093\NAKD\173627\144512B\ENQ2\1006334f\132015PWU\f}\166557\&3d%y\165250\44801MN\38044r\159335?K\34409\ACK\EOT\44504\78068\DLEO\15676G~1 g\985974ne\13669\1075356U\1060554\"B\SUB\1016699\DLE\994930Bj\FS\SOH}wDG\179165\EM#\DC4Vc\\\fz9'\ACK2\SUB\SOI\t\1102083\nb\70657\vR%\t\ENQ\24196DJA3>\RSD\986251V\CAN]\ETX\SI\985787R\42725\1105102dGFO\f\1027792\14140{\STX\161088'\1063310\1014846\183656\STX\SI\DELjb\tQf-9\n-\1012873r\78321m\180126pJlc \133719\988689'\US\ACK>c?sXEN3\1007843\DC2l\1029759\EOTJu'\74452y\v\996071oM[\3007QB&\28108\1053307\27606\&2\v\1072650\ESC\132795\1001058C[O\DEL~h\\8*c\145008O \aDQZ\1031791yb \GSX\1004099`\989803\b\1096355\DLE\1085472\DC3\1056898w\997883)\DC1\EM.\11322\149106\158323K-G\1029431\DC2Hg3\"t\SOH\EOT\1060954b$@\176852\DC2OP\ESC\1038106Ip\28195\&9ty\1036676!" - ) + ), + _tdVerificationCode = Just (Value {asciiValue = unsafeRange (fromRight undefined (validate "123456"))}) } testObject_TeamDeleteData_team_17 :: TeamDeleteData @@ -159,7 +174,8 @@ testObject_TeamDeleteData_team_17 = Just ( PlainTextPassword "*\12110m,b\994288OTCM{x:'\SI,\1040212\DC12ffV\987602\1070011\f\1109675uf\SYN(-@ B<\50301*H9\1099931Qa.%`.6\DC2GQ\ENQ\USt\1058323Mge_gL\t#\CAN\ETX\1111199vG\ETXg\n:;x1\1035394n\SUB:l`^\1045359\&73]IiN\1048742/\bOs" - ) + ), + _tdVerificationCode = Nothing } testObject_TeamDeleteData_team_18 :: TeamDeleteData @@ -169,7 +185,8 @@ testObject_TeamDeleteData_team_18 = Just ( PlainTextPassword "\1063567;7/\DC2\29449\94019\DC3n\122882\31258S9\ACK]\3850\&8P\t\1084384\&2\73795#\181815\&7~`\1100343\175281\1028579~n\51679\EOT\FS\v4?nPy5\EM\EM\1095405;C\nW\78148\&4\SUBTd\147540\CANyR7\ESC/V\1089526\STX\v\1000358,\DC2bv\164290K\92285{mU\1060894)\DC1\73974.T\nB=\STXlsux\DC4\1077175\&1:\CANL\1110743Hh\NUL\174066n/\1102525\162921\993112\59381\DELq\46095*)}\1098746\1037636\1101970\1088021\NAK\CAN\CAN8\USU\b@jr\DC4&\991478\1075234_a\EOT+\SYNm\4275\SYN\DC4Kw\DC2m:P\25327\&6V'G5@\EM Z\DC1s\142323cq\SUBP\DC4.d\36321\NULK\27185\NAK\47378s\1031768j/\tu\158145\ETX\1010872\SOHT\48868f:C[\fJf\1087126\142737\DEL[&hi%)\136397\&2\184974j\\\1072975eM\1085470uvK\v\CAN\FSCw\v\1031529$\EOT7\STX\1027727\FS0\1199\&2L;N\1073075\"H[k\1073178\DC2\1009501f\EMwuSE^\1107505\vf\STXf\DC1\14113\EM`XY\21048\&5B\13496\1022663\20371w\51905Ot\44037\&2T3Wb\SO9!:\RS\FS\1006766\1097727j\ESC~DY\SOHaN\n\1065381Jw" - ) + ), + _tdVerificationCode = Just (Value {asciiValue = unsafeRange (fromRight undefined (validate "123456"))}) } testObject_TeamDeleteData_team_19 :: TeamDeleteData @@ -179,8 +196,9 @@ testObject_TeamDeleteData_team_19 = Just ( PlainTextPassword "_\r}u\1075299Z!X3\160102\ACKKOM\1081288%\DC2\65730B\SO\180051L%w\"\ENQ q]\ACK'\a.Ox\1105498\1057171\\7c\r\33864\21114{\DC1o\1105122uO\CAN42n[k4\rn\152690O\SYNP\135580\1110329\n\1042613N\1061340\16437\\H\SYNbr^\1003766\1060894i\1109911\SUBy<\nO\ACKTp\SI\35591N`f\26658}!~\1082799\NAKcp*!8l\ACK\DC2 \37542\DC3\61149f\NUL1\40820\NAKT\24987\179326 _\94795\&0q%;\119094f8E0<\997746u\SO}\1031140h\35142+a\n\1008145N\1041221I`\DC1\1032664\191259\1113574\131171.3Kj\37035\RS\39573H\SIzI'\NAKH\69706\152434dN\176099\991214\990295`L\ttN\1013377\DELQjV\US1i#Ag\DLE6.\1112310eZ*r\EOT\1024019-b`\1102712\&8\f\SOH\1010355nK\170543fp\187486RW\FSs\994965mH\1045304\30604>\v\1049211r!}\53167'W\993809\1098296qE\EM\181344#y\fW?#\DC3\1072094G\\l5\1068018\986650\1038548\141195\1102837#vsV\1016098\&8PP\EOT\DC4TZm?\167010CC\ESCR;\USw\\Df[JjbN`X\95418\43924x\33016\&9^\98002\127310~\ETB\bzWl&\r\1032458K\996614\154337\b!\SUBL\a\1052800t-w2N\14407\b\EM\177903\37957iG\DEL\1014649e\ETX:q\f4qKRr\ETB48DJTS\1113548[\v\r\DC1\100102TF\1044374\&8s!}SS7\v/\165368)T\GS\ncjR\156817\1023594\NAK\v\167937-%&^\EOT\167120\994763I(\"B\EMP\DELbl\DC4\GSl\1105622\NUL\1096218&\40531&\60243\SO$\GSU\ETX:}l&j\bQ\1104300S1v\1049270%Q\181048^\994564\1042226\EM\ETX,%\"\161513s\SOrfB\STX\26259\US7i\DC3\ACK\26195\"G(\11669%\SUBn>\1063303\&4\995605c\SYNq@\12458*I\ENQ\60070\14051o;\ACK\1043958)\"B|\1009891\170532\1051466\22730\&3\FSg\DC3?\1100853}\SO8%Xl\DC3\16332[O,\125199g%S\160477l\ENQ\"(H;+\STX}J\DC2\1091067\SYN\RS?7\181974\v\RS\59550X~Y$\RS^\18330\1036144\DLE\144451" - ) + ), + _tdVerificationCode = Just (Value {asciiValue = unsafeRange (fromRight undefined (validate "123456"))}) } testObject_TeamDeleteData_team_20 :: TeamDeleteData -testObject_TeamDeleteData_team_20 = TeamDeleteData {_tdAuthPassword = Nothing} +testObject_TeamDeleteData_team_20 = TeamDeleteData {_tdAuthPassword = Nothing, _tdVerificationCode = Nothing} diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamList_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamList_team.hs index 9bf6832fbb0..2a448754c25 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamList_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/TeamList_team.hs @@ -23,7 +23,8 @@ import Control.Lens ((.~)) import Data.Id (Id (Id)) import qualified Data.UUID as UUID (fromString) import Imports (Bool (False, True), Maybe (Just, Nothing), fromJust, (&)) -import Wire.API.Team (TeamBinding (Binding, NonBinding), TeamList (..), newTeam, teamIconKey) +import Wire.API.Asset +import Wire.API.Team (Icon (..), TeamBinding (Binding, NonBinding), TeamList (..), newTeam, teamIconKey) testObject_TeamList_team_1 :: TeamList testObject_TeamList_team_1 = @@ -33,7 +34,7 @@ testObject_TeamList_team_1 = ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -41,7 +42,7 @@ testObject_TeamList_team_1 = ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001")))) ("") - ("") + DefaultIcon (NonBinding) & teamIconKey .~ (Just "") ), @@ -49,7 +50,7 @@ testObject_TeamList_team_1 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) @@ -65,7 +66,7 @@ testObject_TeamList_team_2 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001")))) ("7") - ("\174380") + DefaultIcon (Binding) & teamIconKey .~ (Just "@") ), @@ -73,7 +74,7 @@ testObject_TeamList_team_2 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "") ) @@ -89,7 +90,7 @@ testObject_TeamList_team_3 = ((Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000200000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000002")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) @@ -105,7 +106,7 @@ testObject_TeamList_team_4 = ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000001")))) ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000")))) ("\1065164") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -113,7 +114,7 @@ testObject_TeamList_team_4 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ) @@ -129,7 +130,7 @@ testObject_TeamList_team_5 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")))) ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -137,7 +138,7 @@ testObject_TeamList_team_5 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "") ), @@ -145,7 +146,7 @@ testObject_TeamList_team_5 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -153,7 +154,7 @@ testObject_TeamList_team_5 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -161,7 +162,7 @@ testObject_TeamList_team_5 = ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "") ), @@ -169,7 +170,7 @@ testObject_TeamList_team_5 = ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ), @@ -177,7 +178,7 @@ testObject_TeamList_team_5 = ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ) @@ -193,7 +194,7 @@ testObject_TeamList_team_6 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000")))) (" ") - ("\1027039") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) @@ -209,7 +210,7 @@ testObject_TeamList_team_7 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000001")))) ("") - ("\DC1") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ), @@ -217,7 +218,7 @@ testObject_TeamList_team_7 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001")))) ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "") ) @@ -236,7 +237,7 @@ testObject_TeamList_team_9 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -244,7 +245,7 @@ testObject_TeamList_team_9 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -252,7 +253,7 @@ testObject_TeamList_team_9 = ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ), @@ -260,7 +261,7 @@ testObject_TeamList_team_9 = ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "") ), @@ -268,7 +269,7 @@ testObject_TeamList_team_9 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -276,7 +277,7 @@ testObject_TeamList_team_9 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ) @@ -298,7 +299,7 @@ testObject_TeamList_team_12 = ((Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000200000001")))) ((Id (fromJust (UUID.fromString "00000002-0000-0000-0000-000100000000")))) ("/\38175") - ("bi") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ) @@ -317,7 +318,7 @@ testObject_TeamList_team_14 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -325,7 +326,7 @@ testObject_TeamList_team_14 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001")))) ("") - ("") + DefaultIcon (NonBinding) & teamIconKey .~ (Just "") ), @@ -333,7 +334,7 @@ testObject_TeamList_team_14 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")))) ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001")))) ("") - ("") + DefaultIcon (NonBinding) & teamIconKey .~ (Just "") ), @@ -341,7 +342,7 @@ testObject_TeamList_team_14 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001")))) ("") - ("") + DefaultIcon (NonBinding) & teamIconKey .~ (Just "") ), @@ -349,7 +350,7 @@ testObject_TeamList_team_14 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000")))) ("") - ("") + DefaultIcon (NonBinding) & teamIconKey .~ (Just "") ), @@ -357,7 +358,7 @@ testObject_TeamList_team_14 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) @@ -373,7 +374,7 @@ testObject_TeamList_team_15 = ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ), @@ -381,7 +382,7 @@ testObject_TeamList_team_15 = ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -389,7 +390,7 @@ testObject_TeamList_team_15 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Nothing) ), @@ -397,7 +398,7 @@ testObject_TeamList_team_15 = ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "") ), @@ -405,7 +406,7 @@ testObject_TeamList_team_15 = ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001")))) ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Nothing) ) @@ -421,7 +422,7 @@ testObject_TeamList_team_16 = ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000002")))) ((Id (fromJust (UUID.fromString "00000002-0000-0000-0000-000200000000")))) ("\170783") - ("e\20069") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "\1113463(") ) @@ -440,7 +441,7 @@ testObject_TeamList_team_18 = ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000002")))) ((Id (fromJust (UUID.fromString "00000002-0000-0000-0000-000000000000")))) ("W1") - ("!") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) @@ -456,7 +457,7 @@ testObject_TeamList_team_19 = ((Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000200000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200000002")))) ("") - ("7") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "\189413(") ) @@ -472,7 +473,7 @@ testObject_TeamList_team_20 = ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Nothing) ), @@ -480,7 +481,7 @@ testObject_TeamList_team_20 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000")))) ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "") ), @@ -488,7 +489,7 @@ testObject_TeamList_team_20 = ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001")))) ((Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "") ), @@ -496,7 +497,7 @@ testObject_TeamList_team_20 = ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000")))) ("") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Team_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Team_team.hs index bb67fc2c4b0..8298467e0c0 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Team_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Team_team.hs @@ -23,7 +23,8 @@ import Control.Lens ((.~)) import Data.Id (Id (Id)) import qualified Data.UUID as UUID (fromString) import Imports (Maybe (Just, Nothing), fromJust, (&)) -import Wire.API.Team (Team, TeamBinding (Binding, NonBinding), newTeam, teamIconKey) +import Wire.API.Asset +import Wire.API.Team (Icon (..), Team, TeamBinding (Binding, NonBinding), newTeam, teamIconKey) testObject_Team_team_1 :: Team testObject_Team_team_1 = @@ -31,7 +32,7 @@ testObject_Team_team_1 = ((Id (fromJust (UUID.fromString "00000004-0000-0003-0000-000200000000")))) ((Id (fromJust (UUID.fromString "00000003-0000-0001-0000-000100000002")))) ("TJ\EOT") - ("Jw\USTB") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "\1040673V") ) @@ -42,7 +43,7 @@ testObject_Team_team_2 = ((Id (fromJust (UUID.fromString "00000004-0000-0003-0000-000000000004")))) ((Id (fromJust (UUID.fromString "00000000-0000-0004-0000-000000000001")))) ("Yc\5828") - ("\1104693\t5") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "\34417R3q") ) @@ -53,7 +54,7 @@ testObject_Team_team_3 = ((Id (fromJust (UUID.fromString "00000004-0000-0003-0000-000000000003")))) ((Id (fromJust (UUID.fromString "00000003-0000-0004-0000-000100000000")))) ("2E\1092885") - ("") + DefaultIcon (NonBinding) & teamIconKey .~ (Just "s\1056436") ) @@ -64,7 +65,7 @@ testObject_Team_team_4 = ((Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000100000004")))) ((Id (fromJust (UUID.fromString "00000004-0000-0000-0000-000100000003")))) ("\177218\bk") - ("\1078494u\FSC\SOH") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "X") ) @@ -75,7 +76,7 @@ testObject_Team_team_5 = ((Id (fromJust (UUID.fromString "00000004-0000-0003-0000-000000000004")))) ((Id (fromJust (UUID.fromString "00000000-0000-0004-0000-000200000002")))) ("\ACK\99388\20164") - ("\1073797") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "?&\ESC") ) @@ -86,7 +87,7 @@ testObject_Team_team_6 = ((Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000000000001")))) ((Id (fromJust (UUID.fromString "00000000-0000-0003-0000-000000000003")))) ("\1018732x\1035024]\15985") - ("_'\DC1\STX") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) @@ -97,7 +98,7 @@ testObject_Team_team_7 = ((Id (fromJust (UUID.fromString "00000002-0000-0003-0000-000000000002")))) ((Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000400000000")))) ("\9929\1053910\1017456\&7\1059453") - ("X\n|\1041562") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "\96549") ) @@ -108,7 +109,7 @@ testObject_Team_team_8 = ((Id (fromJust (UUID.fromString "00000003-0000-0004-0000-000000000001")))) ((Id (fromJust (UUID.fromString "00000002-0000-0003-0000-000400000001")))) ("\r\37334{\DC3\\") - ("\57585\1029014") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) @@ -119,7 +120,7 @@ testObject_Team_team_9 = ((Id (fromJust (UUID.fromString "00000004-0000-0002-0000-000200000003")))) ((Id (fromJust (UUID.fromString "00000002-0000-0000-0000-000000000004")))) ("G[Hu{") - ("d\ETXU") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) @@ -130,7 +131,7 @@ testObject_Team_team_10 = ((Id (fromJust (UUID.fromString "00000002-0000-0000-0000-000300000004")))) ((Id (fromJust (UUID.fromString "00000000-0000-0004-0000-000300000000")))) ("\1043846") - (" ") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "\1107305") ) @@ -141,7 +142,7 @@ testObject_Team_team_11 = ((Id (fromJust (UUID.fromString "00000002-0000-0004-0000-000300000003")))) ((Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200000003")))) ("") - ("b@\STX\47358") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) @@ -152,7 +153,7 @@ testObject_Team_team_12 = ((Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000000000001")))) ((Id (fromJust (UUID.fromString "00000003-0000-0001-0000-000200000001")))) ("yR\EOTU}") - ("P\185409") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "J\SI`\1074001\DEL") ) @@ -163,7 +164,7 @@ testObject_Team_team_13 = ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000200000002")))) ((Id (fromJust (UUID.fromString "00000003-0000-0002-0000-000200000004")))) ("E\ESC") - ("") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Nothing) ) @@ -174,7 +175,7 @@ testObject_Team_team_14 = ((Id (fromJust (UUID.fromString "00000000-0000-0004-0000-000100000004")))) ((Id (fromJust (UUID.fromString "00000002-0000-0002-0000-000100000003")))) (".\27232,") - ("") + DefaultIcon (NonBinding) & teamIconKey .~ (Just "N\EM\ETX") ) @@ -185,7 +186,7 @@ testObject_Team_team_15 = ((Id (fromJust (UUID.fromString "00000003-0000-0004-0000-000000000003")))) ((Id (fromJust (UUID.fromString "00000004-0000-0000-0000-000400000002")))) ("#k\NUL,;") - ("yM\RS\ENQ") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "T\f)\tR") ) @@ -196,7 +197,7 @@ testObject_Team_team_16 = ((Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000200000000")))) ((Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000400000004")))) ("") - ("Se") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "\SOHC") ) @@ -207,7 +208,7 @@ testObject_Team_team_17 = ((Id (fromJust (UUID.fromString "00000003-0000-0004-0000-000400000004")))) ((Id (fromJust (UUID.fromString "00000003-0000-0001-0000-000000000004")))) ("\t\b ") - ("A\1029674'W") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Nothing) ) @@ -218,7 +219,7 @@ testObject_Team_team_18 = ((Id (fromJust (UUID.fromString "00000002-0000-0002-0000-000200000002")))) ((Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000100000002")))) ("\23385\1046442") - ("_\1029329\170131") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "x:\40938L") ) @@ -229,7 +230,7 @@ testObject_Team_team_19 = ((Id (fromJust (UUID.fromString "00000003-0000-0000-0000-000100000001")))) ((Id (fromJust (UUID.fromString "00000004-0000-0003-0000-000200000004")))) ("P\187859;gi") - (")\ETB\ENQ") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (Binding) & teamIconKey .~ (Just "V>A") ) @@ -240,7 +241,7 @@ testObject_Team_team_20 = ((Id (fromJust (UUID.fromString "00000000-0000-0004-0000-000400000003")))) ((Id (fromJust (UUID.fromString "00000000-0000-0004-0000-000000000004")))) ("\191094c") - ("\1019354I\STX\ETX") + (Icon (AssetKeyV3 (Id (fromJust (UUID.fromString "55b9ad19-315c-4bda-8c0f-5d7b0e143008"))) AssetEternal)) (NonBinding) & teamIconKey .~ (Just "v0\1099892\&3") ) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/UpdateClient_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/UpdateClient_user.hs index b627f7eb993..2b331247872 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/UpdateClient_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/UpdateClient_user.hs @@ -19,9 +19,11 @@ module Test.Wire.API.Golden.Generated.UpdateClient_user where -import Imports (Maybe (Just, Nothing)) -import Wire.API.User.Client (ClientCapability (ClientSupportsLegalholdImplicitConsent), UpdateClient (..)) -import Wire.API.User.Client.Prekey (Prekey (Prekey, prekeyId, prekeyKey), PrekeyId (PrekeyId, keyId), lastPrekey) +import qualified Data.Map as Map +import Imports +import Wire.API.MLS.Credential +import Wire.API.User.Client +import Wire.API.User.Client.Prekey testObject_UpdateClient_user_1 :: UpdateClient testObject_UpdateClient_user_1 = @@ -32,7 +34,8 @@ testObject_UpdateClient_user_1 = ], updateClientLastKey = Just (lastPrekey ("")), updateClientLabel = Nothing, - updateClientCapabilities = Nothing + updateClientCapabilities = Nothing, + updateClientMLSPublicKeys = mempty } testObject_UpdateClient_user_2 :: UpdateClient @@ -44,7 +47,8 @@ testObject_UpdateClient_user_2 = ], updateClientLastKey = Nothing, updateClientLabel = Just "\14793\13068\SOH\74214\US", - updateClientCapabilities = Nothing + updateClientCapabilities = Nothing, + updateClientMLSPublicKeys = mempty } testObject_UpdateClient_user_3 :: UpdateClient @@ -56,7 +60,8 @@ testObject_UpdateClient_user_3 = ], updateClientLastKey = Just (lastPrekey ("L\100005")), updateClientLabel = Just "\NUL\12245B\ACK", - updateClientCapabilities = Nothing + updateClientCapabilities = Nothing, + updateClientMLSPublicKeys = mempty } testObject_UpdateClient_user_4 :: UpdateClient @@ -70,7 +75,8 @@ testObject_UpdateClient_user_4 = ], updateClientLastKey = Just (lastPrekey ("")), updateClientLabel = Just "M\1066358^YH:l", - updateClientCapabilities = Nothing + updateClientCapabilities = Nothing, + updateClientMLSPublicKeys = mempty } testObject_UpdateClient_user_5 :: UpdateClient @@ -83,7 +89,8 @@ testObject_UpdateClient_user_5 = ], updateClientLastKey = Just (lastPrekey ("Cs \74536=")), updateClientLabel = Just "I\1038139\tCzGW\1034813", - updateClientCapabilities = Nothing + updateClientCapabilities = Nothing, + updateClientMLSPublicKeys = mempty } testObject_UpdateClient_user_6 :: UpdateClient @@ -96,7 +103,8 @@ testObject_UpdateClient_user_6 = ], updateClientLastKey = Just (lastPrekey ("")), updateClientLabel = Nothing, - updateClientCapabilities = Nothing + updateClientCapabilities = Nothing, + updateClientMLSPublicKeys = mempty } testObject_UpdateClient_user_7 :: UpdateClient @@ -114,7 +122,8 @@ testObject_UpdateClient_user_7 = ], updateClientLastKey = Nothing, updateClientLabel = Just "D9", - updateClientCapabilities = Nothing + updateClientCapabilities = Nothing, + updateClientMLSPublicKeys = mempty } testObject_UpdateClient_user_8 :: UpdateClient @@ -123,7 +132,8 @@ testObject_UpdateClient_user_8 = { updateClientPrekeys = [Prekey {prekeyId = PrekeyId {keyId = 4}, prekeyKey = "_Xx;"}], updateClientLastKey = Nothing, updateClientLabel = Just "8\NAKD\57788\111128", - updateClientCapabilities = Nothing + updateClientCapabilities = Nothing, + updateClientMLSPublicKeys = Map.fromList [(Ed25519, "bm90IHJlYWxseSBhIHB1YmxpYyBrZXk=")] } testObject_UpdateClient_user_9 :: UpdateClient @@ -139,7 +149,8 @@ testObject_UpdateClient_user_9 = ], updateClientLastKey = Nothing, updateClientLabel = Just "a\24415\\\a\1053371\1036429\1056854\20649;~k\GSP\NAK\ACK\175008s\1051918A\150295\ESC\NULpY\1054181\26848\EM\1078098T\1011719\992748!W\EOT\SO\152351\v]\v\ETB\98006N\1097932\143101\9071\f8]J\14943\SI\EMY\29869p\NAKvk\99744\1017040\176615\998969\STX\151238q\1035677\RS\v\1030236\&6\f\SUB\DC4\\\SOHw\DC1w[\DC3\1103346r+\983054/s\10708\995966\CAN\DC3~/\SO\1039052\1022548%F\DC4h\1000751\78726z\EOT\1015388\ETXdt-b\157874,DzM\1008898G\1039612\16538\1074902\DC2\18234\16087\&0-pE42\t:<\66329}[\ETX~lac\42511/\a\151380Z,(\\\1077666\127957GM\190643#\191090\SOH+%\1084834\STX\175168|\1007726\\\28896\EM\51660\1094971\a[\57676\1023212\1053746\f2@cczh3?`\n$\STX\1094726\EM\fN\180929z/D\179845.(]g9\3442o\STX%{w\1075429\&4m\STXF\ENQ\49942\16059\CANm\DC1\ENQ/\ESC\42264\1028339QM\991027\176346'43\36345~\t\1036026!\v\14557`qY\1088128zm\DC4fRZGvL\ETB(t\1007154\SYNswr\145599\58315%\1043578\NAK%\1082059G\1691l'\ACK\1029069\137530\170139\149719 \8297\NAK\f>@\40665\1029420\CANu\STX\143750Y\GSVj\DC2\t@O\184863\44709\&4\rf\b\1002476\r_F\DC3\NUL\47001\ETXX$#\t\1093906\ESC7\EOT\b\983099\143369\SO\ETB\EOTA\185268\159378\1015274/:N\DC3\1068202\&1D\96979\1042904 V\DLE\SUB\1087165#\20680\1005166\&8\ETB\a\DLE\RS\995866\USP\ETB\SOH7\r|L\145137R j\ESC\SOH2F>dV=\EMr?\1046227\119883\"&\DC1O\11375\SODuQL$\1032099d\US\157568` <1\\O\445\993915/H\f\r\143532Ah\1032005\ETX\162288uu.lf\1057288/1\1106120\1028078/\7411\138984`9\bq\SUB[Z`\118961eLNyTq\1048960k?{\nWg\72112\1100487\120674q\151928u-\DLE\1008080(\DLE\DC1f\127138\ETB,\rP\7088\&4V\40697\159724(7)..\70295$\n\SOH\78896\989166\92348\134295\FS\5319\47941/0\166710:\94593\SI{]$&\1074979m\1114097\&0\144077\&7)\183400b5f\SYNGyYxU):\1015140L\USQd\121515p\ESC?<\DEL7\DC4W\ESCN\45294;\a\987395\NULm\143966K\ETX\146218\51248\ETB\17306\"\987854*S{G\349r\1010831g\DC4>\NUL\SOH\97274i\NUL\NAKk\ENQK\20758r\1027971!rE\t^\78529@|h'0F'\1037224\157621\1023969\&9)\SYN^\ACKm^\STX\1078787M]\181147R\12517+\1015063^p\43086\&2AzeS\DEL`\141901\DC4\985596\182797e\ENQ\CAN\ETB\36060h=0&kp4\ETB\1023228c\999060\ENQ8$\STX\EOTk\t\CAN\173228y]M\bA\64661x(\STXV\fT\vOO=\1086015`D\1031911i*c\1010700g3\RS\998099\FS\fr\7033g\181534MX\15333\136960\43015x\1089585Rz\154544(P${\98672\DC1*~e\n\t0]z\DC3\EMY\173001\1112133g\152066!\182207@\ACKp\162647\1015149=\62520X\1013875r\65890\1025377\&3u\t\STX\SO\139037n\DC1`\42999;,\DC4\161373D.:\SOH") ), - updateServiceAssets = Just [(ImageAsset "\35113s\1105959" (Just AssetComplete))], + updateServiceAssets = Just [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete))], updateServiceTags = Just (unsafeRange (fromList [WeatherTag])) } @@ -89,10 +92,10 @@ testObject_UpdateService_provider_2 = ), updateServiceAssets = Just - [ (ImageAsset "" (Nothing)), - (ImageAsset "\182860" (Just AssetPreview)), - (ImageAsset "#" (Just AssetPreview)), - (ImageAsset "" (Just AssetComplete)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)) ], updateServiceTags = Just (unsafeRange (fromList [AudioTag, EducationTag, WeatherTag])) } @@ -109,9 +112,9 @@ testObject_UpdateService_provider_3 = updateServiceDescr = Nothing, updateServiceAssets = Just - [ (ImageAsset "t\100362" (Just AssetPreview)), - (ImageAsset "" (Just AssetComplete)), - (ImageAsset "\fu\US" (Just AssetPreview)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)) ], updateServiceTags = Just (unsafeRange (fromList [GraphicsTag])) } @@ -136,7 +139,7 @@ testObject_UpdateService_provider_4 = ( unsafeRange ("<\1090124#FE\1086106s*!\62593\DC4;\31772^WMr\1060834\&8RB\NAK\128903\1007550$\t,C\ETX0\11070\1023381\58817\27286j\\nF\175225W\1113162\&7\SO@\94549w\ENQ*g>=-m+\128253\997485JpQGB\1044309\&4\1060466\SOH!'w*M;c\ENQ\98836\1003286\&3)R\29851sZVy\DLEV\ETX\144137\US\EMJ08\DC2\\\ENQ\1081494\1001187a\1018101$\SUBt\181563\DC3f=\141465%:!\\6\172907\aES\1016438;|\67631\1046123*\32113@1p*Y;uGE\1069430e\1102664\f5\SOHWA\ENQ|\SOH\ESC\1009746\&4:*}$7]Z{/*\DC3`\STX&\155842P\t\1053171N\SYNRL&\SI\169000\USs\162298c2t\NUL\SOH)\26500\&2/rm\1051265wkD>}\1070334\NUL\DLE\128068\178727\&1%\1005755\ra\35525J\13316\19695,\1056622\nU\NAKY\1011081\1058839-#!\SYN3\190953\83058z\ESCl!`\DC3e\1102400\t}GW[P\ESC\1004676\189533[\1061401\ESCJF\21715\&9RA\1068756\"\t7[\1111740\n5\NAK~mEU<\nL|)&.Cu5T\121142 y>\9286$^\45932") ), - updateServiceAssets = Just [(ImageAsset "h" (Just AssetComplete)), (ImageAsset "\180491)p" (Nothing))], + updateServiceAssets = Just [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], updateServiceTags = Just (unsafeRange (fromList [BooksTag, BusinessTag, SocialTag])) } @@ -152,12 +155,12 @@ testObject_UpdateService_provider_5 = updateServiceDescr = Just (unsafeRange ("\ETBI\\.z\96610\CANQaIC\1065269\32625\36609k\1091140J\SUB8/\110715")), updateServiceAssets = Just - [ (ImageAsset "7_" (Just AssetComplete)), - (ImageAsset "p" (Nothing)), - (ImageAsset "\19289u9" (Just AssetComplete)), - (ImageAsset "\172627\182076*" (Nothing)), - (ImageAsset "\1014021\DLE" (Just AssetComplete)), - (ImageAsset "\1004268\52075\985717" (Just AssetPreview)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)) ], updateServiceTags = Just (unsafeRange (fromList [MediaTag, ShoppingTag, SportsTag])) } @@ -176,7 +179,7 @@ testObject_UpdateService_provider_6 = ( unsafeRange ("f\SI4 \1063170|\995839;T\139513E\NAK(Qp!X<#\ETBA\NULuW\44248cis\f=~C\1732\1027485N\161808S\SOH\988099;\EOT2\fA&\187694@RHN\1011941\137440\NAK42!#qAM1I\tu\120271\b\t\19488Q\ACKDi\127780tX\990666\1103592EI\SI\ESC\bK\GS\NULo\1044109k\DLE\187241\1005849Z\CANI\10594l\1044875\137688jg]\SUB\1100178\1078023 +e'u1\ao\175647e\US1\t\9732\9316\&0-d-UJTP\1092036W~\184365\&7\1098050tly\1087376\46624Ozw\tH\nW\1062958d:E\NAK@\DLE\1086957f#=\97609\&1\61954g!]\1051221\1055847pz\78590OA\1056922,\\xDL\CAN\1073075\SYNeF*s_/\f25 \1088055\EM\1053116\986882Aj5\74938\DLE\12992eDbG\SUB`\66727uW@\6764\DC32q-pq\DC2%j\ESC\EMq\993522\153753v\ESC/\1050068|\DC1,\DELw\ETX-\25497K\1048380\US\n:\98876\1102356\RS\142008\1050738 4\93016MxyOMq9~c\1082301\1028090!\RSQ\30115ql ?>\ETB\149698>(\EOT\t>\20400A\1079649/c O\59065]\ETX>}\NAK\1071442\75027\ETX\1048970%g\ESCWc\153028B\171118\ESCc!Aq\1045328a\7285\180743\155835\96854\167241\175754\46512\DLEas;\13803\1026445Z[Fs\180513*m\SI\n\DC1\t\155458ML\nX\tTD+\SO\1107343]a3\1082869&i\1000299:X\CAN\1001282s|\az-\1098006\NUL\187905\CAN\CAN/\ACK@v\150658\1010455^o\191090$+k\EOT)>\FS") ), - updateServiceAssets = Just [(ImageAsset "" (Just AssetPreview))], + updateServiceAssets = Just [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], updateServiceTags = Just (unsafeRange (fromList [MoviesTag, NewsTag, VideoTag])) } @@ -233,12 +236,12 @@ testObject_UpdateService_provider_9 = updateServiceDescr = Nothing, updateServiceAssets = Just - [ (ImageAsset "\74850\1096630" (Just AssetPreview)), - (ImageAsset "\SO" (Just AssetPreview)), - (ImageAsset "`" (Just AssetPreview)), - (ImageAsset "N\32418$" (Just AssetComplete)), - (ImageAsset "" (Just AssetComplete)), - (ImageAsset "" (Nothing)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)) ], updateServiceTags = Just (unsafeRange (fromList [AudioTag, ShoppingTag])) } @@ -285,9 +288,9 @@ testObject_UpdateService_provider_11 = ), updateServiceAssets = Just - [ (ImageAsset "\ETX\95687" (Just AssetComplete)), - (ImageAsset "\1111812" (Nothing)), - (ImageAsset "" (Just AssetPreview)) + [ (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetComplete)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing)), + (ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview)) ], updateServiceTags = Just (unsafeRange (fromList [QuizTag, TutorialTag])) } @@ -306,7 +309,7 @@ testObject_UpdateService_provider_12 = ( unsafeRange ("\US\FSX;,\DC3\149563=VNF\NAK%;i\EOT\996832$k\ETBc7\SOH\143354|:d\SO\GS\RSN\10748/\"V\1021294o\DC14\1047613\54437\ESCj\SUB,\1095459}i0m\CAN\31240x_ \1049571\175311Q\1022107JiC1p/[1\\A[o\51780\FS\CAN\NUL\STX+\127172\120462w\EM=\121430dH\1004989Il(#\GSvd+\69876d\anEh\1002617\nQD\\:@{\"\ETXZ\1014379i\1053082J`&;t}zQ\DC3.\1020713Co6\NUL^vvsh\51873\\a\1051720R<\SI{\NAK;%f\144785{\"\22777\&2\140005kp\ENQ\t\ETB\1112840o\97260|@.\RSX\1052971\a>\ETXek\DLE\FS>") ), - updateServiceAssets = Just [(ImageAsset "e\987785\&4" (Just AssetPreview))], + updateServiceAssets = Just [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], updateServiceTags = Just (unsafeRange (fromList [IntegrationTag, ProductivityTag])) } @@ -364,7 +367,7 @@ testObject_UpdateService_provider_15 = ( unsafeRange ("x\a\136203\SUB^\ESC<4\n\17873\SO>v\157431|\1020922(\185983{\US\30184A\SYN/\1034793\FS&\24692w5945i\n4\DC1+nk\118834ZSV\1011086R\996947\GS\a\CAN\ESC;D_g7T\61134NN.\1080365,\1035561\SOdPu\SUBF\"e\1071157V\1072899o\1019597\SOH\ETX\RS\1090025J\brXcs<\41273eYl)'\DC3F{wjCio\10430\EOT\DEL\66434=F\EOT\1011500\FSAe\99614\29782j\987688\RS\93035_^7\FSzLW\DEL\v9Oy&\1019281\158269=j:\161191\EOTxXF\v!\SI\DEL{\182824\CAN(q#A\f#Y\GSm\1029668\SYN\33488\1091890Q\21517\DC4N\13674bj\21932H;\55134\26121fz\183843\135947.p\147443X\SI+\22973\29225\14419\b\n\35820\1092258\ACK8\1003849\99533dUVae|'3g\STX\SOH\177742xA\190959T\1088684M\167371\&7\60761:\NUL\100886\DC3\GSs\SIyw\1063851Q_u}\SOH>\1069485\134333?\US\SUB\1106685\6158]5Z\1034719%\57389\183657_\DC4\41432^\28540qa\329\1097112/-\ACK\EOT\45370\1089284~H$\FS\9526\b\SOEVy2obJ\138789FK(\995061H[^\1088420\25954n\160665/\FS\US#\1066635db\1006679\&5?\nM\SO\44147Xs\150820\1112961\f]XR,\GS8{A0.T\ESC4\SIL\SYN\EOT\1028786\GSkX\ESCa=6\"qA7\RS\ETBG\ETXD\DEL\1100961d;\185997\EM\NAK5\DEL\1076613Qj\f'D#\v\1087346gY\110778\CAN\8773\&4P2\ETX_\1048072P+V.F9\SOH\156486-oK&\EOTo*\SYN@\174461&w\1082933\n{\b/\39070<'>\148084GFoF\25642\SOH\t]vwT{+\987769\b(mO\35465\47334xR\1099279\SOHk\120988#\DLEJ\n\1111066/R|^\SYNXj\177224(Dc\RS\64631$jM\1058526\n|_\1023319s\181256\1081025U\1077048'\144694\f\NUL\GS\179974puJ\14349 1PH\986811\147997\DC3p0%!\1096149\&8Q3Hc\DLEb3\1063888\DEL^o~\1054122&u\a1,mgg\1046750\141023'J4\r[6\45643o\FS9\SYN\1020964<\RS\31175\fa\DC2\v\1046951b^2\DC3*\DC2Y\8803&p\ETB\27260#*\DEL\41812\SO~mcH#qFe\1015266\DEL\DC4Aq\DC4(\GS[\CAN%%h3U\1013273U\1099555\131387\1019990\&4\166361Tt\43506d7Z\1059964~\984571") ), - updateServiceAssets = Just [(ImageAsset "\SONG" (Nothing))], + updateServiceAssets = Just [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Nothing))], updateServiceTags = Just (unsafeRange (fromList [FitnessTag, TutorialTag])) } @@ -402,7 +405,7 @@ testObject_UpdateService_provider_17 = ("Y\37457\171247\NUL\1102605\19452;\40109l\1091643\1038961\164211\&3\1060552/\NUL[\STX\ETB\r\1050187\&9\SO9\SUB\NAK?yC&\1087572K\19408X\1008435Z\1043931A\FS\ETB\a\FS\1068870\&2(\FS\1081735Wh\1105128;\30117\SYN\177561\121419F'\ACK,\1008576t\b\148040\178770]Ea.Sr\STX\1021147/\1091479 O&\167108P\1051535\12083 P\fvL\1072069xTw\171454R\CAN") ), updateServiceDescr = Nothing, - updateServiceAssets = Just [(ImageAsset "\1032928j\92521" (Just AssetPreview))], + updateServiceAssets = Just [(ImageAsset (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) (Just AssetPreview))], updateServiceTags = Nothing } @@ -420,7 +423,7 @@ testObject_UpdateService_provider_18 = ( unsafeRange ("\ETX62P\SOH\DC4\1109991=\NUL8}\1103539R\1014278Y\187048\CANz-\50831t\NAK\30991:\1108518\\q5!\CANsz\986662.]\1091331}\EOT\SOHk<\1076580jo\ACK*\1006270<\1068043\v\162015'\\Ky\\d\67224Ea\186085\42476\&7\145875@3.`[\83186%\1013254\1103673\2547^o'\NAK3\DEL\f\32802\&7\155976\US\178005\182126\11804\13566\ETX<2\37455\\\EM7u\1101747\996895\1030597`\aF\DC2\1002903\1065461G\SIUMj??\1082038\163609[q\53362\STX|\STX\f\39680?\60538\US\ETB8\STX\EM\1113089\1024191\DLEZ\n#[ \1010523\RSh(\1031090\&3\142124\&1\bC?2rx7\NULjE\nU\1056190\n)4\EOT*\18936r\NAK\EM\vA\DC42TSw,\SI0\1061258\176021\&6RX\1104923KEU\99028as\DC3/\SYN5`,d\"\60033\DC3\180441y\ACKe&|\SO\USE\991388\NUL\34162\3233\SO;\DLEh,|z0\GSZPK#WSN. -module Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user where +module Test.Wire.API.Golden.Generated.VerificationAction_user where -import Wire.API.User (SndFactorPasswordChallengeAction (..)) +import Wire.API.User (VerificationAction (..)) -testObject_SndFactorPasswordChallengeAction_user_1 :: SndFactorPasswordChallengeAction -testObject_SndFactorPasswordChallengeAction_user_1 = GenerateScimToken +testObject_VerificationAction_user_1 :: VerificationAction +testObject_VerificationAction_user_1 = CreateScimToken -testObject_SndFactorPasswordChallengeAction_user_2 :: SndFactorPasswordChallengeAction -testObject_SndFactorPasswordChallengeAction_user_2 = Login +testObject_VerificationAction_user_2 :: VerificationAction +testObject_VerificationAction_user_2 = Login + +testObject_VerificationAction_user_3 :: VerificationAction +testObject_VerificationAction_user_3 = DeleteTeam diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual.hs index b014d923785..29a08634cad 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual.hs @@ -29,6 +29,7 @@ import Test.Wire.API.Golden.Manual.ConversationsResponse import Test.Wire.API.Golden.Manual.CreateScimToken import Test.Wire.API.Golden.Manual.FeatureConfigEvent import Test.Wire.API.Golden.Manual.GetPaginatedConversationIds +import Test.Wire.API.Golden.Manual.GroupId import Test.Wire.API.Golden.Manual.ListConversations import Test.Wire.API.Golden.Manual.QualifiedUserClientPrekeyMap import Test.Wire.API.Golden.Manual.SearchResultContact @@ -112,7 +113,14 @@ tests = (testObject_CreateScimToken_4, "testObject_CreateScimToken_4.json") ], testGroup "Contact" $ - testObjects [(testObject_Contact_1, "testObject_Contact_1.json"), (testObject_Contact_2, "testObject_Contact_2.json")], + testObjects + [ (testObject_Contact_1, "testObject_Contact_1.json"), + (testObject_Contact_2, "testObject_Contact_2.json") + ], testGroup "SearchResult Contact" $ - testObjects [(testObject_SearchResultContact_1, "testObject_SearchResultContact_1.json")] + testObjects + [(testObject_SearchResultContact_1, "testObject_SearchResultContact_1.json")], + testGroup "GroupId" $ + testObjects + [(testObject_GroupId_1, "testObject_GroupId_1.json")] ] diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/ConversationsResponse.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/ConversationsResponse.hs index 0c20d3d7c15..0e2e2365e01 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/ConversationsResponse.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/ConversationsResponse.hs @@ -60,7 +60,9 @@ conv1 = cnvmName = Just " 0", cnvmTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000002"))), cnvmMessageTimer = Nothing, - cnvmReceiptMode = Just (ReceiptMode {unReceiptMode = -2}) + cnvmReceiptMode = Just (ReceiptMode {unReceiptMode = -2}), + cnvmProtocol = ProtocolProteus, + cnvmGroupId = Nothing }, cnvMembers = ConvMembers @@ -106,7 +108,9 @@ conv2 = cnvmName = Just "", cnvmTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000200000000"))), cnvmMessageTimer = Just (Ms {ms = 1319272593797015}), - cnvmReceiptMode = Just (ReceiptMode {unReceiptMode = 2}) + cnvmReceiptMode = Just (ReceiptMode {unReceiptMode = 2}), + cnvmProtocol = ProtocolMLS, + cnvmGroupId = Just . GroupId $ "test_group" }, cnvMembers = ConvMembers diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/CreateScimToken.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/CreateScimToken.hs index ea00bdf7fa2..bde9b24c1d7 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/CreateScimToken.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/CreateScimToken.hs @@ -17,10 +17,11 @@ module Test.Wire.API.Golden.Manual.CreateScimToken where +import Data.Code import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Range (unsafeRange) import Data.Text.Ascii (AsciiChars (validate)) -import Imports -import Wire.API.User.Activation (ActivationCode (ActivationCode, fromActivationCode)) +import Imports (Maybe (Just, Nothing), fromRight, undefined) import Wire.API.User.Scim (CreateScimToken (..)) testObject_CreateScimToken_1 :: CreateScimToken @@ -28,7 +29,7 @@ testObject_CreateScimToken_1 = CreateScimToken "description" (Just (PlainTextPassword "very-geheim")) - (Just ((ActivationCode {fromActivationCode = fromRight undefined (validate "123456")}))) + (Just (Value {asciiValue = unsafeRange (fromRight undefined (validate "123456"))})) testObject_CreateScimToken_2 :: CreateScimToken testObject_CreateScimToken_2 = @@ -42,7 +43,7 @@ testObject_CreateScimToken_3 = CreateScimToken "description3" Nothing - (Just ((ActivationCode {fromActivationCode = fromRight undefined (validate "654321")}))) + (Just (Value {asciiValue = unsafeRange (fromRight undefined (validate "654321"))})) testObject_CreateScimToken_4 :: CreateScimToken testObject_CreateScimToken_4 = diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/GroupId.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/GroupId.hs new file mode 100644 index 00000000000..800ec178dd6 --- /dev/null +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/GroupId.hs @@ -0,0 +1,23 @@ +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 Wire Swiss GmbH +-- +-- This program is free software: you can redistribute it and/or modify it under +-- the terms of the GNU Affero General Public License as published by the Free +-- Software Foundation, either version 3 of the License, or (at your option) any +-- later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Affero General Public License along +-- with this program. If not, see . + +module Test.Wire.API.Golden.Manual.GroupId where + +import Wire.API.MLS.Group + +testObject_GroupId_1 :: GroupId +testObject_GroupId_1 = GroupId "test_group" diff --git a/libs/wire-api/test/golden/fromJSON/testObject_NewUserPublic_user_1-2.json b/libs/wire-api/test/golden/fromJSON/testObject_NewUserPublic_user_1-2.json index 9b78648af33..1d92088b8d5 100644 --- a/libs/wire-api/test/golden/fromJSON/testObject_NewUserPublic_user_1-2.json +++ b/libs/wire-api/test/golden/fromJSON/testObject_NewUserPublic_user_1-2.json @@ -2,16 +2,16 @@ "accent_id": 39125, "assets": [ { - "key": "", + "key": "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac", "size": "complete", "type": "image" }, { - "key": "(󼊊\u001bpó³¢¼u]'ô…„»", + "key": "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac", "type": "image" }, { - "key": "ô¿f", + "key": "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/fromJSON/testObject_NewUserPublic_user_1-3.json b/libs/wire-api/test/golden/fromJSON/testObject_NewUserPublic_user_1-3.json index 946a8e2c787..c0d2ff9b4a4 100644 --- a/libs/wire-api/test/golden/fromJSON/testObject_NewUserPublic_user_1-3.json +++ b/libs/wire-api/test/golden/fromJSON/testObject_NewUserPublic_user_1-3.json @@ -2,16 +2,16 @@ "accent_id": 39125, "assets": [ { - "key": "", + "key": "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac", "size": "complete", "type": "image" }, { - "key": "(󼊊\u001bpó³¢¼u]'ô…„»", + "key": "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac", "type": "image" }, { - "key": "ô¿f", + "key": "3-1-47de4580-ae51-4650-acbb-d10c028cb0ac", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_AddBotResponse_user_1.json b/libs/wire-api/test/golden/testObject_AddBotResponse_user_1.json index b3d6a462f5f..291eba44029 100644 --- a/libs/wire-api/test/golden/testObject_AddBotResponse_user_1.json +++ b/libs/wire-api/test/golden/testObject_AddBotResponse_user_1.json @@ -2,11 +2,11 @@ "accent_id": -3, "assets": [ { - "key": "7", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "", + "key": "3-5-034efa97-f628-450e-b212-009801b1470b", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeamUser_user_1.json b/libs/wire-api/test/golden/testObject_BindingNewTeamUser_user_1.json index e1710c7144c..dbd4431293e 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeamUser_user_1.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeamUser_user_1.json @@ -1,6 +1,6 @@ { "currency": "XUA", - "icon": "Coq쳋\u000b𬟀7\u0016\\N𠯲⃣h3 癆;X\u0002\u0007\u0007$|D\u0000\u0001h'ï–…3-m7ô‡™´\u000ep囆`ô‹‚´6QF\u0018\u00019ôƒ ®\u0017㶃FP;lmô†¥ðª·ð º½å ªn:\rf󿈅\\:裡\u001d\u001d梂\u0019壬i󲀃\u0003jj}󶪤~󱢤{; ð„­óµ¯Ž\u001aL&%2𩮀;@\\2`gAó°’ƒ0𧨥2𧳲h\u001cuF\u0014ä \u001b\u001d\u000e\u000bYnKy?v🨊H\u000ccLdBy𩫪4IôŽ°ƒi󸔥\u0017c6f\u000b\u0001\u0013ó¸­”ceôˆ£\u000es3L&", + "icon": "default", "icon_key": "\u0006cð¥±L ,\u0002\u0015[\u001a\u0011\u001dxe󴑯c\u001f\u0014<`|熹𣸻Q󻃻󱌙<{\u0000^\u001cT𢛰Jô…­›U\u0004\u0016︉\u0013G󴺾+\u0019ð¬xr\u000bç»\u001byTD@>Ouð‘ jꨶE\u00026eó°ŠŸ\u000e\u001bð¡‚Ÿ34ôƒ¤ªê€¨ó¸¤§8ô‚’¦ð§¹ˆuxWêŸô‡¹½Y\u0006ð¢¥(\u0018\u001c$Dôª²ð¤‹¤è·ƒ\u000f3ôˆ’°#\u0016?\u0003\u00060*W3\u0006ô‰„¿i覟h\u0015-꘡ó¼ª\u0006H?\\TvôŒ˜íºQë•\u0010-@k%{=4\u001a!w&ó¾ ƒD\u0012cuT^\u0014\u001dH\u0008ð¡«¡^]ó°­„jXA󶦥ð §@fV,OA𭋵霕F𥦖Az^g7𫘰),Có¹¯}.ð‘° ó³¡~Vô†½•óº‚º(9^zó·¯…ðœš3}Gjô‡‘«\u000cd>ô‰ª™Yð«‘µp#^ôœ§L`S~ôŒº€\u00123\u0004𣞧æ€ð–©‹ã‘ªas:F\u0003", "name": "\u000ce\u0005ó·€°zm𨦻6+)g;5󱬄Z 41\u0011\n\u0002\u0003%|\u0000M󳎰S=`IUK1ó´¿Š]X\r\u001aa\u0019!𒊧+\u0003epRw\u0006\u0005#ðŸ›ôƒŽ‹ó¼¾Žó¸°²UXð”…]>i&rð¡©ô²©Zô…•6\u0014\u0014óº²ô‚ ¯ó¿…‚\u001b\u0016a4\u0000ô‚¬’󸂌𞋬\tLZ\u0006w$=\u0016u\u0003E1ï› C'\u0005𥃔랛𠶎$𘢤ô¤†9;#ó¿„›ó·º&\u001bóº­¤k/\tu\\pk\u0000\u0002ôˆ¡¶)\u001c/Lni]Q\u0000\u000fZ|=\u0011V]]\u001c5𦌻U6>(ä‘'\u0018ð«·ž%'I1-D\"ôŒˆ¿\nð“«\npkHY#\u0000ó·±”u]ô‡–’𣿖\u0002\u001fj'󲪯'\u0018ó¾› &è©„E鎪=ð ¾’Da\u0002\u000bôŒ¨¿=ôˆ¢­V#󲞟\u001e\u001cN#`uny󴺪ô‹“²53#/|,+ópW꺱i4j" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeamUser_user_2.json b/libs/wire-api/test/golden/testObject_BindingNewTeamUser_user_2.json index b744f820aeb..d2731f22528 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeamUser_user_2.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeamUser_user_2.json @@ -1,5 +1,5 @@ { - "icon": "\u001a4\u0015F", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "-\u0006î£v^\u0001_>p㙳\u0003\u0016\u0004\u0005à­ªô‡¯†]ë€ó °\u000f;v}q릎𮧸\u0007\u000fô´–&~쬌<\u001d󺉸`,󼕲snà©›H𧆂ôŒ¯Šð«‰¶:qNi]ô€´œ'󴊤#\u0007#T𩳫}󱸗\u0012󶊣M_\u001c\u0014󱘬ôŠ¤Ž\u0019,\u000e\u0018^]𓀫9ô§¾-\u0007\u0001ID. FAp\u0004󼓃󵔴(Sô€µªð­€ðŸ¡ \u0010sI\u0003e|Mv-\"që¿zMã Œ$H\u0001𡽺óµ¯D]\u001aô»•\u001b𤺴qW2\u0005ô¦\u001ey󸧓gg󸯗 /ô‡£§ð˜ŠŸä§°~&y\u0008\u0006ôˆ®®ó¿¯…赦\u000e\u001c\u0016\u001et\\a.V\u000eHy8k\u001f$OÊ»Xu/=", "name": "G\u0004\u00147󻞽bCy𔔚&5\"ð—¢µB$\u0002\u0012QJb_㵯ô¬“Y 𦆗󾾭Yóµ—‚g\u000b󱿒xkJUió»ˆ.=-ô¡·2ô¸žU\u001b]\u001aôŠ¥™\u0010}R𦙪\u0011ôš¼ô‹­²+R/ôˆ¥¾ð©®Žp(M\u00055Fw<𣌅E󵢃R\u001044ô‚¸Ÿ\u000e%@FPGó°°—JJ\u000bE\u001dz\u001e_\tb]0t_Ax}\rtô‚Š²h\u0013O\u0006󱽊`󽛆vm-?$!)~𥒒bh\u001e󶿅󵾖0x ôŠ¦¡ô‡·+)A&mRfLôŽ·‰\u0005ô€‹§>K@\u001f󵮯\u0007b\u000bPDWG,ôƒŸ¨/J~)%7?aRr󱩅4*^ó¼­®K*ó³–£\u0019\"\u000eó±šð­ l\n\tE𡔚󽎬\u0015\u0007\n𓆫c?\\\u0005j\"\u001bpe𘂒\u0000=\u0019>J" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_1.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_1.json index 612828bf06d..8171d316919 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_1.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_1.json @@ -1,5 +1,5 @@ { - "icon": "~c\u0004~汸YðŸ‰\rO奿KHôŒ·²:ð¢„𡹌\u0000WfA\u001aj<5_𠶦mx3^Em潜\u001afôŸ²\u0014Wx $ã‚›ôƒ¡¶I-ôƒ®„NhzWDC\"ô„–©\u0001ôˆŒ‘I&i\u000c!;-?X舒\ro󴯋G6\u001dô†¼…iô¨#yH%f@6qb?;ô…¦ó´ƒ±&wQ\u0016\u0019zð— £c𬎿\u000cJ𬦦$U\u0016\u0004ð‘¹0]\u000c\u000f7󻛤𪜚\ru)\u0016\u0014\u001byL𡙤n𣋪mm`\u0011/\u0013ô„¨µ,𤙲𘉂DX纇\u0004}\u0004\u0002ôˆ—ð˜˜vE", + "icon": "default", "icon_key": "ó»šW.\u001eí‹•E\u000bS_6ZzJ{'\u000c&M󻉧󳲿𤴄唂󸯡t;C󺱊'að©Ÿ[(X\u000f:ó·«°\u000f\u001f`ô†¤¢zK[䈙ô†”—a\u000b", "name": "UivH&횊𗾉p\u001fzâ·Œ\r$\u0014j9P\r\"ô…œƒà²¶ó°¸€aF>Eô‡˜—𡼡B\u0019&ô‰¯‹\u0014𪭋+'ô ’R;!\u001d󸔢\u000fvv|\rmbGHz󵚲ð—‘3hð¡ˆ\\U|'\u0003;^&G\u0018\u000cê´42\teqô€—\u000eV1}\u001eaT󷧄aO7<;o𫶖\u000cô˜m)$PC\u001b7;f{\u0002t┽>\u0004X@4|/\tH\u0005/D𣋒\u0019ð©œC𘕰Q\u0005Tô‹®¡?d\u0006ô†ŠŽ#H🈣𡽷*𨡴jo8+𩆂\n\u00005L[r7_.\u0010l1ð«\u001f6[EhD\u001fh\u0007ô‹˜\u0015C𢇵,ð«Ž¢,@?y+;\u001f`/^d𡩦󲆪HeCdé¡´\u0013ô€™¬ð£†—\u00052O-Hijo󶂘ó¿¡ó·”¶X}xô¡¾\u00177𣫺쿢C甽\u0010`k*Gl󿙎\t𓀮󵃮\rb}\u0008\u0000芆h\u0016ynr6d(", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "\u0004𠇱\u0017:ó°š¡HL\u0001^bs\u000bG𦜤{Iô‹¥µ]-J\u001côŽŸ—\u000bs9\u0010ó´”½vI`Në°ŸMZz", "name": "\u0008 \u0001+ô´¶;\t095ê––\n\u00022J󴬋\u0011UzD_ô‹š\u001c" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_11.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_11.json index b9317a5f4b3..a52117b520d 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_11.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_11.json @@ -1,5 +1,5 @@ { - "icon": "r켭嚔𑪕u*33{8Gôˆµ†aO\u001d%\u0000[󵂞U\u0005󰤫iKL\u001dNF𨀔ôŒˆ’`\u0004T$`i5ð–£±\u001bŤ98>", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "ôŽ¸ƒI\u0006.𦱂@y0\u0010ôˆ›n\\#skjó¸¸Y_󽔌&xóµ¹³\u001d\u000fyô©‰B\u00160\u0013VP1ô‰“ªq󺌶ôˆ†™æ¸³RôŒ¨“*+\u001e,MP槄*;\n\u0015ë¡«\tð§‹\nGj.ê…Šôª›lã…Ž\u001c~ô†­Š\u0000.ôˆ§‚&\u0001}\u000fô‡ºš\u0011+f^ZC\u0007'T\u0001\nó¹»ô‹¹§UôŽ “`W\r\\fX\nô‹›†TFôŽ¬”`hð—²[ë“«ERdP5<<óº­;\rô‹£›\u0000Dy漆5N/^ð¡†(\u0013󿉋ôƒ‹¤6e\u000c:\u000fB\u0010F-ô‚¸ä±ôƒ¿µRfb긦\u0007DrB󱌬㖬桲\u0000+2.\u0007\u0007}\u0015psFw\u0017\u0013 ð­š—ð¥‚k~", "name": "ë®…Hôˆ’¨ð “𦡃5\u001e󰳡-\u0015\u001bR\nL戮&bD𢂤\u001aH\u001f󾈖\u000c\t;eôƒ´ ëŸ½\tcô‰£¼eôŒš—\u0010\u0003Iðƒ’\u0017ð «¼\u001a \u000fꬓ~FE\u00186𧰔ë®u\"𡈄󾓋\u001cFYI\t/{\u0005\u001e]jô†¸®\u001f22㸌lꕾ$\u0017\u001f𫼷kL{\u0002*𠄶RMj\u001bôŠœ„W3H󹇯\u001c\u0015^\"5ç•ç¼›*ôŒ•§" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_12.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_12.json index 2764ebff6c9..175e5cdad62 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_12.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_12.json @@ -1,5 +1,5 @@ { - "icon": "ô‚¾¥b\u0004ch1a iuBó·“\u001b\u0011ð­± \u0002V󿧅@1𣛸 iY\"mó½…–\u000e\\O*(\u0013𫣇w7wô»¦\u001f2ECkð˜Œ4\u0003à­Œ%\u0004@\"k󷔜󲢼*󻶙\u0011\u001bYC?𫑘2>ì´˜\u0016o8[\u0007V@9Eô‰„ˆâ’…󸣂!k0b\t\n+6\\Ki{\u0011ó°•Ž\u0001\u0016ó½…{0Hó¸•\u0014\u0005󲕅|\u0018󹵤F鈫󷊥dó¿Ÿ\nðˆŸô£¼Xô¿§ô€‡*\u0008C\u0012𘒓Wzô…š¿é‹¡QWt\u0017Aj>eO}Ae\u0014\u0017\nlôƒ„¦g\"ó³—©,6Kæ» ô™€[󸱽󸆑NåºeB!𮇶C\u0004\u0002X#El\u0017`î¾™e ô‹¯¾\u0006\u0003PBCô‘Žfa𫬟", "name": ";𛄘M\u0004ð¨…P]'ó¾‹ô‹²á€–ôŸºiFnRQV꣦@󲚞lôŒ”»\u0007KZ{ìž´S𮦪\u000cg*\n,`!V\u0002ó²‡e'󽹟\u000e0é‚›p4d\u0002s󹈷uK(c'ì»hjB𣘹\u000et'h^\u0016\u00160ó¶_⼠𨮕thH\u000fô†…‡:󹀞l; n4côŒ¿•D[ó·©´8Y+ôŠ¬·\u0004Eô‚›Š\\0ð©…‘KKTc)P1K󾠫󱸡W\u0003<|愸0|5{Y󰺓M\u0014\u000bK\u0010ô‰¹¾\u0001\u0014ó·ž•.\u0017g󲇥\u0010\"W\u00009&0yYZô‹¼\u000bⵖ" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_13.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_13.json index ec9417ccfb1..7c084d93b7d 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_13.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_13.json @@ -1,5 +1,5 @@ { - "icon": "0^ô‹šž7>ð—…¥ \u0002é¦\u000fF\tAH𧑩:𨹫8ä…—Qó¿’›\u0010-A\u0004𦎬\u001bHOc\u000b󵺂\u00139\u0016ó …•{ \u0000]ó¶’Iô—«,𧯞f\u0012ô‰®¨q\u0001🨣GA\u001dT\u0004S\u0010MmnLyô´•=𠀦2k󶫴\r~ô‚©§1! \u001bJy♜J|zgf1}ILN5󺈩Xi᪽Be.ô‰‰\u001e\u0016ð—Ž‚ôŠ’§\u001e+Y;Z$\u000e5", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "oﲕô‚ˆ\u000f[aoM\u001dô‰“}q躷4^\u0017-*%𤎉8ô„¨‹`ô…˜#pH}\u0013?w`A/ð–¼¹ôŽ©™ó²¼€ ô¦¹\nXꀛ󳡲\u0013u\u001e\u0001(󾒲󵮑6\u0002]t{\u0014\";*\rヌqô„“â¾µïª+w&笭(3#𬈙PY]\u001ef\\?F4\u001a\u001fTôŽ©£Rnfq%ð”¹p𥨈𬠶jðŸ­0P\u0008n\u000e\u001c\t䯈\nN.aGx", "name": "Gì©·ð‘™rLb<ô´¯!\u001e|RDð§ \u0006ð”Žð¨¿ëˆ¢Ag墘 \u000by`\u000b󿌣Kã—ƒeä £,𣘥DQEO\u001e|\u000fô†­“ôƒ¨‹grô²¼\u0000\n*1럩R\u000eð”-Yó½™±nô‰ƒ¤]])ô‰‰»C\u0013𣰗\"M@(K㮂\u001e1è«·\u001c\u001a󺜆T?}\u000e=*𭇂\n𑄉\u000b_\"7ôƒ¹±?Lk𤪸x\u0014bu:𣸰㣱󼻩<ó·¼”6\u000e`ô…£’U죑yp𬰚7%" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_14.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_14.json index 690eaabb6fa..add730db131 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_14.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_14.json @@ -1,4 +1,4 @@ { - "icon": "(r'`ô…²¹Gd\u0003)u\u000cq*4ô†¹›ó»¯¿i\u0019M.Ruô‡œ­\tð¤‹ô‰²±mî‹™;\u000có´ž—ô¿°ð¡ƒ¾m\u0007\tô„›‘wRhv\u0007r6ï³¼\";&4\u0016X\u001d\u0016w5{%P&極98\u0003\u0002yL\t󹫣XMu(ð­·6)\u001aꓯWcé—›#\u0002夺lôƒ³·Vy1_\u001a\\\u0018ô’ ê½™Ek>óµ›·\u0006]$\u0004𬘽ô†°©w\r𨅛@&V8\u0007\u0010\u0011\\󶯔ô”¬\u0015)X\nE)ô…ªŸy*%1\u0015\u0012^4hKfó³…²|EY`^\u0013𪗫颕\"pX\u0008g샴>YR(W\\eS\u001dæ°µ(bn\u0016u󴙶𦒟hꆂ\u001cGôŠ‰«\u0010蜙að‘…±n2)󰛬\"<󻿼YUqô€‰€ó¼±£ð­¦‡\r7ྤ\u000bô€Š‘惱\r󺽸|]\u0017\u000eh9f󾻓\u0013w󴧻Y}å‘£1q\u0015Y:æšq \u0017=*#ð’“Ÿ\u0019î½\\å•¿y9Tfc\u0011삯kô‹¯†\\Oxxn&6NtaZ?k:5G@딎\u0013Hô‹¶½hu4𫩷󳈫\u001fR𧠉󺈅væœåµ¡ð‘§¡ã‰‘\u0006Dó°¡€[bb<ô" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_15.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_15.json index f5bbb69dba3..ea408149f95 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_15.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_15.json @@ -1,5 +1,5 @@ { - "icon": "UZ0󱶈t+𨼻ㄭk|\u0011\u0016\u0019_ch\tôŠƒ¨ãŒµ\n\u0017\u0012,xôŽ…·,RE(,G\u001d\"gA\u0015ncdJð§°+\tôŒ¥FP{v=ë‚«'e\u00029B#1ô†¤\t.ô‰„®ð¢¾â²˜\u001cUK\u0001\u0007;\u0002ô‡™Œð£…7B\u001b\u0000D+󽉔ô‹†„`\u000e>\u001b\u0011,𩳟d\u0001(5\u0004P9iR]\u001eNws󻘌󴿔{[HðŸš,5J!𡔆󼾅\u00061\u0008ZQ7fmQOQó°¹—l!\u0013꯲歔*ꪩ*1\u000cô‹¹8nk|\u0015󵦮~\u000cO𧲭𘧿!:3\u0003n{%ᨇ𬦬if/!ç“] <ô¶°Yô‡–˜\u0008\u0014~\t\u0019\u0001<*\u0015𣀥bx4 {ð—Ÿ‹\u0018Vs;g𘉱ð£„\u0002qkI!QJô…²®J𮑈\u0014ï°¡?_\u0002\tôˆŽiB3YdKA7@>Q󳅳󰾩]ô‹´ð £>D󺬃wD\u001b|\u000f'^ð¡™•ð ªQ#q,\"" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_16.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_16.json index f248cde64c7..c0de11ca757 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_16.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_16.json @@ -1,4 +1,4 @@ { - "icon": "_'C󵌷\u00049\nCð–¡šf\u0007ð…Peî°ðŸŽ»ð­žð ® ð˜ƒ½5ô€¬m<\r}𢈳@ô… #.ô‚–‡\u00026%ô…H%ó»±î¬™n\u0004Uk\u001f/\u001cpS𦓈ôŒ„‘[1np\u0006mDó· U\u001dIôˆ¡¦]I삽𩸦AT\u0003\u001fOl{\u0011\tYt7U +Qô†‚\u0018\u001f[\u0002JV\u0004`\u0018rF\tNð¤ð—šð© ‡\u0016𨪒*A\u0010A]4kuó´» Wô”ºí™ŠI\u0014î’™\u000e𗇟", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "name": "r\u001e\u0017뚌𢯠7X \u0019𤸰J\u0018å°¹ô‡¥/`󳬿mgð µ®2\u000b/7cI)&\u0000\r\u0019=m$\u0013rv\u0012W:उ\u0000!:\u0005x󿅤𘆆iy𒌊\u000f\u0005>jô‹eAó¿–\tbj\u0019k\u0011l\nó±•H~]uêž›ó»«!kjVS{42\u0000E?\u0019h褨B!:\u0010X\u0011T3W\u0007vimhKô‡’«\u0011to*P*\u0011}󰳺ô‡¾¡H\r󼜡B" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_17.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_17.json index 3c5f696fac3..6ed4e3b484f 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_17.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_17.json @@ -1,5 +1,5 @@ { - "icon": "~󻂃C]\u0018S7að™£'󶎸u\u001ee\u00148쨞圈9=𠸕\u001b\u0007'ó²…‹jqUiAð¬Ÿi!\u0002Pl\u0012ôƒµ£\\\u000b;Pq|\rUA߀D4ôŠ¤«;arv\u0007h8/\u001e\u0010𩮉PZô½’𮈨|Oó¹–¾\u000cô©„E/6\t᾿f?,x\":{q::Z橳\u0000\u0018DBd\u0001𢓾a\u0003Zô‰„£0ï“‘~r@F", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "1\r4]N!\"ô…¦©R&\u000f1i\u000ee𪙓ygO\u00064\u000bO\u0006~ð˜š\u0000ð“…€b1að—­œQ\u000c\"ó¿££ô€³ˆ)ôƒ›¢*x猤C_\u0001a[*9۵𡹿N[YôŽ—®r\u0019\n3ô†©™.(|as 6H\u001b󿭵󹡖ôŽ‡\u00016\u0015Bmh/\tôŽ§•ä¬´~ó»´‹ó·¼—qbRp𢳼<ô‹…»è®·N/ó½­¥;\u0014l𠜂\u001d𥗂ac\u0015C\u0016\u0004U\u0000\u0013pW[#uð¨©\u001eôŒ‘«ð¨‹\u0012\u001d\u001ar\u0012D#ô€±€ó´¸¥ð’”­(@i󾻹𩿙ð•—DY\u0019L1\u0013|q{&ôŽ¤ªóº’¬1{'ô„…‘y𬛅E)\u0012󴔸!0kdCOX:E󱄥\\󺜙C\u001eISa65N\u0016[i\u000e\u001e\u0000#:q", "name": "|è·¤P\u001fó¾ƒð§„Y\u0001j\u001eYrrì‰0m\u0005󺽲*'𑦢ô·˜P6\u0016㮄\u000c\u0012𞢵e󻸊\u001e󺭋󴹋𑄺\rA'\u001dA\u0015툒ô„¨®J󸊪'T󽔣R2? \u001c\u001f󼄆$\u0015Gr(ó¶¡{\u0005ï³0mJ\u000fD\u0002-\u0018_I𠔎\u000c𤃑\u001cRô‡®hp𣉒B6W2\u0008\u000c6ô¯£\u0012𬌆\u00081'7-T-#ཱDô†±¹ôˆŒ‘T]v$Gl󾛤󼉲5yg󺔀\nQc.`i㧣忚}\u001c&k4𘔫\u0006>#ç´ó½Šš\u0019ð Ž“[vBOPuôŽ¯£@\u0006\u0000ô‰¦Šð¤†\u000e\u0015𘇃篖\u0004\u0003&󼂜?zó±¢¾i\u000cz\nó»¿\u00173\u0007ôŽ¯›Wô·•E^󾮑󶱉\u0015aR𣛯;ì®·\u0001\u0019\na\nvtð  —\u0003a𢕖 J𠸂uXô†½¹?Wz&<\u0014C\u000cx`ó½‘#\u000f懶邵ꩤ\u001e\u0002#\u0016\u0014-Oj\u0004dó½—Œ'FoHqexoh\u001axôŽ‹»ð­‰\u0008ió³°µyr\u000fôƒ¼¯wô¥¢\n8T󶋓2'óº¼ô‹¦ô’½\u001enxW[æ£ó²œšð—§“ð¥i㔕4󶌓YHZ뺃VZ\u0010^0\u0002Cô‚Œ»ó½˜", "name": "ðª|Z凅?l\u00084Dä¹›K0#OV>ó°°…S3'4\u0006ð’† *m-\u001b4\u001fj\u0003__6ó¿£á¦´M믅\u001b]\u0004Dq\u0010uoæµ¾$\u000bUWp1=/o\u0017Y𪙶9\u0012\nQð«’¥ô€¦)ô‰·4wað—‹leQ*󴑞󼡨>@,󿖻𮦮RF4QcNY96𩉓ô€®ˆGô…†”&J\\TzHUiG.C\u001a&\u001cx춈𨿱3ô³ŠAô”¸B)燖穲r󵌈\u0005&VCPa{\u0001\u0019Wꧬ𗰙\u0010/ô‡”³\u000fc:b\u0001ð ’ª)襈ôŒ«’鉲@5󰊈I02g%%1bJl} :ó¹™³\u0016署𦲖𢻉", "name": ";ð­š@m\u0008Zó¹”›1}\r9\u001e)\u0000𧷛ôƒ«£ð—–»\u0001,q\u0001`ô€£žP󱉭ô…Œ\n\u0004Imô„ ±\u0014󷜳ô†¢ƒHB\u001d`󹫇𢴱U𧩣G\u0013m+Lw\u0010󹾧\"0ð‰?-T\u001b\u0003j\u0013|Lq\u0003.𘋘hgq\u001by𘒇5\u0007y\u0006ôš…w^(\u0017+𪻾3\u000cð›°¿xó¶…•i*E\u0013𫹺rTM\u0018ôŠ¶¥$ð§ð®£ž\u0004\\pô‡š1\u0007ð €—ô€ ºzv󼞙\u0019rj\u0007ó¾­’u\u0015n\u000e𨆖6\u001bs\u001dm9Y\u001c%\u0010?6󽯧\u0002\u000côƒ©¾)RST\r\u0010ôŽ‘ \u0011ô‡“¹h\nTS\u0012\u001fô‹»‡\u0002n\u001b𤦦r2ð’‚·ó°•«hrr-$*jô‡ºè§¹]@óµ…㪱J\u0008\"8Nð¬¼e,\"br\u0001\u0000𦱘+멦c^ó´¾<`ô›‡ð¤™„\u0002ó°·™\u0003𧥛D\u0005çš”nqpô‹³ó±“c\u001etô‡¿§G" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_3.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_3.json index 8fa459aa876..8e455519f46 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_3.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_3.json @@ -1,5 +1,5 @@ { - "icon": ":b6ð˜—Lô ¹K<ôŽ¦¯ð—‹µC^\u000eY8LuXSð¦±F\u0001\u0017ìó¹²—\u001f󾥞J]\u0017A2R$P6\\\u0005𩆊-J\u0000t\u000f󿫯*󻣠rô‡ˆM\u0015PL)𗈟\u001aJ𑱚Sôœ‘~'kRYj𑘞{P{V\u0001!󰱿Y~\u000bV\t𤰂{5%i[$7X:ZV\u0000y\u0007삉\u0016\u0005\u000c;!k?O󻚧ôˆ’‰K:}ô‹´³ðª¤·#>\\|(n\u0002𨟼\u0003D𤨬ì®{|𥃇󱄮+á­®J", + "icon": "3-5-3d4b563b-016c-49da-bad2-876ad0a5ecd2", "icon_key": "\u001c\u001ePó±–—Gt\u0016-ë ¬nJ󶲘g^\n\rð«™¿\u001dRó¶¦ô‡œð’ŽŒt\n)1/% hL\u0012Ad\u0001Xq6\u0011)\u0000\u000c6\u000cV\u0014rô‹¶¨\u0011nôŽ–Ÿ,@𩳑ðƒ”\n\u001a%N𫊸\u0006è‘€Xv)\u0016z?\u0014\u0019Y𧤂2ð—˜°um8}죜\u0012yW\u0000HQ\u0005D[Fe\nk󳻂\u0019懷Yk@##u}jð©ºð¥›¾\u0002q\u001bir7) 汬%󸄨~󲪳8ô‰ˆ jeôŒŸŒ0*Gi3ðŸ½je\u0018Qr>󼕣k1爛c󻶢L󷴬𖺉t\u0004W󳿃\u001ao\u000cgh\u0006𪀙C2霩c\u001a)uW\r\u000cB󾧾Sf\u001a\u0001*5l隺\u000fæ–‡\u0019B(\u0005ð ©¾/)!{󵬬9\u0002Aã»fx&𫽹T&𭪕\u0014쯾[\r\u000b\nô…¢‰j2𨤤/ô‰‘°\u0005Qo\u000cjð µ ðŸ¤\nb6\u00183\u001e9\u0019ó´Š–ub\u00173CY\u001dsIz" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_4.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_4.json index 4ed974f0ca4..8cfe6417c05 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_4.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_4.json @@ -1,4 +1,4 @@ { - "icon": "tmIVó³»µ3ôŒ¹™ó¿½‘HKE{ô†­¢î¼R9\u0004\u0017E2ð­L󶢉|a\u0008|CêŠô¢£}\u001d󻱜\u0018O9$)\u0000\u0002𥙒肷\u0000ì‘°K󿉉ô‚‡ d懛\u001d\u000bqóº–§B\u0003\u0007T\u000ePtY\u0015󿫵潌z#k`Pô‚€–\u0002_7LXC]`#-`\u0006ࢺ\u0012pw59\n\u001a\u0010jêƒð­ˆ‰F\u0017ô…ª½\t;\u0012r*圮\u0019S𬤻E8e\u001cxôŠžŽA\u001a|j\u0007d\u00100RDI\rQ\u0015`ô…°œ\u0019)j{ôƒš‚𢸬ôŒ¥©f@\t\u0011\u000c\"\u000có¾µ­ôŒ›‹ô¼•á¢žJ`.ð›“*\u0008F%4󶟬\u0019-3g𘟴ô™´ô€‰\u0002\n\u0010\u0013]sð««Ÿ_\u0007󾒾𧯔\u0012\u000bȃ\u0010ã³¼v|\u0008ô€»§%𨙤g~0\u0004Wp\t 𪒺C\u001c𤥂x\u0005\u0006\u0012-DIYa󵌭:ð®”¿'{ôˆšð£ƒ¢ó¾•œ6Ḋ󴋄=xfo\u0006U'\u0014ôŠ…t\n\u0012ô‹›6ð‹¯/ôš³tR\u0016#ôŽ½‰", + "icon": "3-5-18fef3b3-323c-4c27-bb62-6026d62f77c3", "name": "\u0003óµ³®k;" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_5.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_5.json index ed2cc6465cc..d7f0bc88c30 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_5.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_5.json @@ -1,5 +1,5 @@ { - "icon": "![\u000181潅󰋙䘎𤗈𧗎\u001bô€·˜\u0007\u0018\u0004}rô…µ\u001bt𫞣ðªôŽŸ©ç¹¤ð¥ž“Mó²·¤1\u0018$;\r\u0002\u000e\u0003P\u00193)R=E?9\u0010$>*\\`鞀\u0013\u0007]𣔼󹓭{FKA\u0008ô„¸ð«¨Ÿ\u0013\u001f=UCð«·µKQbô¡ºó·¼©\u001cX7@󳌔C~-[Db $cx\u0003Czvq ,g\rD󻿪", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "s𡜶:\u001fdmã’›\u0015I⸈Yô…ŒŒð¬œˆô‹ª >qó±™’\nQ\u001eô”¾\u001d#w𤇠𩻗ôƒ¿¿ð¡–­B\u0014\u001aLv\"S>ð¤…!]sB+6\u0011oc\u00177蛑lR𗙺\u0019r%Eô‡‹¯B𘆔Aô„¡¥N\u0017?{ô„ˆ¤/|cU𢟋]ð–« ô‡Œ\u0010𣾄ô†£¶+󲃎\t$F𗧊he4𨰴|k/!5Z~ð””®\u0017󸛵\u0001\u0005ô‚ƒ3E!{^茖4fhó»—ˆNôš™v\u000c\u001dó³ªmde!5󺻟y&ôƒ”‹xo,\u0002rkô…¨¸\u0005\u0001JoS󰹇X䧱󲸿a󱽇\u001e󿘄\u0019\u00013j༽Z4\u0014ô„¸£l컬n\u001b@ve#\u0016\u001d𬴣P4ô‡€²\u001bð©££:𦠊z1*\u001fs\u000bd`ô‚¬¥/餄𨜲", "name": "\u000fBð‘‹3𦩜\\#ôƒ…»I\u001fK󹶘h\u0010\u0013\u001e𠻊*󳵆L\u0002w1p\"4\u0004𮉃#u𣖞\u0016\u001c鄂ꊙ$Ÿwu𪞴넣ó¶´\u001a\u0007)\u0012?T 솆8馿.\t\u00151\u000c\u0004Y🙉%މﭸ㮲 &Z4ôˆ¼Œ\u0000@\u0001\u0019𥯩ô‡ŸôŒ‘£xtj𬦽`\u0001î¡¡r\rð–¾’\u00040\u0019\u0000Lyc D\u001côˆº“ô£¾)\u001a-\u000eôˆ¼Œ\u000bló°”ô¿¡\u0018ï¤ð­¨¼f\u000frb/[F\u0000ôŒ£†<1󺼰P\u001dxl\"!11E\u001b0\u001b\u000c$uôŠ¼½N\u001dV^ó¸—¡q𩪫\n`ó¿®ð§¶´:iLXn\u0018󱜞朻O}8!Y\u0015,^X咽hEa\u0010^\u0005\u0008]`ô­´<\u001dZG󵉂\u0001𮞘廑*8p\u001cF@OLpnXTmW𗤩fð¨Žô†®æ•¢Ze1 \u0016Emæ±µf\u0006󱀇", "name": "v𭺬hEWefuuóµ³”jPx𦦹k#\u0001ó°¹¥\u0002\u0003^\u001b\n\u0018â‚…p1D|S1ô„€Ÿô„šç†—\u0016`\t0gó¼£¥,t\u001cw\u000cDT\u001e#H\u0001𣜘\u001f{ôŠž«óº™²ó°”¬lW\u0007,uil\u000fN`5e:\u0016 Y!\u0016󺑛tbôˆ¼" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_7.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_7.json index a03c29a6247..65f8d616ff5 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_7.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_7.json @@ -1,5 +1,5 @@ { - "icon": "_5勞\u0007QôŽ²«W⃛9ꮦ*ó´½³", + "icon": "3-1-b199431c-e2ee-48c6-8f1b-56726626b493", "icon_key": "D\u001e𩉨\u0001󼓤🚱Ll\u001d\tWô‚‚¹o\u0018멤b\u0003|\u001f*=ó¶¶ô„–˜ó±“§6󴆄", "name": "ð£¢ó¾§Œizô‚’³FTã©´;ôŽ¦‘}𮇵ôµ¿9\u000e󲆑7>hAC\u0000H2Oð«‘«mð­´¿2R(?W,=,󱸅M󲓈\u0007M椔\u001a맰q\u000elj\u0004j^.s~\rY%5lM,æ¼=\u0006󸑃𮆫>{\u0018\u0010㸆f=X9\u00169쟉𦺻TI4ä’¿\u000b\u00156󷲘/\u0010\u0015\u0006å°ŒH<\u0005󻙇e\u0005z󸚸:៹\"rS\u0007𨻬\u001c\u0003ô‚§™ó»¹ªë½´\u0014\u0014Q\"ô„ƒ°1:ô‹½”\u001fT.;󾣧䟌}" } diff --git a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_8.json b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_8.json index 79c17c026bb..3a3f84deee3 100644 --- a/libs/wire-api/test/golden/testObject_BindingNewTeam_team_8.json +++ b/libs/wire-api/test/golden/testObject_BindingNewTeam_team_8.json @@ -1,5 +1,5 @@ { - "icon": "✅IT\u001eô‘¦ë¾–%mE󰤊ô€©ˆM\u0007:[fgw\u001b?yeð‘‡ô‹±¯K\u0017lôœ›ó·”’󽎻Qg?𤼃{\u000c!uXA}H)v$𧋓ôŠ·ªmGkC9ôˆ¸¥\u001dôŠ²Šó¼«°1DH𨩌sê•‚7[.𭹆v+ôŠ¥†O^SGjvô…‡®Y󴺥n𥬜Z𓈋\u000e{ô‚º«Q\u0003\u001e1I\u001c5\u0006󼻌ôˆ¥†ó»³º_ô½©?gDႽ\u000cg㪠:g\u001c.J<\u0012\u0017󸋤}/ð—¹µOBLaô€³˜\u0007{ó¶·±-ôƒ¾›\u000b\u000f\u0010ð£¥V \\Ns0tôˆƒ™frR!Qe!$𢌢󶀅\u0016$ô‡‹®!SZô€€ð­ƒ»a\u0010B3", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "v𑈬땻h\u0001_󲋫\u0013\u0006i󴋤\u0011\u0003W𑱑譟\u0012嫢󺥖\u0004\u000c%_ôƒ¹©\u001d\u0016\u0017 N\u0000F󵞛\u0005LUua3ô‰»Mâ†\"ð—ŠŸ\u0001\u001e\n-='\u0011B#\u001cð¡š±>\u0013ð “´\u000f\u001dô‰©ªG7v6w Zቆô€¦®ð¬¥¤ð©¬µ\u001bP>𠀧ô€«·ô†·¹\u000b}?á“„Jg\u0001\u001a^plôŒ½§2.\u000eV\u0013å£ï¯½\u0005Bó¿»ô†·½ð¢ƒ¤<\u000c2䬴Tz@6\u0013𑢫x?𤪑週\u0008\u0010\u0018pôˆƒ°\u0016\u0003NôŒ €C\u000f\u001a\u0011l]R\u0000vL󺵶Nz\u000c-bf}f>\u0002H\u0019𡔤+Zo󴈣6𢓖󽱓é‡ó¸’|`dN 擦󱈠i_ô®±pó¼‘Jæ–ngp@#ô¼ƒAó±–±7l{;ð¬›g4EXë„Œ\u001bô„³†ð¤º´#zð«‚“\u0016y\u0004\tî¹€G\\謖å´#s󵘆Adô‹“”Obh󶑡\u0012ô‰‘³î€‰)3R\u001a\u001c𗳩aw]ô†ž·ó½°¤áƒ¤ð‚¦kC梿\u0001ó´¶óµ¢d8ë‘¥\u0011\u0016ï°ºf9", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "X󸸽;\u0005W\u0006Lk󳌎𣔖\u0017\n]î’¹[~â ¨&Uäºv`I\u0017\u001fl󰉫\tôŠ‹¾?ä‹KM3cô„¨½ó»§³= \u0017t5vKOg\u0015/NC2~i'ôƒ´Ojb\u0008\u0003ï”–ôŠ‡³\u0011\u0001\u0000FWcó·­•sU>P\u0001~\u0019wUHU\u000e#훞ôˆ…¯!Nwnóµ ¡e\u0001\u001a\u000c\u0003\u0017Tl𛀥BYU;aó·‹ K7?,m𥪤Xpa뺹𡰽\u0019 ,M!~^g6}(踑\u001eᾋgX}𧓻)c\n\u00012E", "name": "\u000eLN\u001dr𣎸ô…œ—k𬙅#ð¥™lTD[Jh\u0001ó»•‹è• 6󼧒ôƒŸ \u0015}\u0007db𩵜-\\-1\u00142ó¿ˆ\u0012ð“®1/脼b:\u0005󽩦;Mw\u001c𬸺ô·‹ITuyô€š˜`SP\u0001\u000e\u001d\u0015\u0007\r7Mô…„Žôƒ³–䢷\n\u00163V\u0003R\n1$e.ô‹©…B~yd_zó¿´‰\rVôŠœ—\u001e\u0016𨒺l\u0013ë¡ uô‚²u\"\u0007Tc|sEwó¶·¶wTC|FቿB\t\u0014&\u0008UEN(+M\u000eF;ôŒŸ¢ð ¶­\u001920\nrPW󸓢$ôƒ½©" } diff --git a/libs/wire-api/test/golden/testObject_Client_user_1.json b/libs/wire-api/test/golden/testObject_Client_user_1.json index 3214afaac4e..9fc8b644e4a 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_1.json +++ b/libs/wire-api/test/golden/testObject_Client_user_1.json @@ -5,6 +5,7 @@ "class": "desktop", "id": "2", "label": "%*", + "mls_public_keys": {}, "model": "󳇚;ô‡»«", "time": "1864-05-06T19:39:12.770Z", "type": "permanent" diff --git a/libs/wire-api/test/golden/testObject_Client_user_10.json b/libs/wire-api/test/golden/testObject_Client_user_10.json index 98ace886b8e..d983e256e8b 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_10.json +++ b/libs/wire-api/test/golden/testObject_Client_user_10.json @@ -8,6 +8,9 @@ "lat": -2.6734377548386075, "lon": -1.40544074714727 }, + "mls_public_keys": { + "ed25519": "Wm1GclpTQndkV0pzYVdNZ2EyVjU=" + }, "model": "\u0018", "time": "1864-05-10T18:42:04.137Z", "type": "permanent" diff --git a/libs/wire-api/test/golden/testObject_Client_user_11.json b/libs/wire-api/test/golden/testObject_Client_user_11.json index d6148975c1d..ec8e964d987 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_11.json +++ b/libs/wire-api/test/golden/testObject_Client_user_11.json @@ -10,6 +10,7 @@ "lat": 0.44311730892815937, "lon": 0.6936233843789369 }, + "mls_public_keys": {}, "model": "ML", "time": "1864-05-08T11:57:08.087Z", "type": "temporary" diff --git a/libs/wire-api/test/golden/testObject_Client_user_12.json b/libs/wire-api/test/golden/testObject_Client_user_12.json index 0609931e63f..94ebe936c2d 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_12.json +++ b/libs/wire-api/test/golden/testObject_Client_user_12.json @@ -9,6 +9,7 @@ "lat": -2.502416826395783, "lon": 1.4712334862249388 }, + "mls_public_keys": {}, "model": "", "time": "1864-05-08T18:44:00.378Z", "type": "permanent" diff --git a/libs/wire-api/test/golden/testObject_Client_user_13.json b/libs/wire-api/test/golden/testObject_Client_user_13.json index 2eab01d9785..98b13b92f38 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_13.json +++ b/libs/wire-api/test/golden/testObject_Client_user_13.json @@ -10,6 +10,7 @@ "lat": -2.3798205243177692, "lon": -2.619240132398651 }, + "mls_public_keys": {}, "model": "\u0017ð²¤", "time": "1864-05-07T01:09:04.597Z", "type": "permanent" diff --git a/libs/wire-api/test/golden/testObject_Client_user_14.json b/libs/wire-api/test/golden/testObject_Client_user_14.json index 4eb964907db..8fc1c51edc6 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_14.json +++ b/libs/wire-api/test/golden/testObject_Client_user_14.json @@ -9,6 +9,7 @@ "lat": 2.459582010332432, "lon": -1.2286910026214775 }, + "mls_public_keys": {}, "model": "ô€¸\ró ¨", "time": "1864-05-12T11:00:10.449Z", "type": "temporary" diff --git a/libs/wire-api/test/golden/testObject_Client_user_15.json b/libs/wire-api/test/golden/testObject_Client_user_15.json index 9dae337b828..626f76201cd 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_15.json +++ b/libs/wire-api/test/golden/testObject_Client_user_15.json @@ -5,6 +5,7 @@ "cookie": "ôŒ¨·N", "id": "3", "label": "\u0004G", + "mls_public_keys": {}, "model": "zAI", "time": "1864-05-08T11:28:27.778Z", "type": "temporary" diff --git a/libs/wire-api/test/golden/testObject_Client_user_16.json b/libs/wire-api/test/golden/testObject_Client_user_16.json index b850e39e31b..7216da58868 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_16.json +++ b/libs/wire-api/test/golden/testObject_Client_user_16.json @@ -6,6 +6,7 @@ "cookie": "U", "id": "2", "label": "=E", + "mls_public_keys": {}, "model": "", "time": "1864-05-12T11:31:10.072Z", "type": "temporary" diff --git a/libs/wire-api/test/golden/testObject_Client_user_17.json b/libs/wire-api/test/golden/testObject_Client_user_17.json index 19dfb90c216..9f798211472 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_17.json +++ b/libs/wire-api/test/golden/testObject_Client_user_17.json @@ -9,6 +9,7 @@ "lat": -1.6915872714820337, "lon": 2.1128949838723656 }, + "mls_public_keys": {}, "model": "", "time": "1864-05-12T02:25:34.770Z", "type": "temporary" diff --git a/libs/wire-api/test/golden/testObject_Client_user_18.json b/libs/wire-api/test/golden/testObject_Client_user_18.json index d7342824d6f..ce64abe9656 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_18.json +++ b/libs/wire-api/test/golden/testObject_Client_user_18.json @@ -10,6 +10,7 @@ "lat": -1.2949675488134762, "lon": 0.43717421775412324 }, + "mls_public_keys": {}, "model": "ô…©¹", "time": "1864-05-07T17:21:05.930Z", "type": "temporary" diff --git a/libs/wire-api/test/golden/testObject_Client_user_19.json b/libs/wire-api/test/golden/testObject_Client_user_19.json index 628b6bee415..36fc4d8a692 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_19.json +++ b/libs/wire-api/test/golden/testObject_Client_user_19.json @@ -9,6 +9,7 @@ "lat": -1.4630309786758076, "lon": -0.5295690632216867 }, + "mls_public_keys": {}, "model": "", "time": "1864-05-12T07:49:27.999Z", "type": "permanent" diff --git a/libs/wire-api/test/golden/testObject_Client_user_2.json b/libs/wire-api/test/golden/testObject_Client_user_2.json index 1905ca5e3b0..ad8702b385c 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_2.json +++ b/libs/wire-api/test/golden/testObject_Client_user_2.json @@ -8,6 +8,7 @@ "lat": 0.6919026326441752, "lon": 1.18215529547942 }, + "mls_public_keys": {}, "time": "1864-05-07T08:48:22.537Z", "type": "legalhold" } diff --git a/libs/wire-api/test/golden/testObject_Client_user_20.json b/libs/wire-api/test/golden/testObject_Client_user_20.json index d7b5d85581b..aacfed39787 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_20.json +++ b/libs/wire-api/test/golden/testObject_Client_user_20.json @@ -12,6 +12,7 @@ "lat": 2.8672347564452996, "lon": -0.9990390825956594 }, + "mls_public_keys": {}, "time": "1864-05-06T18:43:52.483Z", "type": "legalhold" } diff --git a/libs/wire-api/test/golden/testObject_Client_user_3.json b/libs/wire-api/test/golden/testObject_Client_user_3.json index ae3b649baf7..119f01ce4a3 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_3.json +++ b/libs/wire-api/test/golden/testObject_Client_user_3.json @@ -10,6 +10,7 @@ "lat": -0.31865405026910076, "lon": 6.859482454480745e-2 }, + "mls_public_keys": {}, "time": "1864-05-07T00:38:22.384Z", "type": "temporary" } diff --git a/libs/wire-api/test/golden/testObject_Client_user_4.json b/libs/wire-api/test/golden/testObject_Client_user_4.json index 2f4390a6520..b50ccb3101a 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_4.json +++ b/libs/wire-api/test/golden/testObject_Client_user_4.json @@ -9,6 +9,7 @@ "lat": 0.43019316470477537, "lon": -2.1994844230432533 }, + "mls_public_keys": {}, "model": "", "time": "1864-05-06T09:13:45.902Z", "type": "permanent" diff --git a/libs/wire-api/test/golden/testObject_Client_user_5.json b/libs/wire-api/test/golden/testObject_Client_user_5.json index 4b59612c627..744b8e430a7 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_5.json +++ b/libs/wire-api/test/golden/testObject_Client_user_5.json @@ -9,6 +9,7 @@ "lat": -1.505966289957799, "lon": -2.516893825541776 }, + "mls_public_keys": {}, "model": "⌷o", "time": "1864-05-07T09:07:14.559Z", "type": "temporary" diff --git a/libs/wire-api/test/golden/testObject_Client_user_6.json b/libs/wire-api/test/golden/testObject_Client_user_6.json index 5d181c05002..ad17cc8b158 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_6.json +++ b/libs/wire-api/test/golden/testObject_Client_user_6.json @@ -9,6 +9,7 @@ "lat": 0.3764380360505919, "lon": 1.3619562593325738 }, + "mls_public_keys": {}, "model": "", "time": "1864-05-08T22:37:53.030Z", "type": "permanent" diff --git a/libs/wire-api/test/golden/testObject_Client_user_7.json b/libs/wire-api/test/golden/testObject_Client_user_7.json index d398ce42bd9..41253b1fb0a 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_7.json +++ b/libs/wire-api/test/golden/testObject_Client_user_7.json @@ -5,6 +5,7 @@ "class": "phone", "id": "4", "label": "", + "mls_public_keys": {}, "model": "", "time": "1864-05-07T04:35:34.201Z", "type": "permanent" diff --git a/libs/wire-api/test/golden/testObject_Client_user_8.json b/libs/wire-api/test/golden/testObject_Client_user_8.json index 0d86d8ff8fd..cd0778819f1 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_8.json +++ b/libs/wire-api/test/golden/testObject_Client_user_8.json @@ -10,6 +10,7 @@ "lat": 0.8626148594727595, "lon": -1.971023301844283 }, + "mls_public_keys": {}, "model": "ô½‰", "time": "1864-05-11T06:32:01.921Z", "type": "legalhold" diff --git a/libs/wire-api/test/golden/testObject_Client_user_9.json b/libs/wire-api/test/golden/testObject_Client_user_9.json index be632d81323..11a583ed3d7 100644 --- a/libs/wire-api/test/golden/testObject_Client_user_9.json +++ b/libs/wire-api/test/golden/testObject_Client_user_9.json @@ -10,6 +10,7 @@ "lat": -0.3086524641730466, "lon": 1.72690152811777 }, + "mls_public_keys": {}, "model": "㌀m", "time": "1864-05-08T03:54:56.526Z", "type": "legalhold" diff --git a/libs/wire-api/test/golden/testObject_ConversationList_20Conversation_user_1.json b/libs/wire-api/test/golden/testObject_ConversationList_20Conversation_user_1.json index 8e0218b16ea..2f211606717 100644 --- a/libs/wire-api/test/golden/testObject_ConversationList_20Conversation_user_1.json +++ b/libs/wire-api/test/golden/testObject_ConversationList_20Conversation_user_1.json @@ -31,6 +31,7 @@ }, "message_timer": 4760386328981119, "name": "", + "protocol": "proteus", "qualified_id": { "domain": "golden.example.com", "id": "00000001-0000-0000-0000-000000000000" diff --git a/libs/wire-api/test/golden/testObject_Conversation_user_1.json b/libs/wire-api/test/golden/testObject_Conversation_user_1.json index a1c782e286a..90e837c0700 100644 --- a/libs/wire-api/test/golden/testObject_Conversation_user_1.json +++ b/libs/wire-api/test/golden/testObject_Conversation_user_1.json @@ -29,6 +29,7 @@ }, "message_timer": null, "name": " 0", + "protocol": "proteus", "qualified_id": { "domain": "golden.example.com", "id": "00000001-0000-0000-0000-000000000000" diff --git a/libs/wire-api/test/golden/testObject_Conversation_user_2.json b/libs/wire-api/test/golden/testObject_Conversation_user_2.json index 2b52df51b68..ffc1f3da5cd 100644 --- a/libs/wire-api/test/golden/testObject_Conversation_user_2.json +++ b/libs/wire-api/test/golden/testObject_Conversation_user_2.json @@ -60,6 +60,7 @@ }, "message_timer": 1319272593797015, "name": "", + "protocol": "proteus", "qualified_id": { "domain": "golden.example.com", "id": "00000000-0000-0000-0000-000000000002" diff --git a/libs/wire-api/test/golden/testObject_ConversationsResponse_1.json b/libs/wire-api/test/golden/testObject_ConversationsResponse_1.json index 2a8a47b4c72..ff9bf4ecd26 100644 --- a/libs/wire-api/test/golden/testObject_ConversationsResponse_1.json +++ b/libs/wire-api/test/golden/testObject_ConversationsResponse_1.json @@ -41,6 +41,7 @@ }, "message_timer": null, "name": " 0", + "protocol": "proteus", "qualified_id": { "domain": "golden.example.com", "id": "00000001-0000-0000-0000-000000000000" @@ -71,6 +72,7 @@ "service" ], "creator": "00000000-0000-0000-0000-000200000001", + "group_id": "dGVzdF9ncm91cA==", "id": "00000000-0000-0000-0000-000000000002", "last_event": "0.0", "last_event_time": "1970-01-01T00:00:00.000Z", @@ -97,6 +99,7 @@ }, "message_timer": 1319272593797015, "name": "", + "protocol": "mls", "qualified_id": { "domain": "golden.example.com", "id": "00000000-0000-0000-0000-000000000002" diff --git a/libs/wire-api/test/golden/testObject_Event_team_1.json b/libs/wire-api/test/golden/testObject_Event_team_1.json index 8cbbc5ac0ef..05172b99c6a 100644 --- a/libs/wire-api/test/golden/testObject_Event_team_1.json +++ b/libs/wire-api/test/golden/testObject_Event_team_1.json @@ -2,7 +2,7 @@ "data": { "binding": true, "creator": "00000003-0000-0001-0000-000300000002", - "icon": "#𖺗匞(󳡭", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "id": "00000003-0000-0004-0000-000000000001", "name": "\u0004Xó³’Œh" }, diff --git a/libs/wire-api/test/golden/testObject_Event_team_13.json b/libs/wire-api/test/golden/testObject_Event_team_13.json index 587d5713db3..5c1522c9c1d 100644 --- a/libs/wire-api/test/golden/testObject_Event_team_13.json +++ b/libs/wire-api/test/golden/testObject_Event_team_13.json @@ -2,7 +2,7 @@ "data": { "binding": false, "creator": "00000000-0000-0002-0000-000400000000", - "icon": "\u0008ô†¿±", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": ",7\u0007S", "id": "00000002-0000-0003-0000-000200000001", "name": "\u0008h0óº´´" diff --git a/libs/wire-api/test/golden/testObject_Event_user_8.json b/libs/wire-api/test/golden/testObject_Event_user_8.json index 6807fd809ad..8906b271471 100644 --- a/libs/wire-api/test/golden/testObject_Event_user_8.json +++ b/libs/wire-api/test/golden/testObject_Event_user_8.json @@ -56,6 +56,7 @@ }, "message_timer": 283898987885780, "name": "\u0007\u000e\r", + "protocol": "proteus", "qualified_id": { "domain": "golden.example.com", "id": "00000000-0000-0000-0000-000100000001" diff --git a/libs/wire-api/test/golden/testObject_GroupId_1.json b/libs/wire-api/test/golden/testObject_GroupId_1.json new file mode 100644 index 00000000000..adbad4db888 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_GroupId_1.json @@ -0,0 +1 @@ +"dGVzdF9ncm91cA==" diff --git a/libs/wire-api/test/golden/testObject_Login_user_12.json b/libs/wire-api/test/golden/testObject_Login_user_12.json index eaf85348770..e3414dd15b4 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_12.json +++ b/libs/wire-api/test/golden/testObject_Login_user_12.json @@ -2,5 +2,5 @@ "label": "\u000f🜓-ðž¡Œ:ð¡é®¸\u0006\u000e", "password": "nô‹œ©Qð©—€\u001bóµ…€&Q/\rdê š\u001f\u0004w2C\u0006ô¹¬ð«”\u0004\u0004v󶥜\u0008f,b\u0002󷜰'ðª¹C]G듡󸓯𮤾4\u0000Y.𪘲\u000e3sI菌Fôˆ²¾5剑rG/:\"󷣦XëŸ6\u001c:\u0018\u0007eYwWTôˆ¦šð¡›‘Msbm\u0015@󰗜󷜉\u0004^\u001c𣹘\u0015@\u0005>\u000c\u001eUc\u0004V9&cල\u0007ó°±´a'PPGð˜¡ð«¶¶>[ൽ2ï·„Xc𠃪[0ó´²–\u0008𘕄B\u0011[󻑵\u001dä°»\u001f\u0019s-u\u0017sô„¡½óµ—ð§¿nô…³€?ó¿’‹ck\u00148XC𪣑\u001eI2ମ\u0002\u0010M\u001b\n?<\\\u0013E𑨛\u001d\n$cySð¡†!,\u000b9\u0017/\u0011ï´?P\u0017ꌞ\u0012ó´±~ô‚Ÿ‰W-W4K8.\u00127\u0019Lô‡Œ¡h\u000f}t+H\u001a\u001bXð›‹s\u0004t𫘧taa\u001d\u000c𥌭(v󺈨M\u001bvg3P1󼊃]gà½4T\u0015$é•„);\\8ôŽ²­\nK\u0015}D'^fJ'𢽥e𪟤骭!\u0019.\u0012{\\CEp󿎈\u0017k_ôˆ¨€äŸð¨„ªôƒ¨¬]MG$ð­´‚[Eô ¾\u0008ô†…{bì—š\u001b^b@W\u0015$\u001c<ó¹¾—&𦅘R\u0006J\u000fôŠ·´ôŒ³±ê‡žn󵸞8]ð¤€\u0005}|k\u0002\u0018Q\u001fI\u0007\u0018DZôƒŸ\u0000ì•rb䨃3G%\u001c𧤡\u0004\u00154YB0-i󸣑IMô†‹´[ô˜‚:Cr$𘔴)Lð¡š…Wé¿.x;ꇵ󻨷󳃅\u001fkb\u0018Y9)\u00164\u000fô™¥Av.\r\u000côƒ¥9{\u000e\u0017P\u000c茂u\r-9cB4󸄛G\u001e夡󷯔r📷HcsPmî ›sð¢›!|J<\u00108\u001c[\u0015WYAK𒔃^󰾪c3󾜀\u0007C\u0003\u0017ô«Y\u0014f\u0006ôƒ‘!󼀑:RlQ!BX\u000c=ô…™¦f𤽂ð›°¿O\u0003\\\"퀛B<\u001eLV4g%3ôŒ…\u0006`\u0015>\nê¹’kp󰯶𩷗î±®H冘lyJ\u0012)ô¦­(óº°›A\u001ch\u0004j観\u0014M\u001bP-q\u0008n\u0018𢿎~\u001d\u0019\"oåˆ%*e2ð¨›L󹼿sy𥕑2m\u001dô€‡–{EG]\u00116B+{󰉆IYa󶈙5,<\u001bX\u000c\u000fð­£µð¥¢Eð ´‡ó¶¶L<\u0019dUO\u0017\u001aZYm\u0006ô‰°R\u001a󲋒\u0013^s\u000cu_g\u0019?i~}V2𤓉R\u001c\u00043j댑m؆ôŒ±”\n7S\u000fT5j𩮢\u000fó·µð¢¤“hð¬£Q𣲺\u0015ZMôˆ®02f🤼l!*\u0001󺯙\u0001ô…”°ô‹‘·\t𑱥\u001ba:q UKEN\u001e-\n\u0003Håaô†˜“\u0008鉶\"󼳴𤢿󼎳R4\u0003\u0010\u001c\u0002󵓎%\"@󶛙6=/x\u0000P\u0004𪬗/ð®™™\u000c\u000c󵙚?*\u000cIcKWQ\"ó´£¾P*ô‹¢©6=d\n𦟰\u001eô‰§š\u0004\u0012Ië¦U\u0008=Pc\u0010", "phone": "+153353668", - "verification_code": "123456" + "verification_code": "RcplMOQiGa-JY" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_20.json b/libs/wire-api/test/golden/testObject_Login_user_20.json index df758baf81d..bfe793f70bb 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_20.json +++ b/libs/wire-api/test/golden/testObject_Login_user_20.json @@ -2,5 +2,5 @@ "email": "[%@,", "label": "ô«€\r9ó³°”`\u0015x", "password": "ryzP\u00139⬓-1A)\u0008,u℉j~0ôŠ”ó¼˜\u000cI𩤎er\u0014V|}'kzG%A;3H\u0007mD\u0002U1\u0000^ó¾´´\u0010O&5u\u0004\u001a𨲆0Aó³¿X\u0012\u001c7fEtð—±–rPvytTð¡›“!𘥩$Q|BI+EM5\u0015\tRKrE\u0010\u001f\r?.\u0002|@1v^\u000bycpu\n$\u0012𭤳𠊆-Q𤸩\n\ró¼›½ð¬O\u0005*ð°´Z\u001fo\u0004n𮂕%&\u0013Me*\u0002;\u0010034\nv\u0015𢑮(ô†¤¦ó±®ºn@ôŽ¥¹|ë´¥d\n*\u000f\u0000}\u0015A!󿕺󽃯Hx\u00173\u0002{#T|5|GCô‰¸®z.\u001fNô‡¸“圴\u000bu\u0016~LPð¤¿CV\u000e qð¥†\u0012e8h\u001fgó¸·ž;\u000c󳌋ôŽ«At󹦊)\u001fG\u0013ð¨ªé¦©|ó¾™»\u000fð ®¹\u0004c~6\u0010:u𨘑##^\nn{d\u0018\ngã½­\u001b\u001f\u001f~A8};T\u001e\u0015)&\u0008\u0006ôŽ¼\u001d(\u0013u;ô‹›;=eô€¨š\"é»vCt)oó°½¾mꮈ𓄈l1+󼿼[\u0002FLxô‡¹¤:ó»¼¥ó²—°71/kEð–¹›p\u0014Ij\u0017蓳&\u001a^\u001cl1\u0006󸺜\u0003W,+3ðŸºð—–·\u00107î¥7rG'ô‡š‚JC9Mô‘¬\u0016\u0012ê´¾>~󸇴Yôƒ’«=i-\u000cS𪆘ð¦¨K2-@\u0005\u000côŽ­³_1D-&🖂lRð­­°/󲫄$:窷:ì°«Dgó··‹O󶧽𩢅\u000e𫹟2z\u0015q𢣫c\u001cliJ{ô²µô‚³¦'BLð©‹ž;\u0002󿤼䠋B\u0000ẟbô…¶¹:wôŽ °Ad\u001a6\u0015oퟯ\nsPWM{\u0003fW󸨅JTó¹–±$ó±ží•ð®®ðª“‹u4ô–¶\tè“¥ó½±¢\"𥚰UMôˆ«´ô‹›®è”¹ô¶­\t\nIn'ô…—„剩㻛\u0019\u0011<\u000b\u0008W\u000f}𢧯\u0008ô…³“ó¼°“\u001d`ô‹ƒx\u0000ó°¼¹K\u001cjô‡Ÿ·\u0011\u000fð© d󲆄k4\u001a󶣔쌗^î‘ ô€¾ƒó¸«i2=$:[fôƒºƒ\u0012n\u0015J<=߬\u000f!zô·”\u000eN\u0015\u0019𬈌Vóº¬CQ_G\nY#kyð š«k\u0013\u0005}OC𗤶}~M\u0019p\u0003\u001ex\u0008𬺚ô…½°\u00088/\u0014?ôˆ„¶BóºŽ\u0004\u000eUó¹©\u001b=%ì¶Jð©Ž—\u0017󲕑󱱨󰡢\toôŒ³¬X_@@뀷ꮰ$", - "verification_code": "123456" + "verification_code": "RcplMOQiGa-JY" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_3.json b/libs/wire-api/test/golden/testObject_Login_user_3.json index 54947a4de3b..b61e6008106 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_3.json +++ b/libs/wire-api/test/golden/testObject_Login_user_3.json @@ -2,5 +2,5 @@ "handle": "c2wp.7s5.", "label": "ôˆºðŒº>XC", "password": "&\u001e\u0014ô¢´ZⲚn\u000e𦯣󶳚/🄕'ôƒ¡¾m󶪩\"☬ð¡·\u001e(&󳓮\u000ef1Wf'I\u000f𘞾󿫦󼛩\u0011Jqô€ ±Y\\Bedu@ó·­·c󵇒Dì¿›ô€Ÿ¶Sð£ž\u0003\u0003W>󵜮\u0014\rSO8FXy𨮱a\u0019ð© ¡\u001aNFð¦§L\u001e$5\u0000k\u001ez*s𤔬𦤜\u000b𪴹\"SY\u0002󲶃ôššub5q\u0005󷨛\u000bN.\t𬳰:lô·´\u001e󺺉\u0007ð©\u000e\u000btôŒW\u0016󾟜ôŽ¿›\u0007'v\u0017ï«“\u0015\u0002 \u0015\u0002숔,ô™Žxó¿±´^ô„¡·æ«¦I;\u0015bôŠ§‘o𧯋_𮡒MME󹩀\u000fô‹¨¼H;\u000e\u0017s\u000eô„‘{Knlrd;讦\u0014\u000fô†€TOôŠ¡ó´ƒ—U뺓ôŒ¢—tô„ºˆ^yä´u$\u0011Jpô™¤ð¨©ð¨‰ž\u0002\"ô‹›§*\u000e󵌎綦󱻌XôŒ‘œ\u0003sK}\u0008𣈮\u00000󱘴12𩱬\tM052𮑯\u00040\u001eó°°šôˆ´{ji\u001b󹎀橻&t \u000f\u001by\u0007L𡎯𠇦󲫫\rô¡¥ga,\u0014do,tx[I&\u0014h\u0010\u0003\u0010Bpmó´¬´-\u0007]/ZIó¼Žq]w3n뜿e岌kYo5ôŠ”œ'KôŠ„œ}v𣵇;󸮨\\=ê„°8g\u0010g*has걿󵨦\u0013\u001fYg?Iä°†\u0015aW2ð¤®m\t}h𥸙RbU\u0002\u0017lz2!\u0013JW5\u001b󺡬U\u000eg,rpOᛡ]0\u001bǟ󵞃F\u000fó¿—ª\u001e\u000e⺄rlô¦²~\u0006+Mn{5󲧸a\u00192\u000b{jM\u0017Tô‚”¹$\u0011ôŒ£†\u001dj_~Z󵸥P\u0001\u0004o@TJhk\u0004\u0017k:-𗥇[p\u0010\u0011\u001e'\r\u0002Q,,ó¸¢?H\rh瘑\rj𤈎\u0012\\(u\u001bu𥱑󴳈o\u0014󱕌ô™©ô€¶‚\u0011q\u001d-\u0008齧\u0011qW>\u000cysá¿‚,'𧃒<", - "verification_code": "123456" + "verification_code": "RcplMOQiGa-JY" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_5.json b/libs/wire-api/test/golden/testObject_Login_user_5.json index 19f1aa2570f..7042d152a24 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_5.json +++ b/libs/wire-api/test/golden/testObject_Login_user_5.json @@ -2,5 +2,5 @@ "handle": "c372iaa_v5onjcck67rlzq4dn5_oxhtx7dpx7v82lp1rhx0e97i26--8r3c6k773bxtlzmkjc20-11_047ydua_o9_5u4sll_fl3ng_0sa.", "label": "LGz%ð’j\u000c\u001e/\u0001", "password": "ð˜›ð­†´DU󼸸hpóµ±»t~\u0012\u0001\u0002*ôˆšy1ô‡‘®H𪒧{e\\S\u000e?c_7\t\u0014X𡀓6𪊲Eð˜ˆj\u001a\t\u0016ô‰¯¿>HO]60ó±­“\u0003\"+w,tô„¸\u0007k(b%uð¤º`>bó»‚°e\u0006c𤽡ôŽ œ)ã—7ôˆ‘ `𭟉yO+v%ó¼—€\rc<ðƒ¤2>8uô‹‰²ô‡µô¸—ð’”™a𫯹\u0015=\u0004ô†¼8R&j󾣆\u001b\t4sj-󲉛鷔n𡘽ôƒ²™N\u001d\\󺡋𑩠5\r𗫬(P!爳棧\u0008ô„«¼Mr~ï¹£\u0019jt>Z\u001d~𢖼Aó»²¾\u000e\\>\u00116\">%댤ôˆµI@u5ð­·³\u000brY\r;7ô…ŸŒ#ô‡’‡ó¸‡ž\u0018'ï“ó¾µ\u0019_I_zY󱂤ð¤Ÿ\u0019dó½·¤cdôƒ‘¯ð¡’†Cp3曕\u001dXj\në“¡jyê°’î—‰\t-䩹ꥈdó¿ “L\u001aYF\u0006POLí—®\u0012\u0011\u0011\u0012*\rH\u0010(?\u0013Fæ“œ\u0010\r]ô…†‹jð©£ @\u0005TôŒ®s\u001cF2\u0015]8\u0007\u0013!\u0015Wð«…•ôŒ²Kóº¢ô¢ž_%󴥚ô¯­'ôŒ†¥ð‘‹˜(#\u0001ky\t\u0017!ä’¢\u0015\u0014\u001b{ðˆ•U2LS'", - "verification_code": "123456" + "verification_code": "RcplMOQiGa-JY" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_6.json b/libs/wire-api/test/golden/testObject_Login_user_6.json index 498aa516f3e..c90752a5c9b 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_6.json +++ b/libs/wire-api/test/golden/testObject_Login_user_6.json @@ -2,5 +2,5 @@ "label": "ó·­„'󳩽KW\\\u0000\u0014", "password": "K?)V𤊊}_ð­·ôƒ˜\u000cJ3!ó°·•ôƒ•ì¦Ÿð¨ª·ô…Ÿ˜\u0007ó·½»\u00017\\#z9𠥿ô‡½‹ó°©šó¾’\u0019\u0013𦈎'\r)~Ke9+𪷶𪺢󲭎MôŒ”©\"h\u0001Th\u0004`;\u0006ôŠ¶ \u0005󺦪'e{\u001cvé¼µ\u001f𢿻*㽬ô†º¦ì¸Ÿ:E]:Rô‹‚¿K}lô™ Y집ô€‹¦S~\u0004#Tó»“„1hIWn\u000b`ë†Kb~\u001b\u0010dT\u001c\u000fôŠ¨­f\u0017Y7\u001eð ‹œ\t󳸻㑦뱲\u001dG\u0013BH#\\RAd𨣓gô…³¤ô™¼\u000fk&\u0002E囉\u001c\u001c\u001c$t󴧥:OôŒ‘q}_󽯀.\u0001\u0014\u0002𦙎c`L>ô€¡¸lô‰”‚m'BtB5ó´¼,t\"ô„•¤9(#\u00054\u000fIy>󻯶ôŒ«¾\u001dbf\"i\u0017ã Ÿaô‰Š¡C@ô‡˜¼ôŠ¨©çºŸ\u0015󳻹嬰*N\u0016\u001b:iXibA𡚓𩘤qô€—]:9rð’‰\u0000ô€¢‹\u001fCN\u001f𤃾ô€¹ó¸eR\u001eZbD5!8N\u001bVá²°\u0006ðªˆ\u001auzô“¾ð­¾”~\u001b\u000f%{3I/FæŠ/DMS\u001f>o𭬿ZôŽ¬ž\u001d[K𭇡𗇅ô‰­±ó³€’\u001bO-4\u0018\u001f\u001cZp", "phone": "+930266260693371", - "verification_code": "123456" + "verification_code": "RcplMOQiGa-JY" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_7.json b/libs/wire-api/test/golden/testObject_Login_user_7.json index a492835be1e..8ba7a5ba2aa 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_7.json +++ b/libs/wire-api/test/golden/testObject_Login_user_7.json @@ -2,5 +2,5 @@ "email": "BG@⽩c\u000b}\u000fL$_", "label": "\u000e\u0015eC/", "password": "&󲉊󹴌𔖘\u0002J<-~\u0002>\u000b𒇴𥄿5QNí‹ð¨¤¨ql\u0015𒈲3}{\u0013𪒺S壓;\t7𬺖_F~D*fô€•”)ô„¥‚-9僛7GK= %\u001e@kOF#ð«»©ô‹Œðž¡‚8_ê•…\u001dLé‚\u0003󿶊0Wl1A`LYz\u001fy僸\u001ao\u001b[\u0014\u0008tð‘a\u0003s~\u001fF𪰤G`$\u000bG\u0011󾿅🙣/󷪺C>\u000f", - "verification_code": "123456" + "verification_code": "RcplMOQiGa-JY" } diff --git a/libs/wire-api/test/golden/testObject_Login_user_8.json b/libs/wire-api/test/golden/testObject_Login_user_8.json index 49eb4bbfed5..20658e70ab3 100644 --- a/libs/wire-api/test/golden/testObject_Login_user_8.json +++ b/libs/wire-api/test/golden/testObject_Login_user_8.json @@ -2,5 +2,5 @@ "email": "@~^Gô†ª\\", "label": null, "password": "z>ô‰°ƒóºŽ‡/𡞯ôŠ³Œ\u0008%$󽖨𣄄:}\t\u0018ô‚œ™ó¾º½)ãŠóµ™¼s󵪾\u0018}é±¢\u0019[ê…¾\u000bX#VG,df4𢢵8m5ë”OTK𣑌鋎꺯◆Z\"ZS\u001bms|[Q%ô‰²¡\u0005W\\ó´–™C𭌈+ô…•ºá€’ä–¡vð¬¡êŽž){󻆡𣃒f𭬔}:X-\u00082N\u0019\u001fl🎢쇈Yô…¤¡ó·›r2.1싸\u0004+ð¡¥™\u0013𣡈]'󻂳s󳤴ꜵ.}ð­‹£oó²¶X𠜥⬅\r\u001aNq6󸻕'\u000cd\u001e㢧ô‹¾œ,:%\t𥅬𒃣QDô‰– \u001b(q4KDQ2zcI\u0010>\u00195󲤼1\u000cBkd\u0013\u0006:F:\u0004𘨥ⶂO N\u001c,Nôš¶ó´Œ·[h9ᜬ:xZ=\u000côˆ¾€\u0013u\u001e\u000ce#\u001a^$lkx耤 \rr\u001aJó·¦ó¸“¡\u001cR][_5\u0015î¯â·¤è¯ƒ5惵ô®µó³—´é‰…K!ô eRR%絬+h~1󲞮谟lTzS$\u0010ô‚¶³\"*ô‰•·pmRE\u0013(\u001f^ὯJcâž‘ô…«‡i\n+G$|󲫉𦉻g\u001c\u000cgU3Yð„œ\u0006f)ôŠ¾º\u0016𓈄ôŒ­ž/\u0000Piꩦ{󿸟jôˆž…\u001c9𠚽󺊬翉w$눟𞴦)Si𨴄牿FXô‚‹’j{`궤`󳿗ð§4u%ô…”ªP*ô‚‰»æŽC\u001eR\u0016-잚󶽕gð°º:S>c㮢ð Œ\u0010Yô„~a)YW_Jôƒ¢¤P\u0007+ Uôˆ·“j\u0019k\u0001ô‹´˜\u0011䣷e𪋘𪳠,ág@\u0012\u001dHXl.\u0017ð¥£2\u0013mYô¢«\tv?L8Lô†¼N𠦽\nb1j󾸸𤋵xfQ=\\\u0005e󳇪󹽶U\u0012p{\u000eôŒšŒjd^@Uó²¯tP.\u0012Y%R`a\rð§®7}HnUf𠛸m^7:\u0015=ì±¼>lð—‘‘hwp27𤦾jE\u000cx=!.\u0013]Ar\tw\u0014&\u001ak㒞s󾦄ᆒI𣪗ô‚¼¥dsY\u0010𬚢dX.𣭷i]𤹉󻃀\rWS\u001fUôŒ¬\u001aì‹œôˆ¨‚\u0010\u0002N~-\u000e6ð®™ô„²\\Oð­Jcô€»‡ô…¢®\u0000HSo\u0010-W\u00136𩥑Iô„º¨)𘗘={𘗔h洹M󹩪FwJQôž¨ck\u001a\u0018|UV-\u0015\u0001|\u0014;\u000c𦓫𣦃\u0005S\u0015.B\"D𧲿#o*𞹱胜m\u001eô€“ªB3Gg;\u0011\\𬆳ôŒ’®\u0005 B^\u000fð¥¶$e餴𩠵>fMgC𭮌,o🗨\\?󼛣~/s\u0001?MMc;D18Ne\u0004\u0018)*\u0002\u001d㾌1/\t\u0015 󶫒󷘿zè‹BvôŽ²‹(=<\u000eqôª¬?L᪽ô„—»à®œc󳤌<&!ôšŒó´†j~O3USw\u0012\u0003\u0007\u0017+󺀡Nyç²°(/Sco\u0002{3\u000fEh\u0016ó¼†ó¹«æ°”-\u001c.'\u0005X𘉸𤮓Ti3ô€©²\"%\u0016\u0008𮀜+\u0004\u0002^ôŽ§¯)2bR\u0006\u000fJB[ó¿Š»&O9{w{aV\u0005gZ?3zô„ˆ­8á³ð¦”–ó±´µ`ôƒ¥”\"PE)uKq|w\u00160\u001b. \u0003ð‘» sxW𧉥󴚗m\u00057e)ð“˜ó¶‘¼:s\u0018Yj⚎㿤\u0006\u001flTuô„¥I.ô‰™œO#kQ\u001e!gôƒ”—\u0018Q\u001fðªƒ\u0016\u0006|\"M\"P\u001f\u0003@ZPq󸌖gYð¤’=\u0007ô‚­l8󾌀3󲻄󹪢CN<𤆤gJ󽡢]ð—‹”mX~\u0006w3\u0010𫸴8\u00076\u0004}\u0010i\u0013L5ó¼‚PY^|!Vz\u001b4èµ°!iLaâ¼»\u0014ô‚­ºð©€œ\u001d:󾟿𤢈h\\dLxô‰£•\u0019𥚚\u001að ·«R%ps7ð—€sô†““fg\nIfô„¢¿\u0011l\u001aó¹®—-n_áž±UY?4d]|c\\[T\u0007jS䦖휆é„aKóº––ô© \u0003\u001cx+", - "verification_code": "123456" + "verification_code": "RcplMOQiGa-JY" } diff --git a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_10.json b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_10.json index 8c206d3e0e5..97834f65c1f 100644 --- a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_10.json +++ b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_10.json @@ -2,7 +2,7 @@ "accent_id": -5, "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_13.json b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_13.json index 10c2c3e593b..3f8a4222ac5 100644 --- a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_13.json +++ b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_13.json @@ -2,30 +2,30 @@ "accent_id": -6, "assets": [ { - "key": "\u0010k", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "/", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "呬𮀉", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "\u000cô‡€ŠE", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_16.json b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_16.json index 490d5905858..06779bb3835 100644 --- a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_16.json +++ b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_16.json @@ -2,12 +2,7 @@ "accent_id": -5, "assets": [ { - "key": "\"吝󿳖", - "size": "complete", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_18.json b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_18.json index e2c845c07f1..bd39b093114 100644 --- a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_18.json +++ b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_18.json @@ -2,17 +2,17 @@ "accent_id": 8, "assets": [ { - "key": "\u001c", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "ð–«³ó°‘‘", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_19.json b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_19.json index 779f6e45c78..d69befdcbc7 100644 --- a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_19.json +++ b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_19.json @@ -1,27 +1,27 @@ { "assets": [ { - "key": "ð¦³\u0011", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "\t", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "V#", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "(\u0003", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_2.json b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_2.json index 8da98068cc8..eb75d9b63dc 100644 --- a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_2.json +++ b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_2.json @@ -1,22 +1,22 @@ { "assets": [ { - "key": "C#ô¹¦", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "\u0014\n", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "V", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "Y+_", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_20.json b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_20.json index aa68bf97097..0a7b710fff6 100644 --- a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_20.json +++ b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_20.json @@ -1,17 +1,17 @@ { "assets": [ { - "key": "\"", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "ô†µ›d", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "8", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_3.json b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_3.json index e7dc04b5481..7d833c75372 100644 --- a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_3.json +++ b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_3.json @@ -2,16 +2,16 @@ "accent_id": 0, "assets": [ { - "key": "'\u0012", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "`", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "?ôˆ¯…\u001b", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_8.json b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_8.json index 161483bf34e..fc11374d349 100644 --- a/libs/wire-api/test/golden/testObject_NewBotResponse_provider_8.json +++ b/libs/wire-api/test/golden/testObject_NewBotResponse_provider_8.json @@ -1,21 +1,21 @@ { "assets": [ { - "key": "ᘻ~", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "i𗳫", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "\\", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "%o", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_1.json b/libs/wire-api/test/golden/testObject_NewClient_user_1.json index 496b6ef8b3e..1e53084e62a 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_1.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_1.json @@ -4,6 +4,7 @@ "id": 65535, "key": "\u0019" }, + "mls_public_keys": {}, "model": "", "password": "]?S`bO,%\u000coVô‚—‰\\+ôˆ»’1𮈑Hc\u0007\u0002蛊\u000bH\u0002<^eô…ƒ–(\u001c(UsaJ𒓨\u0004\u0003J\u0000~Kðª§í‚¥ôŒº\u0018Bå–†wP\u001eb?^𥋾!\u0006󱪦z\u0004DîŸ\u0010Vó·²²ná–¡/Wð–®…\"\u001b겸mnq_t\u001bCRôŒ¡”\u000fCq󼉺ô°²\u0013\u0007\u0018\n㤔𫕱\u001dr'\tAôµŒ\u0012󶌖󷶽ç¿VW\u0017K&#~\u0016>\u0011J\u000e;UFO\u0006d\u0014\u001b\u000e󲕒턘\u0006\u000f`𦆱RxHJVQ]:s\u0007ð­¿›IY팡~H6,80ôƒŽó»¬¹\u0016wx_è• ð—­¹q\u0005:N𤨾ôŒ¿“ôŽ¢’𦚶\u0000F\u0003As𦑥󲢲bf\t𤿵|\u0006óº·¹\u0012{\"\u0010â…ó¿•>𭪌,i.L\u0001𣳴>𠆃g翘󺻚𭻽ôª…6󰇵\t+𨩰8\u000ev\u000c\u0000\u0008\u001ev)é–ˆ\u0011J\n\u0002ô†¶¢è¶\u0014&R\u001b:~ð–­ƒ\nO鄹5\u001d!3Vô‡¹‘𣺜,ô…n\u0018ó°·„ó¿‹½^ͱ\u0018䔧\u001d!\u001a砂𪆾󷣣;8{\ttB[A\u001dHôŠ­–\nv巇󵯣z|\u0018S^.@\u001c𢚛#PôŽ¬—V6:%7|\u0012oð˜†ô‚´‡)V𧓲H郘e\u0015ê®/ôˆ¸¢\u0007Svð¦º?\u0018W7\u0017IOc~Eôƒª‰ó»‚ªb\u0017ð¥½ð–¡¶?曻󽲪e\u0019.|DXlNA-ôˆ‰ˆð«¥ƒE\u001b+ó¾“SZ𗺉󱢵hRm/^{ï©ŒôŠ‚ó½…¥=*)ô‰¡¿ô‡¬ˆó¸§ŽHjRó¸€ã¢¿Vg󾓤!g𩒿𣻀ð£\u0013PY[=2\u0012!0󵙕\u0016\"ô´­ó¹˜-<2]`6:ó¿¤'&8\u0003\"LLð—®*^:\u0005\u0019󿳞 T󽃙e[8%J#~uô§–绿j=hX66)+\u0016\u0007U\u000e\u0001ô…”²jôˆ»ŒôŠŒˆð¨°ªTd^ô‡˜Œó°„¶\u001b:\u001d𘀲ô†£ƒ@ôŒ°›X\u0013\u000e싧\u0019%`鞇ôŽª˜ïº›ô‰‰»\u0002\u0007-\u0019+_𧻨:H4o\u0013HS6`\u0005ð¬‡M\u0017V?.>óµ·CO-𬞹󼤊bf}🀥ZM󼦨\rNpô‹š–󽀘𪂡%\u0006/WNZté«‘HnOô‡•“=xmô‹—­ê·¸@$Z󾊘dEZ_1\u0000\u000cw󰃔\u0011Iq\u0019a&ô¸†ôŠ­Ÿ\u001be\u0012:ô¦¾#\u0002e\u0002ó²¥·1Sôˆ—¢v=Aó²¹\u0000.ཙôŽ‘™<1 ð¡ŠŸð ‘Š$>M6岤ô‹­²óµ‹ž&t_6\u0019𡵱q\u0015ê±¾ZTX\\\u0013ô€¬­ä©¾a:ip\\]𣨊yð¡ƒè€ª:b\u0012炧\txB;!ó½¥ô‹‹©î€‚HmV\u0017vð—“·v\u00016(/phð­¡·.äž©\u0017\u0000Cð¢‘ôƒ¥³)R5ôŽ–´a\u0010%^ôˆ‡—X𨎤$D9iô…ƒ\u00126wMó¿˜\u000eI%xôŒµ€+YT𣨤𩪱𢶋Hð­•—L\rU3\u000b.+<\rT󶞜ó³‹_vð—™]H(\"\u0000Jå‹¢C\ru\u0011-ô‹®¾Q\u0019\"\"ð©–¨r\u00047P𨌠 \u001cô•ŸRó»¾—ôŽð  ¢dz󸿥ôƒ—D𤈻Z\u0017ó¿—†í‡Sb7ð¥‹CL\\", "prekeys": [ diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_10.json b/libs/wire-api/test/golden/testObject_NewClient_user_10.json index 7b4b182e7ee..f08c32386b1 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_10.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_10.json @@ -5,6 +5,7 @@ "id": 65535, "key": "\u0002" }, + "mls_public_keys": {}, "model": "9FO", "password": "\u000fxXs\u0002ô€Ž©2ô…˜î›‚헴퀰X\u000bôŠŠ®\u0004Ik𫇌E?ZQð ’´O3X󲘱!#B_𥓛}ôˆ´–yôš¨PM\u0005{\u0013𦙆Ln\u0006F\u0012\u0000â¬6g贎󲊰摳$S\u0014\u000fóµˆ4'?zô‡¿™0鬨Aå”\u001e\u0010:pg\u0017,_V蟯殨󾉧U=S^$󾔆u:X漏~/*ì•´2\u0005d7pJ-^\u0000\u0007ð —6a𦳟:%ôƒŸ$ô‡€±69R\u001a/G?C\u0007b\u0001l$/K\u0003\u0013𢹢g 4J3\u0000#筞󰹡\u00102\u0017𪴯vV2\u00196𗧪\u000bè´W헞𫈱+4tk!+Yð¥¬ô§‰\u0015Qó¹´¾ô½“GHQU\u0008x;c_\u001a^蜢]S7\ru]\ró¼¦!ô„§ð¤¨¬2\u001bMgô†™³QE\u0000m\u0000\u0002\u0011P𥶫\u000b(Hó·®ó»µ©|gôˆ’Ž\u0016b\u001c\u001b?=f󸮋헢\u0013\u0000\u0006g\u001f6𪄔Q첃q\t\"𮚲b\u0012𡹽3K𡶤9IxGW(\t:\u000c$9𒇇\u000fU?(𫎵\u000cgI;4\u0019𤄕\u0005\"RGbgCf9ï©…Ioô²«ì©·8wb(]\u001fôƒ™!ôƒ„žô‡u)t\u0010kO\u0000_\u0014\u0017-\u001e 4\u0007,\u000cä´·Qgdô‡š°å§ôƒ’º\u0013;󰳯~ôŠ®«Ui\u001cHó¿±\\󽻞\u0012vmUó°„½Dð ¹±\u0019󰘟\u00172D几鲯\u0006\u001aJ\u000eI,ô‰ƒ«TgXb\u0001\u001cô‹‹¨\u0003ô‹¥C2🌥7ó¼……P \u001a\u0018(8htD\u0011ç ð§°…ô„©’WzoM\u001d8\u000b.[zIG`T𨈊<$sô†±¡ð¨ –\u00160=f(\u0005?v^?_ẊX>7呱|EQzó½²®q3\trô‡³‚nq$yqð¡©5ó±Œiô‹®‡h3\u0005ô‡ ¤ä«™Ac󴺫s𥻅蜣X,\u0019ôŠ…¶>몠2\u0005}\\M\nd3%`>æ…•?\rvô’•è£¡d>\twE𭹿G󻯖7ó¹·ž\u001b{Gô‰³\u000e\u000fvô¤œ,cPô‚½¨ó¸¾¦\"4\u0010M\u0016𫦛󹘥8uô‡’˜n\u000ew\u0003V\u001f\u0010\u0012\u000cXf,󼿺bôŒ³·\">&0xFqd]^\u0012\u00054@9\u0000ð—„Bð’‚²}L\u000b\n9S\u0004\u00015im\u0012-ð—–}\u001a'𘈅#,ó¼ ¢c\u0006\u0003P$xcð¡‰tB\u0013\u0017è©ž\u00160\u001bu\\ô€„³bå©«\"\u0004󵵃bwE:\u0008\u0014ô„¯vp8󹪞\u0001\u001178$E\u0012JE>{?áž…I\u0015J*7sI\u001beuô‡†©â¢µ1a\u0015)R𭌸|PJô“—at\u001a)\u0007i2ó¶¢mYë“•~gdde>ó¶­yG5bÊ ð — ó´™‡zJ󰛞\u0015T?i\u000côƒ´„F,]9\u001dë“%hB🖭h蟲9ð©Žš=K\u0015rFVWO\u0018Vcï½’ô„…ôŠ²Ž\u0015ó¹ †\u001783\u001b4\"󼲎\u0012Rô†€žð›°­1UDô¤£Fiô‚¯•8\u0019?j5jó³´›\u0001W)ô‚Š‡fJ*mô†–‰d🃓l\u00142ô‰–™\"Xgp𗘸v\"\u00176U噳w\u000e2*𪩢厱vl\u00180\u0006;jh༟IC\u000e󺣖L;55\u0018ó»²\u0006`m3*. C)𑊌𦋴x>y<ô§ª+>*\u001b\u000bs\u0001ô‰´ºNSChj9ì“\"#M`?í—ši\u0005\u001f\u0002\u001dlcS󿲞\u0019C\u0005\u0001_/u?/钷󳛦gðš¿ðª²*\u0002_vô‚º\u0012C\u0013\u0017𪡖J~637\u0010\t\u0011P\u0010yð””'Wz\u00168󶔘j4Õžd^7图JF&,\u001aë «", "prekeys": [ diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_11.json b/libs/wire-api/test/golden/testObject_NewClient_user_11.json index a5c9eb103e3..134f1550167 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_11.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_11.json @@ -6,6 +6,7 @@ "id": 65535, "key": "" }, + "mls_public_keys": {}, "password": "\u0005WU#*\u0008ð©™°\u0003pภ\u0014có¶ šeëž½mô‹§\r\t󷑴Ậ\u0015z7\u0005\r?I~ôœ¼;>$]I\u000eê“›\u0011𨋙Tô¨¢ë°Ž\u0006\u001cð«­©y71,h㇛{祪`\n\rðª°+Nô‹¬€\u001dlqmiôŒ ƒ\u0001\u0019=T#%󹨨\u0006\u000cZ_UCe\u000fk\"\n'%ô†¬·I\u001f\u001eZR.n%\u0000'`u5NI\u0017x\u0006+ð„j𩤃(x\u0011;𬒖zyUe\u000c𮬥邒\u0012ow󿄺?-Wzó²²Z+\u0001\nôˆœ—\u001bly󿨓6l\u000fNn\u000fT\u001b\u0019*ôˆ¥£8&E.\u0008𤒌ô…›–\u0019_\n{ð®…°\u001e𦰛\u000e`󸻦ë•ó±µš^1𢀱=3X\u0011K\u0015\u001ec\u0001Bô‡™’B\u001dm1$\u001cDve𢭆g\u000fô‰§¢D<\u001aKo~X\u0001ô‹«ô·€-3ô‚™“\"â–­é­¼e6G\u0017󶹬ô–½í…Ÿ3eM\u001931[L\u000e\u0014@ð —F\u001f,bÇ󳿵\u000b\u0002󰱓D\u0012V\u000bX\u000f~ô‹¸ž nS/𩞟RTD7cMd\u0011\u0004Owó²·¨yO[8ôˆ—¬î³—𘤷[ð­”«>𘗓\nA<=PE]'\u0012ó°µ¾7I Qô…… äŒE=\u0016\u0003;v\u0018\u001a𧿡!ek\u0012B4\u0002\u0004ôŒ¨¾ð«¤†ã‘b\t@{0ô€»–𬔱𥀹\"\u0003\u00039/R\u000b𠤶n󻀄\u0006A\u0013\n:ðž¹’ë¤ë„ªð®“¨%Z\u001f\r[EYo0N\u001e\u0005KR󼎹ôŒ¡±~c?\u0019J?`\u0011󺄿ð˜„\t󽕦h.n|\u001a2󱃺{'𣙂ô‡º¬/^ut𘢀𠶸--ô …\u000f쵽󼤶T?󴎸𫘜󶀛?\u0010ô„Š²1ð«·¡*6\u000eti6𠶹_Z7\u0010\u0015\u001b\u001a\u0007/M,\r󷟞毶ô€ŠŒlg\u001djH.uC$옊^1h)\u0008,\u0017𨪥aôŠ­¦R/ó¼°Š_\u000f.tx\u0003 >é½ó°ž”%B\u0017󶚨㥷5\u0002𥟕\u000cð¦§ð¡š´î†•}-j\u001byO\u0018뤶^ô€¯«F쥳J\u0019\u001aE}&9w𓌹u\rä®nYűq䗹*3\u0008\u001cð¢šô€±€~Rx\u000f\n\u0006\na?S3æ¥\u0008n\"gfôºœî‰±F&㻺V\u0017󺇔W󸀯\u0016N\u0006\u0016|r,;.w$\u001c\u0008.ro[V󱆦󷾛&\u000e䊭+䈙=\u0006\\M\u0019YpXZ|Jnnrô€¶€\\óµ´å¯¥A}H𨆗\u0017:_\u0013[\u00012%\rh\u001c\u001b]ð–£”\u0013𮤲;\u000côµµ#\r*0𖤧Z𢟓𥡻󻷜ôˆ´”2𤊓\\ 7摭𫌌\u0015ì‚™é𫱲pôŒ—«]Pð–­¯q\u000b\u001ei\u00153FXð’†’8\u001a胵J\u0013i夹#ïK󽓇%5j𫹑6鴆𪷅ô²‰ð”¤]B\u001f󿈤3󺀙-o;9>󶘾A3~\u0006\u001cRê·ð¢•˜\u0001a\u000ciFV󴨵e\u0008ó¼´ð—©œn*󶇳\u0005𤡨\u0003\u001dnp\u0014m,\rô‚›€hb\u0013ô…‚¼è• ,&d", "prekeys": [ { diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_12.json b/libs/wire-api/test/golden/testObject_NewClient_user_12.json index 8773c45498d..46587e52b92 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_12.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_12.json @@ -5,6 +5,7 @@ "id": 65535, "key": "\u0005" }, + "mls_public_keys": {}, "password": "T𨦷fqdAä›’k3ô³±[\rK\n\u000c^ôŠ”¥0𗼯\u0011\u0003'ð—°’M+\u0003\u0007\u0017\nw\u0001Ü…K𧘱⮨z~qp𥞅\u0017\u001dW4m@CYG(ìŽob:\u0018P~My\u001b𪸩\u000bð¡‹\"è”›&𑘩Xô…€r\u0011ð œó¹ª—v-p+))\u0005\u00029i {&p𨊜[\r\u001f&;b>N\u001b~bu\u0013\u000b:W:S9\u00101L|\\S+b0Q\u0002ç²rs~;\u001fG;󰸼oó¶­à³%\u0002m\u00125UTR\n\u0013X\u0017ô‚™²~\u001eï“\u0010;\u000e/\u001f-\\mPc<:.HmK𪰯o\u000c\u0001ô‹‡‹&B2\u0007R惛51\u001d&-ð«·¨c𬹸󱚺t~rT𪳔H?6𥽸~Jô°q,\u0002\u001e#ô‰ƒ¹W.=ì²µLô††¤-ô†¿‚\u0006𪑆𥇌75*Oq!芙{x\u0010tô‡ƒ¥ó²¤´X(w.\u0004\rô}kp/\th,z\\檩\u0006n+=屸jL~F\u0015縟7\u0007ê¹´ ,0È…b\u001b\u0011\u001foA/\u0006\u000b󵛜\u0004[o(z+c\u0001è²…sè­žC篧\\W6\u0015'\u0002ô„‰²6Xb~TOôŽŽ¦ô‡” U辤\u0001\"H2ô…¡œ\u0011ô–²ô…›‘O#\u000fu\u000f𑒔ÆB,4󠆳ôœLôŒŸMt>I)U?p\u000frQ󲋧󺅼7ð©ªô‚š¶\u0014\u001b\nภ\n$󸻟\u0007bhXT\u0013wè¹½ xᶶ\u001b?p\u0008\u0006󸼬N󿶆=j<𦎰=)\n6懘ZF=*`(\u0013\u000fôˆ±´\u001b\u001c𪀲\u0003yR},ô‰¯¶r\u001d\u001f hY_ô‚£–ôµç³eHsWjô„…™N쟛H6dd\u0011%g󹊧6ð¨¦=KX:V5ló¶½0\r6K6?ó»´\u0018󸑕:,x󲫻_{rgc\u0008I\u001aï½¢ô‹Šº`\\\u0019l/`\t\u001f\u0010`r󻟠+Eૃ𖭪L󲃻\u0019ਰdô€Šð¥¤¤O\u0016ô‹œ.\u001dom濯vô‰‡𥪧d@ô†¹±.O,*𤾡K\u0000[)H|Y\u0007pe\u0007x\\P𓲠N󷉩ôˆ§ˆ|\u000c󺩚c\u001a\n\\+\u0001\u001aWCk\u0001e\u0003G.B\u0000&g@L\u0003?B\u000eCó¼® \u001b.\r\u001dv(uz)\u0019䶰󺽴Y?/R\u0010`]g1o1\u0015:\u001f5󸤦󺺹\u0016KS\u0018?𛀫ô„¿°ó³¸šô†ƒ•#bf\u001b󿨳mcí“œQ", "prekeys": [ { diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_14.json b/libs/wire-api/test/golden/testObject_NewClient_user_14.json index c4efe34df63..94de84841ac 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_14.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_14.json @@ -6,6 +6,7 @@ "id": 65535, "key": "译믳" }, + "mls_public_keys": {}, "model": "ôŽ®", "prekeys": [], "type": "legalhold" diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_15.json b/libs/wire-api/test/golden/testObject_NewClient_user_15.json index da74da74b09..867c5f5836e 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_15.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_15.json @@ -6,6 +6,7 @@ "id": 65535, "key": "ð˜¡ð›°«" }, + "mls_public_keys": {}, "password": "0\u0019M~K𧥪ê¿ð¨‡‘óµ½ðªƒ¸y󵆎*𪧒󾦒\rE\u0011(ô‰·—óµ¦+ð«…‹\u0011ó¶…\u0012ô‡š£\n:󼄴Ig\u001að’Žl\u0019ꤟp\u0004xô‹Œ–#\u0008󾵜~󴸦f\u000eð©‚…L㜹5ô…¯‚x[#&\u00131qHð –³eA\u0008è´•ó´½ô¬„U\tb'𞤷0\u0014ôŒ˜¢ë•¾iELNQ\u0008a\u001eb\u000b=ᶀG}s\u001dNóº—Œ$L\u0011\u0017\\ôˆ—’𘆬ð¥Œ\"󳩺4\u0018\u0011H\u001atô½¦|\u001c6ð¡Ÿ“\u000fqu/k/bó´«Ÿe4/\u001cDôˆ®‚\u000bó¿®„R雎tL)󸪀6Q\u001ed뗎𣸂qzqbC\u000c\u0004pS\u0010Y/󱱪ôŽ§˜T󾽎å‚,tw~)k6\u0008\u001fZc󴓧\u0017ô‘¶\u001c𢬒Z;6'⛳\u0014e8)4YTô€‡·7gm@ADð«ž…\u0008{𪷅\u001a'x5\u001f\n\nvð¡·µôŽŽ‚L[è­¨ë˜NÏ“N 逻Oì¼8󹻫蒙\u0011\u001d\u0019\u0002󶸊\u0008𬆩󱻠yX)𢊪WmBo\t\u0003Fj=5nô‰²¶\u001fð©¡µ*\u0000WuDo7\u000bLt]ó¾ff첎OôŽ¾…\u00104%SqDð¡—Š][v \u0010\u0015\u001a\u000fôŽ›—𩲼\r.Z\u001eíµ\u001f\u001có»¿’", "prekeys": [ { diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_16.json b/libs/wire-api/test/golden/testObject_NewClient_user_16.json index d1bc996d9d2..bf5c0ef26a2 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_16.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_16.json @@ -6,6 +6,7 @@ "id": 65535, "key": "ô‡Žºé‡¹" }, + "mls_public_keys": {}, "model": "\u0006;𢿘", "prekeys": [], "type": "legalhold" diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_17.json b/libs/wire-api/test/golden/testObject_NewClient_user_17.json index 07c8bd30bfc..d00a2002fd0 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_17.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_17.json @@ -5,6 +5,7 @@ "id": 65535, "key": "ð¡°¦" }, + "mls_public_keys": {}, "model": "\u001bã°³c", "prekeys": [ { diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_18.json b/libs/wire-api/test/golden/testObject_NewClient_user_18.json index 4e296c9e992..1264282e9c6 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_18.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_18.json @@ -6,6 +6,7 @@ "id": 65535, "key": "zð«­" }, + "mls_public_keys": {}, "model": "ô‡ƒ™ô´ S", "password": "\"5\u001e𧇡â”ô«¾Y󻯃]\u0010qDDó¿šg7G󲛖𒔧騒𠬱ceEð®’• Nx}𪉄\tFå»a\u0006N\u000c\u0010󽽂)Uî‹‹ó¹ ˜v\"\u000e#ôƒ¦¹Cb/Dc`;k:qA\u001fQcsw[uG~çŠ2i\u0017𦖡RB\tô‡“¾\r(\u000câ¶ô‚¶¢&\u0001dô³ð¥½¿f>e2~ó´‚u=ô‚¦Šâž˜/슨ôŒ¡ºQïºqTôŠ°’𤊊Sa@k#(\u0017\u0007>!m=5vN~_#ô‚µB𭃜N\u0007𗃾蘒.K4ð¥s𦓩zT𪆙vv7/\u0019\"#1*'q| 𦘋\u0012R`\u0006O>Pð¥»Ga \rQ\u0011sôˆš¨nh+q𤵗 N\u001d\u0018\u001eô—\\;〔\u000bMyY\\w\u0017+8bJ&2?Dô…‚Y󶋞#x𧪖6AôŽ‚¢85着𬰯\u0015𣥻ChhåšœVJ^R\u0018'F𧉳[\u0006\n:%ô‰¡¶~}\u001bEAS@Wn5@ô‹²­&a2ô‰­¹`ðœŠâ„˜5\u0018^\u00045W\u0019\u001a\u0011R\u0011D\u001eL2m5XofV?Yð¦€\u0001\u0013ó¸·ˆg\u0005q;󴙜㇂oP\u0016,\u0011ô…½©ó²·°>ã§á²¥ð¨™•ô€±ºW&lHVm󶈣B\u000bl@%󽬚U#\u0000ôŠ¼…$XbM%qó¿Œ\u001c0zô‰·”\u0003r%.ô†‚›ç¹•\u0016Z0\u000cB+_@\u0011tcOð­–˜ô‹ƒ®|𭧮&~ð¡·N낈v𥷉𡩴`궭1ó·«“ôŽ¥£ôŠ¾®â˜‰vf\u0016EJbó°’Ÿ\u0013\nG}\u001a#_$Dô„ž­ó¹Ž¼/\u001e\u000bf\u0011𧇑\u0013 \u000b8'GP\rb\u0014ë±€ó°“–ç”±?r 𡋧ôŒ­†\nð§d\u0008VStN\"\u0016[Gز\u001b\u001f󶼈g𡉀_\u0001Y1?\u001fduô‚©Šð’“‡ó³¿=ô†‘–})Jô…¸ª$X]Y\u0013ôƒˆ„h\u0011H0\u0002ð”Žóº¯•äµ§\u0008⳯C\u0014\u0007ô†‘—󺇰\u001e|oTc!eUó²–Pr𡶮\u0015\u001e4ð­š„DG𡘱x󳂗FG0P윪/F\u001e\u0002\u0003𩼃*\u0017\u0016ä–§Gw]ó·½ô‡´°ô…‹Žôƒ†m}tg6𨣶\u000f*𪒮\\Wi\u0019Oa'\u001côƒ¢ˆó´’€shôŽ¾ð¢»lE+&\ng`óµ–‘3kY\u001dY=\u00114`ôƒ™žl\u001eô‰ŠŸ!r交\u0000wð˜Žô‡„?\u0017\u0002ôˆ—‰I\u0016\ngWcrN_)7f+#\u001bð««²ô†µ¶jt\u0010ð‘£’hAx\\-{𖥋^4rf\u0010ð®‹6MJb__벶`n𑌫4oZA꧄𖥵H.-Y {\u0007\u001c\u001fu[4@6󿗮𗂢අ\u0016\u0012/|,]\u0015󷋆E!\u0010\rZ𫤓Q𬎗ôŠŠ8.X\u000cwZ\u0014-{KWitíŠ >ðª¶L_Y5\u001d\u0016aEé²–0Bf'M\u0014󷷢詸\u001bó¶§2tF)ôŠª™&xf9|>K\u0019%z\u0007󷶯\u000cHð ´\u001e\u0008ôƒ¤˜\u000bJCmoJ\t$\r鶩S0𠶄k\u0017󳆅zó¹½®\u00087îž›\u0012ACxôŠ²p늷-e🚇\u001d&? \u0001St}S'óº·’qmôŠ­”푬\u0019\\c\u001b齌;󵑇积Y8mz3\u0002ROJbY\\ᤖô„­–w𤇂 in\u000f\u0015nmð®´rô„’„\"󵓦%*Uð«\u0011CôŽ¢h\u0007\u0011MIQWC𪄢{%\u000fð‚¿í“°<_$\\d=\u0001)-4~x𫮚Ov\u0000\u0014𭘪pô€»å¹¡có³¾¾,å´‘o\u000fóº²N\u0002󽖞𦳋B'd󵯤`ôƒ†”W%ô„¥®ôŽˆŒ@hx\u0016.\u001d𨈨cJâ«›Mó¼¢ó°†š,I\u0012\u001d\u0006!䡷𨂈ȴ\u0004\u0014:\t\u0014\u000b\\ôŽº¥]ô†½ð¡¾¢r\u0018𥺲%\u0002\u000fp𗤺rຮR\u001c3X\u000csW岧]L𥽮1`J\u001b\u000fä©«9gM", "prekeys": [], diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_19.json b/libs/wire-api/test/golden/testObject_NewClient_user_19.json index 05bdb42d431..ac0b9aa2649 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_19.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_19.json @@ -5,6 +5,7 @@ "id": 65535, "key": "" }, + "mls_public_keys": {}, "model": "\u0018ó»¡Žg", "password": "#ôµˆ~\nkô‚´§J𮃺POZ\u000c\u001a\u0006JJ\u001e$âµ¢\u0004ê’‰lL[\u001c]h,Xf󾞮\u0018d𨒥\u001fﺳô‡ žt\u0003]裞𥻋e鼖e\u00130q겜ð®‹[WSGO{\u0008=Hsô‰¼ ;6\u001aä©œ\"\u000e𠸩&ô¤ƒ]Xmx𣂦\u0001iâ“¢j\u000b\u0016\u0015󾀇.\u000c\u0017\u000e8E휳ió¿€®P`oó¼¿ið¡©¿\u001a\u001b𑘃ô­•}8𣃘󴡈颃𛲈\u001b󸇜,v󺔇\t𩜋^\n𮥅ó¼‚yOX@F \u000417uqî–šG⤦\u000f\u001d9?8+O(󼨷v4,[k ã¯--Qk𦹩\u0003A\u0019𨑢9𡸰󰗵@䦲ôŒ”Œð£¾’ð »«Q\u001f4;\u0010\r\u000c0𖡇fz\u0007DCð¦­3ó¿“|Sn&i\u0004jô€«®ðŸš¿KS7𣫙\u000cAx\n\u0006\u000bxôž’5ðŸ§MWIp$mô‚½—ð‰m󲇷ð­¹ð€§mä¤\u0014\u000e9SDb-b\u0007=F𓆓掿\u00147/{ôƒ©²d#X9\u0016걉넋/{=󹇽Z\u0010𨅔xhé” A\t\u0002J\u0007ó¹°•9Bk_ôŠ£·H^𨥕g[38&\u0006𤮊ôˆœ74𗤙Dwdu~rð‘¬ó·™—ô… œ&ð­ƒZ^\rl|\u000c\u0017A\u0002~2r>\u0004sM𩻈iX~`\u0006zK\u0004WôŒ–󻇴ezCh6O\u000e%\u001a\u001a=𤧭:ô…”·DQ\u0000c`\u000f\u0007DmG)M)Th𮤋\u0000[u\u0017\u0016\"?\u001903ð­¹®xi{h\u000cy~??)w\u0000y-IaG\u000b\u0000ì°œð¦ŸJó¿°‚g(\n\u0011\u000e\t+p\u0005Slð“Šô‡¡‰ó¸©¬\"N=Q:/⌯[ð§½\r\u0015𤄛B釔@kA\u001fwi\u0015,p=鸫l6ðŒŽ'𠘟ð —t\u001eWq󰤻W\u001a\u0002\u0003\rð’‹›ð¦¿C!j\u0010\u000f>\\W󵔇\u0003ð¥´q+\"⩘|oó±­Œ\u001e󻻛𧭨$ó¼‰ê®¡B_\u0000ô†¹ªDV\u0013ð«‚W?rH5Xi\u000b\u000e\u0006𭹇N-𨊯PQ2`𪟩r\u0017gaÂœ\u001chT#ay}^\u001bPô‰¼Œyp{\u0012#^/5X-D\u0000\u0006𩽋\u001e󼓞\"}Z`x{ô¬€TT_ Z0亇󽴗皕k\u0013Q.NT2/ô‹šŒO\u0001P^G\u0016î´­/%d.ðª…󰱑P\u000c2iYS@\t\t\u0014.oâ´˜'?-0a]ð—°‰)f9w4ð¡‘—X𬫋𬖸\u0011\u0007=ô‰™¬T𢵇g\u001eb7\u001d@ô…ŽµV/,󻔤K𮓕\u0013MP\n?Wó¶ˆ5/zó¿£”\u0006ï–¦uð—¿»nKB\u001f赚\u0013U\u0015h\u001fr#ô‡’—\"ô…°½-𢵿(K𬴯\u00166𩯉ôŒ‡¾+bô‰­»sô’¨\u0002kô‡Š”𤢸ôš~Rl\u001a\u001e\u000c\"X3𧻻\u0016OQRpJó½¯\u0001WvE~ë±€I5󲠬壕j\u000b=PKð¡‚™%#\u0017\u000cN2䪰ð¬“vz9ó·‹¼nY\u0010\u0016=F\u0002\u0004൘9Wã’’\u0012^C)ZX\u0017JN2Póµ…\u000f|\\󵉆h}P1Vô²©7\u001b\u000b\u0010lð¬²\u001cz\u0019\u0012ôŠ¾˜IMó² \u0001/NBiM𩥸~;1xì€Md\u00191췷㣤e#\"\u001fð—„eV?i{ô®’ô¨„󶩵ð¥®&\u001c\u0016൬𥚽#", "prekeys": [ diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_2.json b/libs/wire-api/test/golden/testObject_NewClient_user_2.json index 422365d9460..e073c4c47e9 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_2.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_2.json @@ -7,6 +7,7 @@ "id": 65535, "key": "\u0011𢮨㙢" }, + "mls_public_keys": {}, "model": "om", "password": "Iô„‡ó³ƒ›oIC\u0008yó¿—„1ã•›&w>S~z豿\u0007{2Dj\u000b|Z\"\u000cô‚¼„*[ï½Vô‰›kS𣚇AôŽ¼ô¹(2\u0010ë±\u000ei\u000f(ó¼…𩌬f?q\u000e5𣱽dô„¾˜^nIôš¯_?󸵊Hð„»\u001af󳈙\n󵈿x\u0006dZô“¹^N\u000ca\u0016\u001ab=ô¡·SP😄aTd\u0019ð­œ\u0013\u0006\u0017!ó· ð¢¬¯o{uoN\u0018qL\u0015\u001bc=\u000b@o2ó¾µ²\u0004𢲖\u001fô‡ ¦5v\u0002\u001d_k,\u0013mAV>$󻎕ôƒ†œ\u001eôŠ„³\n⌔-ea}G`r? óµ‡\u0001\u001fð š•9\u0008rl𥶽}uð¢‡ô‡·šôƒ—¸@M6M𥷣𘃸𨺤|E5Udô€¨tLjQ󹭵ᩎ\u001e\u000b\u0011jE\u0006'~f\u000fRó¶°\u0015d}}ô‚±¸qó»¹–\u0011𤺆9𧋕\u001e𘣰\u0003𭦜\r\u001c\u001fè¿ŒãŸ\u0015/\u001d掶ôŠ“¾îº…\u0000(:ô™©n#m9x ô‡ð¬²¸}ô€¿Ží“–\u001d󲊹\u0008`ô‰¡¹G#T\u0012-8\u0015䞆𠷿\tp/!\u00024C\u001a'DP'.\u0007ôŠ8<9\u0016\u0015Eqð©’Ep]\u0007jZ%󺘵áŠO製>\u0018\u0006w*f<ô‡Ÿ\u000ejzpjY\u001f\u001a䪎\u0011\u0011\u0006|\u000eôƒ¸´;𡇑F!f七b%ô€‚Št9\u0012\u000cð¤’X! ð ¡¿C\u001e󻎮ð§¨C!冻H(/\u001dV)e\u00162\u0000#H$BAJy\u0017𧭞X𡜶\u001c\u001a~\u000c\u001b;\n<\u001df~{\u0008_", "prekeys": [], diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_20.json b/libs/wire-api/test/golden/testObject_NewClient_user_20.json index 3d583210024..f37d98c1963 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_20.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_20.json @@ -5,6 +5,9 @@ "id": 65535, "key": "<" }, + "mls_public_keys": { + "ed25519": "R1krdDFFUXUwWnNtMHIvenJtNnp6OVVwalBjQVB5VDVpOEwxaWFZM3lwTT0=" + }, "prekeys": [ { "id": 0, diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_3.json b/libs/wire-api/test/golden/testObject_NewClient_user_3.json index 84691caf382..aef1fcf3480 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_3.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_3.json @@ -6,6 +6,7 @@ "id": 65535, "key": "v7" }, + "mls_public_keys": {}, "model": "󸊺\u0013ð ®™", "password": "ô€»óº´®`7ô‚§³óµ·µô·µml𢘡\"\u0001\u000e\u0011*\u0013Q\u001e㧕.I\u000cô€“­\u0010XHyy𥻱%\u0015\u0002ygG`H;ð—’™\u0001}Uð«—šð¡ \u0004cXó½¾¼Xè—™!|v@\u000e\u0018\u000cpó³–“ðŸ‰dôšše𡂶\u000br`c󵸼󹼈𢴓 \u0012îh\u0005OS\u001c_X$𬗹\u001d𒆆gô„—¤x%\u0008ó·¹´YzZô…Œ½2`^h^RôŒ¹¯ï”©bô‹—±BOyjó¾!|E î’³\u001f!Bu`5I$\u0019,Jt\u00137𓈣L)𑌻Z\u0016/pl𪌢Xb\u0013\u0007\u0002ôŠ›£\u000e!ô‡¢\u001a#𩧈G6𧤕m\u0018ó»•“RI𭷿󰄦_󳗻󴉤W^\u0006jô…‘†ðª€‹x\u0019󽿠5BV\u0008VU󴤣𢶛\rPtôŽ¯ªèº›Cð“„\u000c𦯭F>\u000bkqOC\n-_q\u0010c", "prekeys": [], diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_4.json b/libs/wire-api/test/golden/testObject_NewClient_user_4.json index b02f1c71ac0..520d926108e 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_4.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_4.json @@ -5,6 +5,7 @@ "id": 65535, "key": "i" }, + "mls_public_keys": {}, "password": "󽪩\u0019𮀹SyAf\u001e󸀻Xï±~RZ}ôƒš°6}󾈳keó½²°v$Byð¡ž”ôƒ«„,\u0002yð¡¹~ôŒ›¤9srô…¶‘\r_ô…†¾\u0006ì‘”ô‡™¨óµª’7ôŒ°†lô‡™¯ì¤|󶣒𧸜\nô€¾\u0017)uôŒ³ˆô§‚%縉\u0017󲇜P)eE:sRôˆ¯‰\u0001@󽨅y\u001f冹v𨚀󸓽\u0011[9b|\u0003HNQ|=ó»¡3Q7麯ô•©\u0010noh9)J\u001cð­¥»Pí’¥cN\u000eVê¼ð§‡»O4S\u000e\u001dR\u0001\u001fBn\"D𦆂3c]Npy𗈟ð‘½ðª¤‡k𦡛<\u0017ô‚¯H\tôƒŠ“𨑑\u0011o\u000c/w\u0019>%ð‘™\u0001𣘀,ð– Ži\u000bZ[Ië‰\u001aN1mZ狻綀Hô‚£‹Imv#JR/;\u0006K\u0010å¦âŒ{ZR\u000796G\u0005y𥊭+ô†ºžðƒ®=!\u0019\u000b\u0014\u001dóº·ó¾•–<ô€£™ð“Ž²ð­¨“𣬕$󺓺\u0018-;\u00034v\u001eôˆ²¾\rô†ª©qôˆ—’4_C𧌒\u0015U.PD\u0014NYI\u0010v\u0008\r\u001cxH󷞦%mxCó¿˜\u001aô€…¢\u0018#cEôˆ¬¨;j`ð¡–‰I[ABy󶤸f0𡯯t󷪧\u000f𥟾?\u0016~(Z\u000b\u0013E!\u0004󻉉[TWó²‡\u001fn\u000eå•ž+&\u0002𬫨\u001c+ð ‹¾\u001cv\u001eô…‹‘D󽌔$<@\u0017\u000cCEb𥌼&óº”e\u0011\u0018gN\u0012]\u001e\\A:*\u0006tV\u0007jD3ôŽ¿ƒzBA4\u00195\u001a\u0016󲟘\u0018:%óµ´Ÿì•\u000e𬽔{\u000bT_~0\u001d\u0012\u0004V0ô„˜£K7xI/ó¼¢ í‹\u0002ô‚¹¢\u0019𠎩⃥.m𒔤\u0010ó¹£aê°š|\u001a᧷R\u001br~𮬙\u0004\u0010Frvbð›‰\u0003z\u0008I\\o+'Q\u0004l :?ð‘šr\u001f𣳚ô‰“¨o+𩘵Jn󾨫.*𧩣_7:󾟶\u00153T\u0005\tð¥½b/q\u0016@OmAð—®’xZ춦\u0018\u001eh\u001fg\u0007E_K\u000f{ô€½³ô†Œ&ô¢²N )𦞈zB\u0017\u000fó¿¿´Fôˆ•‘\"\u0006Bð‘™\u0005𬨫S\u001dX檊ô‚º”yHAi𦟠\u0013å©…F8A/e.8$𘋌\u0010Ou-Mw󶔞4zb𨘊BCô‡±ô…”¯\u0002Y%Bg\u001e37v~2\u001b\u00188\u0018\u0011\u001cz#q?ADm4\u000bBK&Z\u0018Kô¬œf!*/\u0006\u0012iVTs𢢠ô‚¤Žô‰¾Šð¥«´OQ𥦤-ë“™[J󾗉䜺󿪵H1Kôƒ–\u001fð£ˆô„€­1\u000fk+EB?󳥵󻨦ô‰¯y,\u0018\u0000\u001ffT\u0010{\u0015ó¼…»\u0016討E,\u0001y襔?󻲺$ll%ó¿š—\u0001`)ð’‹ô‡•®u𥊄ôŽ§ð¤“¨A\u0016pDoz]L䶢/ó¸·ô„¥Œ&f`AX=RA\u000bô“•%,Oô€•Ž\u0015Bð‘®@\u0019\u0005ó¿«„ô‡¿€-󳧟T󳮦l6\u0015'𪈠^ô“’('2AP&?\u001f\u0010\u000f0fßœ\u001b$\u0012籺5v_ô€°©\u0005𥗖> [3h)?윷󽯊𡠑mó°“cBD߫󸒎󷪿\u001a%X\u0000:Pib\\sષt𦉱󵡼{Sz+O\u001d\u0014\u0010\u0013.\u0019;2S\u000fFôŠ°\u0014ftF}3G\u0000$wR]\u001fYpY\u000c\u000e𢢘;\u0003e渫陞$\u0013(\u000fw\u0017Bp🗨\u0005\u0010ð¥°ó¾ƒ\u000b:ì ½V󰌖.\u0001N<î°œ$\tj3쬖\u001f䢖E,$\u0007\n\u0016\u001a%󹩮P󰉆,ó¼…k\u0000ð ­ºEð”›\u0005'\u0018wrhj?9ôŠ´ó¸˜¡\u0018#ð¬î•…\u0005\u0013I\u000c\"\u0005탩^𣓭}T𨈩", "prekeys": [ { diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_6.json b/libs/wire-api/test/golden/testObject_NewClient_user_6.json index 903b1efd9dc..8cc1aea0e1e 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_6.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_6.json @@ -5,6 +5,7 @@ "id": 65535, "key": "ô —" }, + "mls_public_keys": {}, "prekeys": [], "type": "temporary" } diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_7.json b/libs/wire-api/test/golden/testObject_NewClient_user_7.json index d7c5f36a295..5506bf96d84 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_7.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_7.json @@ -6,6 +6,7 @@ "id": 65535, "key": "%V[" }, + "mls_public_keys": {}, "model": "𤳘", "password": "𢂵l󱜣\u001bQ\u00108SH\u000c\u001d\u0017-\u00165\u001cPyôƒ‹·r/=󰻀N3󼶋&N㯔wð¢‚X/Q*𤳊㖰Xó´Ÿ+9=U󺀔\u0001\u0005\u0002}i\u0008ô‹»Žô…Š¦\u001a\u0005Z(𩃨󶡬o#Xm\u0018M󹺦3ðªˆ^Pná¹crQc󰳃\u000ep\tr\\ó°°‰t}\u0004`ð«’Mð‹±\"ቬS\u000f󽎥\u000fQAôŽŠ\u001ebó¹ ð‘µ¤0󱭼𰡠+~󲆽\u001a8,b𧪯gô‡€¢%\u0018â«·\u001a\u0005)e\u0001󷼊\u000c9ô‹Š!yWó¼ˆ${ô‚®€}ô„Ÿô‡Žð£–½+q{\u000c𨆶󻽛|g\u0006\u0001/`L~󿨫ôˆ¹…𦦜\u0003!\u00038\nóº’›ó°’”p\u001e&\u001eè‚´ô‹¦½+ô€¹¯uft ó½‰Dô¤¬ð§‰¸'\u0001󴈑\u0015V\u0016n8x熓\u0010鲶\u0018P~l$fáº\u0011\u0005ó´‘∙\u001eLx<@\u0012\u0019󺸸H𬘅sô…®’18𥎀𡫜\u0017 \u0011I\u000c=\u0018=[Vó¿—¦'\u0001뗣󳉃G`6'\u001a!*;\u0019Jô†¦‡\u0002y\u0011\\\"!\u0007E澌ð£²Bó¼‚ð«Ÿ´AL\u001e!\u0005]ô€žµ?_t/𡤿aﬖO G\u001eô…ô‰ž§{=\u0012<<󿘣[o󹕞Lz\u0007\u0006ôŽ¬¸\n>.\u0005I|hlT;h\u001f\u000eI\u0015\u000e\u0016ô‚±ó¸Š¬\u0012󽛑\u001c\\P\u001cð©´·\u000b{Y\u0010 ' q!ó¿¦g󸂑T󸕌^Lg:ó¾´¬)~𬺔&R{ô‡Š°ð­½ˆX\u0017ô‹­ó·‹½zᎥs.8n\nî \u0012|\u0016RP&\u0008\u00037\u0005\u0007Uó´¼µ{\u00100%v5 䚀\u000eð—‚…EU\u001eà´8\u001a/ኹW['`Jô„¥¤uv𧳄ôƒ–‰\u0013\u0010\u0019ôˆ¢†+P7ð§†P_Xð˜¢ó½€º2ô‚»½54ð­„„xBô™g𬂟\"\u0011\u0001rC)0^\u0001rQ\u000cpdë—¦ôŽ„œî¦‹äž¿B\u0015𧨉ꔩs󵘠6gi6m7𣖮\u0004\u0005L\u000c𘚳f?\u00180$𣟜\u001b]>CV\nVPb:^e6ᵨ\u0005ô€‰ô‹½$!Lô€¼´vv\u000e/:\u001a\"ô„½o𩢿\u000ce{:󴬧ᩣ\u0014\u000es\u0007z~ï‘%H.xF󾘉𨡤h\u0017ó°¤°\u001bôƒ‚½E\u001b𩤺7\u0001\u000b8ð—­¥:PS: ôŠ©¿:󽸇6c0𨻢ô‰ª®$ôŸ{)|\u001dô½K\u000c\u001ea#GKó¿¥°\u0003m\u0002br@ᥑ\r'bv\u0008󳑮\u0016󸧑dv󴑦\"\u0015𩹊\u001bó¼¥¥\u0010>ð‘Š¡\u0008𨅪â¿S/[ôŠ»¼];⟶A$\n5Sð¦¾_y𮃩\u001bP]e\u0018𬊥ôˆ™ð§ˆ¡-\u001dô‡™ƒ\u001dôŒˆó¿·¾&P$\u001dTð†TLX[셎🧳)\u0017P;닧\u000emô»¾Dôƒ‘„ó¾´‹>EI\u0000\u0015I\u0018ô‚œ®\t[AtgxM\u0016󽟕^ôŒ¶€\u0008^V𣅒+l)n󻘀\u0000\"ôªµy\u0007.\u0010", "prekeys": [ diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_8.json b/libs/wire-api/test/golden/testObject_NewClient_user_8.json index 8d6c83d505d..4aa14c9fb9b 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_8.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_8.json @@ -6,6 +6,7 @@ "id": 65535, "key": "\u0013,\u001f" }, + "mls_public_keys": {}, "model": "", "password": "\u0008\u001eôˆ¨‡F\u000b\"p𭈱𧠩(\u0018\u0002\u001a{1.\u0002Sh1\u0004𢛕Jð¨œð¦¼§ì²‰?䆢\u0014hg^hô‚“™&\u0001\u0007^â¤ôŽ³°Vð’“£\r~\nHD\"\u0017TC#𥓇\u0011󻘚\u000câ—ˆD\u000f\u0006X\u0018?T\u0002Qa\u000b:}\u0006ó¾®´\u001cv\u0005\u0004;F{ð¼°ð©‡£P庶SNm9yl\u0006ó½µ½zm\r\\ym\u001d\u0000VYm&H\u001b*t𬚊pó±—ˆSb땶󾎌od𪰷3Lî—£ó¿¹›\u000e$3ô‡œº\u001e\r㉿\u000cy𪙪B]\u0007j\u0013g:\u001d𤀪7\u001b\u00001y79R\u0003ðž ¨\u0015o󻣥\u001d:C\u0004&3g\u0003PonO#u\u0014A5=z#7>+R𬚰蓩Qk1ꛨ\u001cF\u00112MYó¿“ô¸„oqk\t/\u000cWI+1!]}r\u0013\u001b\u0015K\u0011*M\u0002ô…¤«\u0001\u0004qe\u0018𠱩1]bj/t🀼,\u0011jk\u0019ô‹³Š\u0014\u001f/󸭘ਂ\u000e3ð¹k\u0013ô‰¿³\u001c~\u00043ó¸’¯{뛶Hf(x\u000f/Qô\nzgdyTô¦¢4CEôƒ‚\u001c\u0008\u000c=v\u0019ô¡+C𢷸ʂfó·…œx8b\u0010/\u0005\u001e\rQ;Ec𬚻󰙊\u0016QM.)\u0019k\u0004^\"&~EQmQ[ôŽ¹¡GR\u0016z,ô¢kdR\u001a\u0011M\u0004!Nfn)󲲆,oFb~\u0017\u0011#\u0010\\%\u001c\u0004C󳚊󵘩₊.ð©·ºGô°¯,{\u0004R𪡛=ôŽ¨œð¡ˆ“ô€’°g\u0010𢹉\\r\u0010F~V襳례\u0006P⧎&hC#R#z\u0010_\u0012ô‡²¼ô‰¹™dKð¦¿å™Š2:M󴹯t\u0014ð–­\\ó³:\u000b\u0005X\\𪬲,\"󸫙:渕l\u001c𣠠Xôƒƒµhv,V$'\u00040zk[", "prekeys": [], diff --git a/libs/wire-api/test/golden/testObject_NewClient_user_9.json b/libs/wire-api/test/golden/testObject_NewClient_user_9.json index ef434ea610f..3e5d5beb50d 100644 --- a/libs/wire-api/test/golden/testObject_NewClient_user_9.json +++ b/libs/wire-api/test/golden/testObject_NewClient_user_9.json @@ -5,6 +5,7 @@ "id": 65535, "key": "" }, + "mls_public_keys": {}, "model": "m{", "prekeys": [ { diff --git a/libs/wire-api/test/golden/testObject_NewConv_user_1.json b/libs/wire-api/test/golden/testObject_NewConv_user_1.json index f2dc14a6af9..62b6d2cd506 100644 --- a/libs/wire-api/test/golden/testObject_NewConv_user_1.json +++ b/libs/wire-api/test/golden/testObject_NewConv_user_1.json @@ -10,6 +10,7 @@ ], "conversation_role": "8tp2gs7b6", "message_timer": 3320987366258987, + "protocol": "proteus", "qualified_users": [], "receipt_mode": 1, "team": { diff --git a/libs/wire-api/test/golden/testObject_NewConv_user_3.json b/libs/wire-api/test/golden/testObject_NewConv_user_3.json index 62f3d801c26..f4db13e892d 100644 --- a/libs/wire-api/test/golden/testObject_NewConv_user_3.json +++ b/libs/wire-api/test/golden/testObject_NewConv_user_3.json @@ -10,6 +10,7 @@ "guest" ], "conversation_role": "y3otpiwu615lvvccxsq0315jj75jquw01flhtuf49t6mzfurvwe3_sh51f4s257e2x47zo85rif_xyiyfldpan3g4r6zr35rbwnzm0k", + "protocol": "mls", "qualified_users": [], "users": [] } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_10.json b/libs/wire-api/test/golden/testObject_NewService_provider_10.json index 7164b7e67eb..88656b0eb3c 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_10.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_10.json @@ -1,16 +1,7 @@ { "assets": [ { - "key": "", - "size": "complete", - "type": "image" - }, - { - "key": "", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_11.json b/libs/wire-api/test/golden/testObject_NewService_provider_11.json index b819e5500ac..2a6646e7857 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_11.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_11.json @@ -1,16 +1,7 @@ { "assets": [ { - "key": "", - "type": "image" - }, - { - "key": "", - "size": "preview", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_12.json b/libs/wire-api/test/golden/testObject_NewService_provider_12.json index 9b31888d423..77d144457f4 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_12.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_12.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_13.json b/libs/wire-api/test/golden/testObject_NewService_provider_13.json index 0c81a7abb80..1ecdd4e4744 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_13.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_13.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "g", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_14.json b/libs/wire-api/test/golden/testObject_NewService_provider_14.json index 842bfede2f6..54a58187f36 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_14.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_14.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_15.json b/libs/wire-api/test/golden/testObject_NewService_provider_15.json index 8f4761c8d7f..fc9655b8ca4 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_15.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_15.json @@ -1,12 +1,7 @@ { "assets": [ { - "key": "", - "size": "complete", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_16.json b/libs/wire-api/test/golden/testObject_NewService_provider_16.json index 1edf7e274d3..b1ae02f07cb 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_16.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_16.json @@ -1,21 +1,21 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_18.json b/libs/wire-api/test/golden/testObject_NewService_provider_18.json index 03ec6a44e2c..3b3d9dbc070 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_18.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_18.json @@ -1,15 +1,7 @@ { "assets": [ { - "key": "", - "type": "image" - }, - { - "key": "", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_19.json b/libs/wire-api/test/golden/testObject_NewService_provider_19.json index 05a1aab2612..710d35140c6 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_19.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_19.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_20.json b/libs/wire-api/test/golden/testObject_NewService_provider_20.json index b01417adb15..7305469136c 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_20.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_20.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_4.json b/libs/wire-api/test/golden/testObject_NewService_provider_4.json index 5ab941b4e13..a992cbef58a 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_4.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_4.json @@ -1,17 +1,7 @@ { "assets": [ { - "key": "", - "size": "complete", - "type": "image" - }, - { - "key": "", - "size": "preview", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_5.json b/libs/wire-api/test/golden/testObject_NewService_provider_5.json index 38ea4547e38..c3f5c2c30bc 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_5.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_5.json @@ -1,31 +1,31 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_6.json b/libs/wire-api/test/golden/testObject_NewService_provider_6.json index 3371cc45021..7ed1a9376b2 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_6.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_6.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_7.json b/libs/wire-api/test/golden/testObject_NewService_provider_7.json index 7b607c78134..784130b735d 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_7.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_7.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "\u0006", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewService_provider_8.json b/libs/wire-api/test/golden/testObject_NewService_provider_8.json index ad7c718ad30..dc5ccf6a385 100644 --- a/libs/wire-api/test/golden/testObject_NewService_provider_8.json +++ b/libs/wire-api/test/golden/testObject_NewService_provider_8.json @@ -1,12 +1,7 @@ { "assets": [ { - "key": "", - "size": "preview", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewUserPublic_user_1.json b/libs/wire-api/test/golden/testObject_NewUserPublic_user_1.json index 536556b965d..f4c138be250 100644 --- a/libs/wire-api/test/golden/testObject_NewUserPublic_user_1.json +++ b/libs/wire-api/test/golden/testObject_NewUserPublic_user_1.json @@ -2,16 +2,16 @@ "accent_id": 39125, "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "(󼊊\u001bpó³¢¼u]'ô…„»", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "ô¿f", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_NewUser_user_1.json b/libs/wire-api/test/golden/testObject_NewUser_user_1.json index 6fb028d3955..975b72224b9 100644 --- a/libs/wire-api/test/golden/testObject_NewUser_user_1.json +++ b/libs/wire-api/test/golden/testObject_NewUser_user_1.json @@ -2,17 +2,17 @@ "accent_id": -7404, "assets": [ { - "key": "á¸5\u0014󸾲𗓰`\u0007\u001aG{?", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "something", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "KE", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_NewUser_user_7.json b/libs/wire-api/test/golden/testObject_NewUser_user_7.json index a778929fa94..291e71c640d 100644 --- a/libs/wire-api/test/golden/testObject_NewUser_user_7.json +++ b/libs/wire-api/test/golden/testObject_NewUser_user_7.json @@ -5,7 +5,7 @@ "phone": "+12345678", "team": { "currency": "XUA", - "icon": "Coq쳋\u000b𬟀", + "icon": "default", "icon_key": "\u0006cð¥±L ,", "name": "\u000ce\u0005ó·€°zm" } diff --git a/libs/wire-api/test/golden/testObject_ServiceProfilePage_provider_11.json b/libs/wire-api/test/golden/testObject_ServiceProfilePage_provider_11.json index 5de6225d157..7ab7a1a8984 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfilePage_provider_11.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfilePage_provider_11.json @@ -4,7 +4,7 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_1.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_1.json index f489283e9eb..163fe345386 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_1.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_1.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "\u001b", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_10.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_10.json index d7a79140493..80a10f49374 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_10.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_10.json @@ -1,12 +1,7 @@ { "assets": [ { - "key": "", - "size": "preview", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_13.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_13.json index 7d4a6540b94..79478060368 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_13.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_13.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "B", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_15.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_15.json index 40320f66c4e..0c42a0a0b79 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_15.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_15.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "*", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_16.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_16.json index 0939bdefb07..3c3c435f0f5 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_16.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_16.json @@ -1,12 +1,7 @@ { "assets": [ { - "key": "", - "size": "preview", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_17.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_17.json index add08735159..62ecef784b4 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_17.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_17.json @@ -1,12 +1,7 @@ { "assets": [ { - "key": "", - "size": "complete", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_2.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_2.json index c4f71c210a3..eccf6c18d40 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_2.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_2.json @@ -1,22 +1,22 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_3.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_3.json index 5aea13d2406..bf3d110ee57 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_3.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_3.json @@ -1,11 +1,7 @@ { "assets": [ { - "key": "", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_4.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_4.json index 20897d07227..62ec6cb9e72 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_4.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_4.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "1", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_6.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_6.json index 06d9b7c11d5..2a9ce8c82b8 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_6.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_6.json @@ -1,25 +1,25 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_9.json b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_9.json index ccbb904285c..282f807626b 100644 --- a/libs/wire-api/test/golden/testObject_ServiceProfile_provider_9.json +++ b/libs/wire-api/test/golden/testObject_ServiceProfile_provider_9.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "\u0011", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" } ], diff --git a/libs/wire-api/test/golden/testObject_Service_provider_11.json b/libs/wire-api/test/golden/testObject_Service_provider_11.json index a6f0e648e72..24237ec49e1 100644 --- a/libs/wire-api/test/golden/testObject_Service_provider_11.json +++ b/libs/wire-api/test/golden/testObject_Service_provider_11.json @@ -1,12 +1,7 @@ { "assets": [ { - "key": "", - "size": "complete", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_Service_provider_12.json b/libs/wire-api/test/golden/testObject_Service_provider_12.json index 4de5b9715ee..275a1c0f8c9 100644 --- a/libs/wire-api/test/golden/testObject_Service_provider_12.json +++ b/libs/wire-api/test/golden/testObject_Service_provider_12.json @@ -1,17 +1,17 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_Service_provider_13.json b/libs/wire-api/test/golden/testObject_Service_provider_13.json index 7259671be23..1662fda8125 100644 --- a/libs/wire-api/test/golden/testObject_Service_provider_13.json +++ b/libs/wire-api/test/golden/testObject_Service_provider_13.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_Service_provider_14.json b/libs/wire-api/test/golden/testObject_Service_provider_14.json index d3e8f5e933e..533ab463127 100644 --- a/libs/wire-api/test/golden/testObject_Service_provider_14.json +++ b/libs/wire-api/test/golden/testObject_Service_provider_14.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "A", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_Service_provider_15.json b/libs/wire-api/test/golden/testObject_Service_provider_15.json index 113b41b921a..11b50f74dd0 100644 --- a/libs/wire-api/test/golden/testObject_Service_provider_15.json +++ b/libs/wire-api/test/golden/testObject_Service_provider_15.json @@ -1,35 +1,35 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_Service_provider_16.json b/libs/wire-api/test/golden/testObject_Service_provider_16.json index 8013b050279..2968134faac 100644 --- a/libs/wire-api/test/golden/testObject_Service_provider_16.json +++ b/libs/wire-api/test/golden/testObject_Service_provider_16.json @@ -1,11 +1,7 @@ { "assets": [ { - "key": "", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_Service_provider_2.json b/libs/wire-api/test/golden/testObject_Service_provider_2.json index a129126c8eb..8463539f0e5 100644 --- a/libs/wire-api/test/golden/testObject_Service_provider_2.json +++ b/libs/wire-api/test/golden/testObject_Service_provider_2.json @@ -1,12 +1,7 @@ { "assets": [ { - "key": "", - "size": "complete", - "type": "image" - }, - { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_Service_provider_4.json b/libs/wire-api/test/golden/testObject_Service_provider_4.json index b227bc0a2ac..8e3c74622d8 100644 --- a/libs/wire-api/test/golden/testObject_Service_provider_4.json +++ b/libs/wire-api/test/golden/testObject_Service_provider_4.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_Service_provider_6.json b/libs/wire-api/test/golden/testObject_Service_provider_6.json index ef2d363e957..ea1d4c176e0 100644 --- a/libs/wire-api/test/golden/testObject_Service_provider_6.json +++ b/libs/wire-api/test/golden/testObject_Service_provider_6.json @@ -1,26 +1,26 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "complete", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" }, { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_Service_provider_7.json b/libs/wire-api/test/golden/testObject_Service_provider_7.json index 5c3fdd8cd07..3a434d76102 100644 --- a/libs/wire-api/test/golden/testObject_Service_provider_7.json +++ b/libs/wire-api/test/golden/testObject_Service_provider_7.json @@ -1,7 +1,7 @@ { "assets": [ { - "key": "", + "key": "3-5-5cd81cc4-c643-4e9c-849c-c596a88c27fd", "size": "preview", "type": "image" } diff --git a/libs/wire-api/test/golden/testObject_SndFactorPasswordChallengeAction_user_1 b/libs/wire-api/test/golden/testObject_SndFactorPasswordChallengeAction_user_1 deleted file mode 100644 index d7d7e107861..00000000000 --- a/libs/wire-api/test/golden/testObject_SndFactorPasswordChallengeAction_user_1 +++ /dev/null @@ -1 +0,0 @@ -"generate_scim_token" diff --git a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_1.json b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_1.json index 9e633f8c0a8..f6b3b1facad 100644 --- a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_1.json +++ b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_1.json @@ -1,3 +1,4 @@ { - "password": "i>Lx}$\\\u001e?󼃩k𢮇󺟛`8\u0000Yë±”|\u001b?î‹ŠS|\u00196X-XKïŒð«¬¬(\u0016@]_𪽵󴖌b1\u0008ð”›ó²¹ô„’œô©¥\u001ds𦀼ᦎô†„­Cô…“¹:e9]x𣞇\u0017Pxh짷Gâžh𤕲ð £\u0013\u0014hbY\u000f󿢨3ð ¯B0\u0007쾯ðªµó³‰­\u001f-\u0017Oó°Š“9𪭺%\u001dð—”´W\u001f\u0005+\u0015\u0008\u0018!,h@\u0010R󷿕緙󿪛,ã…‚}.{^ôŠ‰•K*\\󳘭_X|9T\rM~\u0008\u0001KsC=󶬴wô‚‰=\u001c\u001b\u001e\u0000wFM%CXf\r@/NUô¤5v\u000b\u001a2Uô’£K4᱑r𡵑Qô½´[Z\u0014#)\u0016\u0003sR!\u000bt\u0019\u001f󼻡{\u0015z󿽎ôˆ²Žó¾ªˆ|@럭\\𨆦[놆󼯕D];s𑆃c\u0019ô‰©³q\u0000é¥]F};f\u0011mzô‰¼Ž$\t\r&SuI!,\u000b[,\u001acî…«8=ôˆš« \u0013𢌧vð™œJ]\u001b\u0018-\u001dF\u0017\u0014\u0001FD@𡥶ôŒ¾Ÿ\u001a󲲪<\u0014ð¨ˆ\u0012\u0012Uôˆ¤9𢡻MQjmAb+\u001e🢛\u0017\u0005Kô‰„†à«¦v;\nQ鶫𗹅[uDô€¹²{ð®­\u0006O㨖@7\u001e:+:1á¸ðªƒŠMr(èfôŠ›¶ô„¡±S骛b󾇉ô…¦»\u000bF謑󱯛\r\u0008ô‰¶” ô‰Ÿµmâ«E\u0004𡢛𦹂{\u0008=S\u0015=󶖠ḫ⊣\u0011\u0000ó»…¦\u0001h\u0010\u001a󺋼RPt\u000b7ô¶œð¦ºœ\u000bDôª½S󳞗.\u0012}zP뢅E^?泂𧡧S\\SôŒŒ„`\u000e&\tU𛆫🩧𬓖{jjô‹³’\u000f|1\u0001R\u0007\u000c9\u0016,󶧬b\t^󼸰?V\n\u001dz\u001e?=@袽ôŸ—\u00139'\u0008\u00198y\u0003&󾺣ô„‹ž\u0000uôƒ§\u000e,)\u001fîš­b&|h~軯X\u0003D󱉑\\~'\u0004\u001d%\u001c\u0010|\u0003󾌳3s\u0004\u000e\r]|7Jô„…ºJhi镉ᖡ𤗜\u0000\u001a%󰨥\u000e\u0012vjc|m깞VDN4kW󼦖ðƒ¬$\u0019\u0011rå‘£]@ô‰Ž–Kð¦¯ð­£:uóµ…—\u0019G𗇽8qN2;\u0014)X[p\u000c\u00011󻼰󻱀뛸ôˆ“½x.P𨜸l\"s#n0\u0003~\u001f\t𒊨[ô€¦†ó¿ž†ã¨„`ô‰¤¡\u0014ô”¡1L𡊸󲱉bð˜˜\u001b,\u0005#\u0002@A\u000e𫦶\u0003{\u000eæ®­1sX䯌}|EZJ\u001e\u001dxoSe楌l:0g3\u0006" + "password": "i>Lx}$\\\u001e?󼃩k𢮇󺟛`8\u0000Yë±”|\u001b?î‹ŠS|\u00196X-XKïŒð«¬¬(\u0016@]_𪽵󴖌b1\u0008ð”›ó²¹ô„’œô©¥\u001ds𦀼ᦎô†„­Cô…“¹:e9]x𣞇\u0017Pxh짷Gâžh𤕲ð £\u0013\u0014hbY\u000f󿢨3ð ¯B0\u0007쾯ðªµó³‰­\u001f-\u0017Oó°Š“9𪭺%\u001dð—”´W\u001f\u0005+\u0015\u0008\u0018!,h@\u0010R󷿕緙󿪛,ã…‚}.{^ôŠ‰•K*\\󳘭_X|9T\rM~\u0008\u0001KsC=󶬴wô‚‰=\u001c\u001b\u001e\u0000wFM%CXf\r@/NUô¤5v\u000b\u001a2Uô’£K4᱑r𡵑Qô½´[Z\u0014#)\u0016\u0003sR!\u000bt\u0019\u001f󼻡{\u0015z󿽎ôˆ²Žó¾ªˆ|@럭\\𨆦[놆󼯕D];s𑆃c\u0019ô‰©³q\u0000é¥]F};f\u0011mzô‰¼Ž$\t\r&SuI!,\u000b[,\u001acî…«8=ôˆš« \u0013𢌧vð™œJ]\u001b\u0018-\u001dF\u0017\u0014\u0001FD@𡥶ôŒ¾Ÿ\u001a󲲪<\u0014ð¨ˆ\u0012\u0012Uôˆ¤9𢡻MQjmAb+\u001e🢛\u0017\u0005Kô‰„†à«¦v;\nQ鶫𗹅[uDô€¹²{ð®­\u0006O㨖@7\u001e:+:1á¸ðªƒŠMr(èfôŠ›¶ô„¡±S骛b󾇉ô…¦»\u000bF謑󱯛\r\u0008ô‰¶” ô‰Ÿµmâ«E\u0004𡢛𦹂{\u0008=S\u0015=󶖠ḫ⊣\u0011\u0000ó»…¦\u0001h\u0010\u001a󺋼RPt\u000b7ô¶œð¦ºœ\u000bDôª½S󳞗.\u0012}zP뢅E^?泂𧡧S\\SôŒŒ„`\u000e&\tU𛆫🩧𬓖{jjô‹³’\u000f|1\u0001R\u0007\u000c9\u0016,󶧬b\t^󼸰?V\n\u001dz\u001e?=@袽ôŸ—\u00139'\u0008\u00198y\u0003&󾺣ô„‹ž\u0000uôƒ§\u000e,)\u001fîš­b&|h~軯X\u0003D󱉑\\~'\u0004\u001d%\u001c\u0010|\u0003󾌳3s\u0004\u000e\r]|7Jô„…ºJhi镉ᖡ𤗜\u0000\u001a%󰨥\u000e\u0012vjc|m깞VDN4kW󼦖ðƒ¬$\u0019\u0011rå‘£]@ô‰Ž–Kð¦¯ð­£:uóµ…—\u0019G𗇽8qN2;\u0014)X[p\u000c\u00011󻼰󻱀뛸ôˆ“½x.P𨜸l\"s#n0\u0003~\u001f\t𒊨[ô€¦†ó¿ž†ã¨„`ô‰¤¡\u0014ô”¡1L𡊸󲱉bð˜˜\u001b,\u0005#\u0002@A\u000e𫦶\u0003{\u000eæ®­1sX䯌}|EZJ\u001e\u001dxoSe楌l:0g3\u0006", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_16.json b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_16.json index 61d0f8fba9b..742858dff6d 100644 --- a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_16.json +++ b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_16.json @@ -1,3 +1,4 @@ { - "password": "l\u0003c\u0006=1\u001df\u0003x󳉸𤀼\n\u000c036J𗟶6ð«ž®V{}t\u001d?TR\"\u0018)\u00186h:i\u0017Z9\\i\u0012b\u001dó¼£¢6(IvY \u000e:삓𢓰\u0006\u0010\u001cedô†š²\u0004}OK𗙸Hó°¡²W9_u󽦃`𪪆ô‚±ƒó²·’\u000b\rIAy\t\u0002yô¥‚L;\u0017Xz\u0019+g\u0002\u0017ôŒ–E\u001e\u0008F󾈠DK󸼶_\u001b\u001bð«»’xMah6*ô„¶µó¾º¾s\u001dï¹J𦢑)N7J\u0010ð­¼´\u000e<^\u0006𡂨9ὓZó±£#,Eô‡ººyNG𗃎)~c\u00015wC𠻌:\n&5ô‡§g𨋶\u0015𘪉w\\C\u0017\u0018ôž¦.ð©’±}k\u0017~ô‹Ÿá¨›ô±¦9󶳃󻚨i\u0010\u0003\tDg\u0016a\t\u001eð¥ª\u001bð¬Ž_C\u0012\u000bGbu\u001c[zo(󰘽󾵺g\u001bfe\u000e=𒀓筡M\u000b@ðŒ“󲆡\u0018uq\u000f8펟EJôˆ´‰\u0008𑱦;\u000f,c\u000b稆ð©±.1K\u0018@\u000c媾𨟋W\u000bX\u001ek󻩵\u000fó²›²%\u001bó¹·¯>\u0005Ió¾¼¢DXL6I\u001bB.PཚôŠ’•\u000f7\u0017Pu\u000fK:\u001eὑ}S>\u001dó³š\u000b\u0005r;~`Co,/,ð¤‚\u001fU絉\\ngr󳞔ið¤œ\u001dô†¦™Yô…«\u000b5FajE#!\u0007F쑼z+\u001d~Ly󰳦ô‹¡¿\u001aExbmgxU[å›–]b\u0005mo󰌞k^hó²\u0015D𪘻𣒀B\u00052󵫾f𠎯PWU\u000c}ð¨ª3d%y𨖂ê¼MNé’œr𦹧?K虩\u0006\u0004귘𓃴\u0010Oã´¼G~1 gó°­¶neã•¥ô†¢œUô‚»Š\"B\u001aó¸»\u0010ó²¹²Bj\u001c\u0001}wDGð«¯\u0019#\u0014Vc\\\u000cz9'\u00062\u001a\u000eI\tô„ƒ\nbð‘\u000bR%\t\u0005庄DJA3>\u001eD󰲋V\u0018]\u0003\u000f󰪻Rꛥô³ŽdGFO\u000cóº»ãœ¼{\u0002𧕀'ôƒ¦Žó·°¾ð¬µ¨\u0002\u000fjb\tQf-9\n-ó·’‰r𓇱m𫾞pJlc 𠩗󱘑'\u001f\u0006>c?sXEN3󶃣\u0012ló»™¿\u0004Ju'ð’‹”y\u000b󳋧oM[ிQB&æ·Œô‰»æ¯–2\u000bô…¸Š\u001b𠚻󴙢C[O~h\\8*c𣙰O \u0007DQZ󻹯yb \u001dX󵉃`󱩫\u0008ô‹ª£\u0010ô‰€ \u0013ô‚‚‚w󳧻)\u0011\u0019.ⰺ𤙲𦩳K-Gó»”·\u0012Hg3\"t\u0001\u0004ôƒšb$@ð«‹”\u0012OP\u001b󽜚Ip渣9ty󽆄!" + "password": "l\u0003c\u0006=1\u001df\u0003x󳉸𤀼\n\u000c036J𗟶6ð«ž®V{}t\u001d?TR\"\u0018)\u00186h:i\u0017Z9\\i\u0012b\u001dó¼£¢6(IvY \u000e:삓𢓰\u0006\u0010\u001cedô†š²\u0004}OK𗙸Hó°¡²W9_u󽦃`𪪆ô‚±ƒó²·’\u000b\rIAy\t\u0002yô¥‚L;\u0017Xz\u0019+g\u0002\u0017ôŒ–E\u001e\u0008F󾈠DK󸼶_\u001b\u001bð«»’xMah6*ô„¶µó¾º¾s\u001dï¹J𦢑)N7J\u0010ð­¼´\u000e<^\u0006𡂨9ὓZó±£#,Eô‡ººyNG𗃎)~c\u00015wC𠻌:\n&5ô‡§g𨋶\u0015𘪉w\\C\u0017\u0018ôž¦.ð©’±}k\u0017~ô‹Ÿá¨›ô±¦9󶳃󻚨i\u0010\u0003\tDg\u0016a\t\u001eð¥ª\u001bð¬Ž_C\u0012\u000bGbu\u001c[zo(󰘽󾵺g\u001bfe\u000e=𒀓筡M\u000b@ðŒ“󲆡\u0018uq\u000f8펟EJôˆ´‰\u0008𑱦;\u000f,c\u000b稆ð©±.1K\u0018@\u000c媾𨟋W\u000bX\u001ek󻩵\u000fó²›²%\u001bó¹·¯>\u0005Ió¾¼¢DXL6I\u001bB.PཚôŠ’•\u000f7\u0017Pu\u000fK:\u001eὑ}S>\u001dó³š\u000b\u0005r;~`Co,/,ð¤‚\u001fU絉\\ngr󳞔ið¤œ\u001dô†¦™Yô…«\u000b5FajE#!\u0007F쑼z+\u001d~Ly󰳦ô‹¡¿\u001aExbmgxU[å›–]b\u0005mo󰌞k^hó²\u0015D𪘻𣒀B\u00052󵫾f𠎯PWU\u000c}ð¨ª3d%y𨖂ê¼MNé’œr𦹧?K虩\u0006\u0004귘𓃴\u0010Oã´¼G~1 gó°­¶neã•¥ô†¢œUô‚»Š\"B\u001aó¸»\u0010ó²¹²Bj\u001c\u0001}wDGð«¯\u0019#\u0014Vc\\\u000cz9'\u00062\u001a\u000eI\tô„ƒ\nbð‘\u000bR%\t\u0005庄DJA3>\u001eD󰲋V\u0018]\u0003\u000f󰪻Rꛥô³ŽdGFO\u000cóº»ãœ¼{\u0002𧕀'ôƒ¦Žó·°¾ð¬µ¨\u0002\u000fjb\tQf-9\n-ó·’‰r𓇱m𫾞pJlc 𠩗󱘑'\u001f\u0006>c?sXEN3󶃣\u0012ló»™¿\u0004Ju'ð’‹”y\u000b󳋧oM[ிQB&æ·Œô‰»æ¯–2\u000bô…¸Š\u001b𠚻󴙢C[O~h\\8*c𣙰O \u0007DQZ󻹯yb \u001dX󵉃`󱩫\u0008ô‹ª£\u0010ô‰€ \u0013ô‚‚‚w󳧻)\u0011\u0019.ⰺ𤙲𦩳K-Gó»”·\u0012Hg3\"t\u0001\u0004ôƒšb$@ð«‹”\u0012OP\u001b󽜚Ip渣9ty󽆄!", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_18.json b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_18.json index 324c2b0bde9..ca88a0e54cb 100644 --- a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_18.json +++ b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_18.json @@ -1,3 +1,4 @@ { - "password": "ôƒª;7/\u0012猉𖽃\u0013n𞀂稚S9\u0006]༊8P\tôˆ¯ 2ð’ƒ#𬘷7~`ôŒ¨·ðª²±ó»‡£~n짟\u0004\u001c\u000b4?nPy5\u0019\u0019ô‹›­;C\nWð“…„4\u001aTdð¤”\u0018yR7\u001b/Vô‰¿¶\u0002\u000b󴎦,\u0012bv𨇂Kð–¡½{mUôƒ€ž)\u0011𒃶.T\nB=\u0002lsux\u0014ô†¾·1:\u0018Lô‹—Hh\u0000𪟲n/ôŠ½ð§±©ó²˜îŸµqë*)}ôŒºó½•„ô‚’ô‰¨•\u0015\u0018\u00188\u001fU\u0008@jr\u0014&󲃶ô† ¢_a\u0004+\u0016mႳ\u0016\u0014Kw\u0012m:P拯6V'G5@\u0019 Z\u0011s𢯳cq\u001aP\u0014.dè·¡\u0000K樱\u0015뤒s󻹘j/\tuð¦§\u0003󶲸\u0001T뻤f:C[\u000cJfô‰š–𢶑[&hi%)ð¡“2ð­ŠŽj\\ô…½eMô‰€žuvK\u000b\u0018\u001cCw\u000b󻵩$\u00047\u0002óºº\u001c0Ò¯2L;Nô…¾³\"H[kô†€š\u0012ó¶f\u0019wuSE^ôŽ˜±\u000bf\u0002f\u0011㜡\u0019`XY券5B㒸󹫇侓wì«Otê°…2T3Wb\u000e9!:\u001e\u001cóµ²®ô‹¿¿j\u001b~DY\u0001aN\nô„†¥Jw" + "password": "ôƒª;7/\u0012猉𖽃\u0013n𞀂稚S9\u0006]༊8P\tôˆ¯ 2ð’ƒ#𬘷7~`ôŒ¨·ðª²±ó»‡£~n짟\u0004\u001c\u000b4?nPy5\u0019\u0019ô‹›­;C\nWð“…„4\u001aTdð¤”\u0018yR7\u001b/Vô‰¿¶\u0002\u000b󴎦,\u0012bv𨇂Kð–¡½{mUôƒ€ž)\u0011𒃶.T\nB=\u0002lsux\u0014ô†¾·1:\u0018Lô‹—Hh\u0000𪟲n/ôŠ½ð§±©ó²˜îŸµqë*)}ôŒºó½•„ô‚’ô‰¨•\u0015\u0018\u00188\u001fU\u0008@jr\u0014&󲃶ô† ¢_a\u0004+\u0016mႳ\u0016\u0014Kw\u0012m:P拯6V'G5@\u0019 Z\u0011s𢯳cq\u001aP\u0014.dè·¡\u0000K樱\u0015뤒s󻹘j/\tuð¦§\u0003󶲸\u0001T뻤f:C[\u000cJfô‰š–𢶑[&hi%)ð¡“2ð­ŠŽj\\ô…½eMô‰€žuvK\u000b\u0018\u001cCw\u000b󻵩$\u00047\u0002óºº\u001c0Ò¯2L;Nô…¾³\"H[kô†€š\u0012ó¶f\u0019wuSE^ôŽ˜±\u000bf\u0002f\u0011㜡\u0019`XY券5B㒸󹫇侓wì«Otê°…2T3Wb\u000e9!:\u001e\u001cóµ²®ô‹¿¿j\u001b~DY\u0001aN\nô„†¥Jw", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_19.json b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_19.json index 74a1673eef2..eb522a7400a 100644 --- a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_19.json +++ b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_19.json @@ -1,3 +1,4 @@ { - "password": "_\r}uô†¡£Z!X3𧅦\u0006KOMô‡¿ˆ%\u0012ðƒ‚B\u000e𫽓L%w\"\u0005 q]\u0006'\u0007.Oxô¹šô‚†“\\7c\r葈剺{\u0011oô³¢uO\u001842n[k4\rn𥑲O\u0016P𡆜ô„¹\nó¾¢µNôƒ‡œä€µ\\H\u0016br^󵃶ôƒ€žiôŽ¾—\u001ay<\nO\u0006Tp\u000f謇N`fæ ¢}!~ôˆ–¯\u0015cp*!8l\u0006\u0012 銦\u0013î»f\u00001é½´\u0015T憛𫱾 _𗉋0q%;ð„¶f8E0<ó³¥²u\u000e}󻯤h襆+a\n󶈑Nó¾…I`\u0011󼇘𮬛ô·¦ð £.3Kjé‚«\u001e骕H\u000fzI'\u0015Hð‘Šð¥²dN𪿣󱿮󱱗`L\ttNó·šQjV\u001f1i#Ag\u00106.ô£¶eZ*r\u0004󺀓-b`ô¸8\u000c\u0001󶪳nK𩨯fp𭱞RW\u001cs󲺕mH󿌸瞌>\u000bô€‰»r!}쾯'W󲨑ôŒˆ¸qE\u0019𬑠#y\u000cW?#\u0013ô…¯žG\\l5ô„¯²ó°¸šó½£”𢞋ôµ#vsV󸄢8PP\u0004\u0014TZm?𨱢CC\u001bR;\u001fw\\Df[JjbN`X𗒺ꮔx胸9^𗻒🅎~\u0017\u0008zWl&\r󼄊K󳔆𥫡\u0008!\u001aL\u0007ô‚€t-w2N㡇\u0008\u0019𫛯鑅iGó·­¹e\u0003:q\u000c4qKRr\u001748DJTSô·Œ[\u000b\r\u0011𘜆TFó¾¾–8s!}SS7\u000b/𨗸)T\u001d\ncjR𦒑󹹪\u0015\u000bð©€-%&^\u0004ð¨³ó²·‹I(\"B\u0019Pbl\u0014\u001dlô»–\u0000ô‹¨š&鹓&î­“\u000e$\u001dU\u0003:}l&j\u0008Qô¦¬S1vô€Š¶%Q𬌸^󲴄󾜲\u0019\u0003,%\"𧛩s\u000erfB\u0002æš“\u001f7i\u0013\u0006晓\"G(ⶕ%\u001an>ôƒ¦‡4󳄕c\u0016q@オ*I\u0005㛣o;\u0006ó¾·¶)\"B|󶣣𩨤ô€­Šå£Š3\u001cg\u0013?ôŒ°µ}\u000e8%Xl\u0013ã¿Œ[O,ðž¤g%Sð§‹l\u0005\"(H;+\u0002}J\u0012ôŠ—»\u0016\u001e?7𬛖\u000b\u001eX~Y$\u001e^äžšó¼½°\u0010𣑃" + "password": "_\r}uô†¡£Z!X3𧅦\u0006KOMô‡¿ˆ%\u0012ðƒ‚B\u000e𫽓L%w\"\u0005 q]\u0006'\u0007.Oxô¹šô‚†“\\7c\r葈剺{\u0011oô³¢uO\u001842n[k4\rn𥑲O\u0016P𡆜ô„¹\nó¾¢µNôƒ‡œä€µ\\H\u0016br^󵃶ôƒ€žiôŽ¾—\u001ay<\nO\u0006Tp\u000f謇N`fæ ¢}!~ôˆ–¯\u0015cp*!8l\u0006\u0012 銦\u0013î»f\u00001é½´\u0015T憛𫱾 _𗉋0q%;ð„¶f8E0<ó³¥²u\u000e}󻯤h襆+a\n󶈑Nó¾…I`\u0011󼇘𮬛ô·¦ð £.3Kjé‚«\u001e骕H\u000fzI'\u0015Hð‘Šð¥²dN𪿣󱿮󱱗`L\ttNó·šQjV\u001f1i#Ag\u00106.ô£¶eZ*r\u0004󺀓-b`ô¸8\u000c\u0001󶪳nK𩨯fp𭱞RW\u001cs󲺕mH󿌸瞌>\u000bô€‰»r!}쾯'W󲨑ôŒˆ¸qE\u0019𬑠#y\u000cW?#\u0013ô…¯žG\\l5ô„¯²ó°¸šó½£”𢞋ôµ#vsV󸄢8PP\u0004\u0014TZm?𨱢CC\u001bR;\u001fw\\Df[JjbN`X𗒺ꮔx胸9^𗻒🅎~\u0017\u0008zWl&\r󼄊K󳔆𥫡\u0008!\u001aL\u0007ô‚€t-w2N㡇\u0008\u0019𫛯鑅iGó·­¹e\u0003:q\u000c4qKRr\u001748DJTSô·Œ[\u000b\r\u0011𘜆TFó¾¾–8s!}SS7\u000b/𨗸)T\u001d\ncjR𦒑󹹪\u0015\u000bð©€-%&^\u0004ð¨³ó²·‹I(\"B\u0019Pbl\u0014\u001dlô»–\u0000ô‹¨š&鹓&î­“\u000e$\u001dU\u0003:}l&j\u0008Qô¦¬S1vô€Š¶%Q𬌸^󲴄󾜲\u0019\u0003,%\"𧛩s\u000erfB\u0002æš“\u001f7i\u0013\u0006晓\"G(ⶕ%\u001an>ôƒ¦‡4󳄕c\u0016q@オ*I\u0005㛣o;\u0006ó¾·¶)\"B|󶣣𩨤ô€­Šå£Š3\u001cg\u0013?ôŒ°µ}\u000e8%Xl\u0013ã¿Œ[O,ðž¤g%Sð§‹l\u0005\"(H;+\u0002}J\u0012ôŠ—»\u0016\u001e?7𬛖\u000b\u001eX~Y$\u001e^äžšó¼½°\u0010𣑃", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_5.json b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_5.json index b88a403626a..a7510c2e573 100644 --- a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_5.json +++ b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_5.json @@ -1,3 +1,4 @@ { - "password": "꧈h󼋚~ôƒ°\u0008'Nó´·°xh\u0017\u0006(].XO*𗤹U\u0007@*M3𢤗넭𠈆8X!ð –Q\u0017\r𡪹𬳂\u0013m|Ræ–˜2>vdôŠ† \u0017h@.\u0017 G4QôŠ—’0I!?](\u0019ó¶—„\rd\u0018\r\u0019=󿜿8󾄜ᾦ5=ô»·ä®è«›>󰌢E\u0013d&OÒƒ_\u0004DM帽*ó·„Œ<+9\u000c𛉡\u0005\u0010\u0017zU9Bq\u0008s9ð®MY5aó¾…¬!눠H2oó´‚Œ\u0016\u0013P|뤇𬞻2\\\u0003e\u0005K󿌳8?\u001aq🇺,𘓂;%\u0010𪪹\u001c\u0005]\u0006𪟧F3ð©’¥\u0003𫤣\u001f3Ph\u001b𠵡\u000fó¾­´lJRL\tîµ…&\u0010nU$)🧗ôƒ€žó½·™\u000c\u001bK\u00044ô‹°Œ:ð¦‘󺶳ô‡£žU\u001cð–zz{g^H󸹻O\u0008a5bo\u0002\u0013i;:ãž„é´óºµ¯\u001eJW>󱌈\u0019VôŒ„ŠB<ó¼·ó´¯»*'\u0001ô‚¯°1d;ô‚¸§aꋶH惂M𓆲:li=r~`åµ½zCôˆ­¦s\u000b󺵗T~2ô‚¤‘X+I\u0014^BQôŽº›\u0001X\n𢬇w\u0005ô…€µC𬽦ô†„¸P𞀑𫙬\u001f\u001c>á´™ôžŽLI|?\u0001ô‚ŠIEðŸ¸ôŽ¦‚⯬+ó³³™ì³Iô˜­)\u0010\u0011\u0007𤛼>楲(\u00125XôŠšð°§i{8V[i𮛀v\u0018j\u001dRq\u001eʽE|𥷬3B/;9駻\u00110\u0010\\g𥜳cDGó°§·i1\u001c$\u0000ó¼—´\"a?N\\ô‹¥c$W:󰀧Ló¾¼±aôŒ¯™J)]x\u00123\u000eB\u000b𑠃󾦟Q\u0011Gg\u0007\u00153å«¥rI\u0003ôƒº¸>Ió°‹zIX\u0010\u000bó°´4u{Xô‡Œ¥ð¥¶\\Xv@\nð¤›\u001b\u001e13Aã’‘W !\u000côª‹ôŽ®­ïª¼3yr\u001aC26\rU7\u0001/ Eð˜ˆ}\u000b\u001fV\u0012O2;Z|Fó¾µ\u00027𬷰ôŒ©˜L\u001f󲃂󹵫]K:/-ó¿‚󰥄U1lꆊ\u001cRn ]∾" + "password": "꧈h󼋚~ôƒ°\u0008'Nó´·°xh\u0017\u0006(].XO*𗤹U\u0007@*M3𢤗넭𠈆8X!ð –Q\u0017\r𡪹𬳂\u0013m|Ræ–˜2>vdôŠ† \u0017h@.\u0017 G4QôŠ—’0I!?](\u0019ó¶—„\rd\u0018\r\u0019=󿜿8󾄜ᾦ5=ô»·ä®è«›>󰌢E\u0013d&OÒƒ_\u0004DM帽*ó·„Œ<+9\u000c𛉡\u0005\u0010\u0017zU9Bq\u0008s9ð®MY5aó¾…¬!눠H2oó´‚Œ\u0016\u0013P|뤇𬞻2\\\u0003e\u0005K󿌳8?\u001aq🇺,𘓂;%\u0010𪪹\u001c\u0005]\u0006𪟧F3ð©’¥\u0003𫤣\u001f3Ph\u001b𠵡\u000fó¾­´lJRL\tîµ…&\u0010nU$)🧗ôƒ€žó½·™\u000c\u001bK\u00044ô‹°Œ:ð¦‘󺶳ô‡£žU\u001cð–zz{g^H󸹻O\u0008a5bo\u0002\u0013i;:ãž„é´óºµ¯\u001eJW>󱌈\u0019VôŒ„ŠB<ó¼·ó´¯»*'\u0001ô‚¯°1d;ô‚¸§aꋶH惂M𓆲:li=r~`åµ½zCôˆ­¦s\u000b󺵗T~2ô‚¤‘X+I\u0014^BQôŽº›\u0001X\n𢬇w\u0005ô…€µC𬽦ô†„¸P𞀑𫙬\u001f\u001c>á´™ôžŽLI|?\u0001ô‚ŠIEðŸ¸ôŽ¦‚⯬+ó³³™ì³Iô˜­)\u0010\u0011\u0007𤛼>楲(\u00125XôŠšð°§i{8V[i𮛀v\u0018j\u001dRq\u001eʽE|𥷬3B/;9駻\u00110\u0010\\g𥜳cDGó°§·i1\u001c$\u0000ó¼—´\"a?N\\ô‹¥c$W:󰀧Ló¾¼±aôŒ¯™J)]x\u00123\u000eB\u000b𑠃󾦟Q\u0011Gg\u0007\u00153å«¥rI\u0003ôƒº¸>Ió°‹zIX\u0010\u000bó°´4u{Xô‡Œ¥ð¥¶\\Xv@\nð¤›\u001b\u001e13Aã’‘W !\u000côª‹ôŽ®­ïª¼3yr\u001aC26\rU7\u0001/ Eð˜ˆ}\u000b\u001fV\u0012O2;Z|Fó¾µ\u00027𬷰ôŒ©˜L\u001f󲃂󹵫]K:/-ó¿‚󰥄U1lꆊ\u001cRn ]∾", + "verification_code": "123456" } diff --git a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_9.json b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_9.json index 086a90dbaf9..9f5eaf211e1 100644 --- a/libs/wire-api/test/golden/testObject_TeamDeleteData_team_9.json +++ b/libs/wire-api/test/golden/testObject_TeamDeleteData_team_9.json @@ -1,3 +1,4 @@ { - "password": "5\tt\u001f\u0002.-:𤮶󶒱ôŽ¢¶á²†ð¬†ˆ!z\u0006)󽊦,è´¾cH|\u001b`ó°„¼,ô·¤\u0010_jb\u0016\u0010󴃀\u000f(Y츖@\u0002$膻m!;P\u000eð¨œt\u001c?lôˆ­¹}Ui\\\u0005b𥷖RJó½…¿\u0006é°‘*W󸺜\u0011󰯃\u0013Qô‰•™Jh\u0001Y5i-𢷸\u0013K@W󽣂iã¡žp\u001b2[,J\u0013\u0006\u001e\u000c\u000c/󿸸!á™·-\u0001XBq;Vð˜‚󸣧\u000e)u\u000e>(💯ð¡­kôŠª€\u0002R诗\u0006?1\u0007w<\u00013\rH:\u001eYA\u000f|IOV\u0018X\u00156\tMó°­‡Zôˆ¡ˆ*󰱤\\\u000clð °€9ô‰š¯\u001a\u0018\u0011\\\u0007\u0011ô‰·ŠH0\nk\u0004\u0012\u0014\u001aó´°¤XO𩾂|!𧈿 ô‰³¾é½§ôŒ©›W\u0000Xd󲧳Lð¢ \u001d\u0010𘀾c#s=g𥿟\r0$\r\u000bD|\u001d󲡠:H4\u0002Mgæ«•Qfꢌ/,zï’¬i\u0007ó¿¼›#ð—”—hmFôƒ®§]hô‰¿€Z󱡧\u001a*ôŒ”Œ6\u0018\u0013Bq𩛞lôŠ‡˜ó¼œ¸\u0003=-8QP\u00053ôˆ©çˆ£iì­¤\u001fXG:E3(\u001b/ó½¯ ð­º†3\u0006ó½—»ç†Y𧶡\nn󶆊7SN\u001a4<󹀘E\u0007UDeBUIJLê—¼w_ó¶”hGI\u0011w:nJ\u0006fW촰󲫷\u00072v\ts`ð„šóµ–¹\u00081'j:ôƒ«º\u0003<'﨨\u001c91i>T:XD\u0018\u0007ô‡›‘!M\u0019wc@𪟠\u000c_|\u0018MìŽtmó¿­‡\u0016æ–¡:+Xtó¸„󻌥ã·ä–󶈮^ )!\u000eꛗ󻓲y&󲕉캊kó³’š\\ô„—½-&\u0011\u001a󴵌𥫑\r맟\u000bSô‹šªc,ô¨”4\u00000E^iôˆƒ‰ó·¿ªâ†·\t𘡃]p󵛫𡩆\u000eq{Z8ô‰‚¶K㮩躀]\\\u001a`a|縉\u001dð¡°šuk#E,z=+!/\u001a𧲧\u000f$bᆭ5\tioð˜—\u0016/ಪð¢<ôŠ•o\u001d\u0003\u0019껫$U'ó½””ó´€¹vuQr07ä°‘\t5🟈\u0018󰃱󲷶\u0018D𠃎(💯ð¡­kôŠª€\u0002R诗\u0006?1\u0007w<\u00013\rH:\u001eYA\u000f|IOV\u0018X\u00156\tMó°­‡Zôˆ¡ˆ*󰱤\\\u000clð °€9ô‰š¯\u001a\u0018\u0011\\\u0007\u0011ô‰·ŠH0\nk\u0004\u0012\u0014\u001aó´°¤XO𩾂|!𧈿 ô‰³¾é½§ôŒ©›W\u0000Xd󲧳Lð¢ \u001d\u0010𘀾c#s=g𥿟\r0$\r\u000bD|\u001d󲡠:H4\u0002Mgæ«•Qfꢌ/,zï’¬i\u0007ó¿¼›#ð—”—hmFôƒ®§]hô‰¿€Z󱡧\u001a*ôŒ”Œ6\u0018\u0013Bq𩛞lôŠ‡˜ó¼œ¸\u0003=-8QP\u00053ôˆ©çˆ£iì­¤\u001fXG:E3(\u001b/ó½¯ ð­º†3\u0006ó½—»ç†Y𧶡\nn󶆊7SN\u001a4<󹀘E\u0007UDeBUIJLê—¼w_ó¶”hGI\u0011w:nJ\u0006fW촰󲫷\u00072v\ts`ð„šóµ–¹\u00081'j:ôƒ«º\u0003<'﨨\u001c91i>T:XD\u0018\u0007ô‡›‘!M\u0019wc@𪟠\u000c_|\u0018MìŽtmó¿­‡\u0016æ–¡:+Xtó¸„󻌥ã·ä–󶈮^ )!\u000eꛗ󻓲y&󲕉캊kó³’š\\ô„—½-&\u0011\u001a󴵌𥫑\r맟\u000bSô‹šªc,ô¨”4\u00000E^iôˆƒ‰ó·¿ªâ†·\t𘡃]p󵛫𡩆\u000eq{Z8ô‰‚¶K㮩躀]\\\u001a`a|縉\u001dð¡°šuk#E,z=+!/\u001a𧲧\u000f$bᆭ5\tioð˜—\u0016/ಪð¢<ôŠ•o\u001d\u0003\u0019껫$U'ó½””ó´€¹vuQr07ä°‘\t5🟈\u0018󰃱󲷶\u0018D𠃎A", "id": "00000003-0000-0000-0000-000100000001", "name": "Pð­·“;gi" diff --git a/libs/wire-api/test/golden/testObject_Team_team_2.json b/libs/wire-api/test/golden/testObject_Team_team_2.json index 895a61e70d1..826ec582ba7 100644 --- a/libs/wire-api/test/golden/testObject_Team_team_2.json +++ b/libs/wire-api/test/golden/testObject_Team_team_2.json @@ -1,7 +1,7 @@ { "binding": false, "creator": "00000000-0000-0004-0000-000000000001", - "icon": "ô¬µ\t5", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "è™±R3q", "id": "00000004-0000-0003-0000-000000000004", "name": "Ycᛄ" diff --git a/libs/wire-api/test/golden/testObject_Team_team_20.json b/libs/wire-api/test/golden/testObject_Team_team_20.json index a2e88fc5df0..5899393e6f2 100644 --- a/libs/wire-api/test/golden/testObject_Team_team_20.json +++ b/libs/wire-api/test/golden/testObject_Team_team_20.json @@ -1,7 +1,7 @@ { "binding": false, "creator": "00000000-0000-0004-0000-000000000004", - "icon": "ó¸·šI\u0002\u0003", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "v0ôŒ¡´3", "id": "00000000-0000-0004-0000-000400000003", "name": "𮩶c" diff --git a/libs/wire-api/test/golden/testObject_Team_team_3.json b/libs/wire-api/test/golden/testObject_Team_team_3.json index d06ee96c92f..bacd42f414c 100644 --- a/libs/wire-api/test/golden/testObject_Team_team_3.json +++ b/libs/wire-api/test/golden/testObject_Team_team_3.json @@ -1,7 +1,7 @@ { "binding": false, "creator": "00000003-0000-0004-0000-000100000000", - "icon": "", + "icon": "default", "icon_key": "sôº´", "id": "00000004-0000-0003-0000-000000000003", "name": "2EôŠ´•" diff --git a/libs/wire-api/test/golden/testObject_Team_team_4.json b/libs/wire-api/test/golden/testObject_Team_team_4.json index e2c72e1c830..939cc911f07 100644 --- a/libs/wire-api/test/golden/testObject_Team_team_4.json +++ b/libs/wire-api/test/golden/testObject_Team_team_4.json @@ -1,7 +1,7 @@ { "binding": false, "creator": "00000004-0000-0000-0000-000100000003", - "icon": "ô‡“žu\u001cC\u0001", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "X", "id": "00000000-0000-0002-0000-000100000004", "name": "ð«‘‚\u0008k" diff --git a/libs/wire-api/test/golden/testObject_Team_team_5.json b/libs/wire-api/test/golden/testObject_Team_team_5.json index 5b259bd37a0..00e49fe7506 100644 --- a/libs/wire-api/test/golden/testObject_Team_team_5.json +++ b/libs/wire-api/test/golden/testObject_Team_team_5.json @@ -1,7 +1,7 @@ { "binding": true, "creator": "00000000-0000-0004-0000-000200000002", - "icon": "ô†Š…", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "?&\u001b", "id": "00000004-0000-0003-0000-000000000004", "name": "\u0006ð˜¼ä»„" diff --git a/libs/wire-api/test/golden/testObject_Team_team_6.json b/libs/wire-api/test/golden/testObject_Team_team_6.json index b9ce15d026b..debe6299327 100644 --- a/libs/wire-api/test/golden/testObject_Team_team_6.json +++ b/libs/wire-api/test/golden/testObject_Team_team_6.json @@ -1,7 +1,7 @@ { "binding": false, "creator": "00000000-0000-0003-0000-000000000003", - "icon": "_'\u0011\u0002", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "id": "00000000-0000-0002-0000-000000000001", "name": "ó¸­¬xó¼¬]ã¹±" } diff --git a/libs/wire-api/test/golden/testObject_Team_team_7.json b/libs/wire-api/test/golden/testObject_Team_team_7.json index 6ed09fa61bd..28718021fd8 100644 --- a/libs/wire-api/test/golden/testObject_Team_team_7.json +++ b/libs/wire-api/test/golden/testObject_Team_team_7.json @@ -1,7 +1,7 @@ { "binding": true, "creator": "00000001-0000-0002-0000-000400000000", - "icon": "X\n|ó¾’š", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "icon_key": "𗤥", "id": "00000002-0000-0003-0000-000000000002", "name": "⛉ô“–󸙰7ô‚©½" diff --git a/libs/wire-api/test/golden/testObject_Team_team_8.json b/libs/wire-api/test/golden/testObject_Team_team_8.json index 3b010018380..940b270dd31 100644 --- a/libs/wire-api/test/golden/testObject_Team_team_8.json +++ b/libs/wire-api/test/golden/testObject_Team_team_8.json @@ -1,7 +1,7 @@ { "binding": false, "creator": "00000002-0000-0003-0000-000400000001", - "icon": "󻎖", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "id": "00000003-0000-0004-0000-000000000001", "name": "\r釖{\u0013\\" } diff --git a/libs/wire-api/test/golden/testObject_Team_team_9.json b/libs/wire-api/test/golden/testObject_Team_team_9.json index 2765bd238bb..bf120473c4a 100644 --- a/libs/wire-api/test/golden/testObject_Team_team_9.json +++ b/libs/wire-api/test/golden/testObject_Team_team_9.json @@ -1,7 +1,7 @@ { "binding": false, "creator": "00000002-0000-0000-0000-000000000004", - "icon": "d\u0003U", + "icon": "3-1-55b9ad19-315c-4bda-8c0f-5d7b0e143008", "id": "00000004-0000-0002-0000-000200000003", "name": "G[Hu{" } diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_1.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_1.json index bd8d2ce2ec5..270dd504ee9 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_1.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_1.json @@ -3,6 +3,7 @@ "id": 65535, "key": "" }, + "mls_public_keys": {}, "prekeys": [ { "id": 2, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_10.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_10.json index b65ab21495e..e1cfd19d518 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_10.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_10.json @@ -3,6 +3,7 @@ "id": 65535, "key": "" }, + "mls_public_keys": {}, "prekeys": [ { "id": 1, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_11.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_11.json index a76feaa29b4..d9f808c1cca 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_11.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_11.json @@ -1,5 +1,6 @@ { "label": "`9q)å··", + "mls_public_keys": {}, "prekeys": [ { "id": 0, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_12.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_12.json index b1c08c1b190..22ccaae1b0c 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_12.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_12.json @@ -1,5 +1,6 @@ { "label": "o\u001a", + "mls_public_keys": {}, "prekeys": [ { "id": 3, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_13.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_13.json index ca858396e70..392c55e45b7 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_13.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_13.json @@ -4,6 +4,7 @@ "id": 65535, "key": "/\u0018ó»°”\u0010k" }, + "mls_public_keys": {}, "prekeys": [ { "id": 0, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_14.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_14.json index 6f8e06f5d71..5073b14eac0 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_14.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_14.json @@ -3,6 +3,7 @@ "id": 65535, "key": "'S\u0007sm" }, + "mls_public_keys": {}, "prekeys": [ { "id": 0, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_15.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_15.json index cf10e4667cc..d2c397d40fa 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_15.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_15.json @@ -1,3 +1,4 @@ { + "mls_public_keys": {}, "prekeys": [] } diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_16.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_16.json index 776dfe1758b..83beb88c98a 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_16.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_16.json @@ -4,6 +4,7 @@ "id": 65535, "key": "Ll\u000e󳘂k𪾴\u0004f\u0012" }, + "mls_public_keys": {}, "prekeys": [ { "id": 2, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_17.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_17.json index 7be5a4fd543..e0d73d51365 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_17.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_17.json @@ -1,4 +1,5 @@ { "label": "cóµ€³S\u0015\u0010}0 6\u001e", + "mls_public_keys": {}, "prekeys": [] } diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_18.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_18.json index 412d1b07d41..994c7ca451f 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_18.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_18.json @@ -4,6 +4,7 @@ "id": 65535, "key": "󶨶󵧈4/" }, + "mls_public_keys": {}, "prekeys": [ { "id": 0, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_19.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_19.json index 9cad714d27d..1bbedf3bc61 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_19.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_19.json @@ -4,6 +4,7 @@ "id": 65535, "key": "𮩲tð—¥°ð›±¥i" }, + "mls_public_keys": {}, "prekeys": [ { "id": 1, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_2.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_2.json index 47949668348..34bd0e57cea 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_2.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_2.json @@ -1,5 +1,6 @@ { "label": "㧉㌌\u0001𒇦\u001f", + "mls_public_keys": {}, "prekeys": [ { "id": 2, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_20.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_20.json index 231a9d057a0..ae45621bd36 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_20.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_20.json @@ -7,6 +7,7 @@ "id": 65535, "key": "\u0014 }Kg\u000be3" }, + "mls_public_keys": {}, "prekeys": [ { "id": 1, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_3.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_3.json index 2035b2516b6..7919d9a56e0 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_3.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_3.json @@ -4,6 +4,7 @@ "id": 65535, "key": "L𘚥" }, + "mls_public_keys": {}, "prekeys": [ { "id": 1, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_4.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_4.json index 00efa2df959..6054bc30d45 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_4.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_4.json @@ -4,6 +4,7 @@ "id": 65535, "key": "" }, + "mls_public_keys": {}, "prekeys": [ { "id": 1, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_5.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_5.json index 90598aa4ca6..04525638e9b 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_5.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_5.json @@ -4,6 +4,7 @@ "id": 65535, "key": "Cs 𒌨=" }, + "mls_public_keys": {}, "prekeys": [ { "id": 0, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_6.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_6.json index 7de9dd38df2..f3b217635b5 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_6.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_6.json @@ -3,6 +3,7 @@ "id": 65535, "key": "" }, + "mls_public_keys": {}, "prekeys": [ { "id": 1, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_7.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_7.json index 838f4945b3e..0312f248b36 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_7.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_7.json @@ -1,5 +1,6 @@ { "label": "D9", + "mls_public_keys": {}, "prekeys": [ { "id": 0, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_8.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_8.json index f8b30726dcc..3d984cf7c15 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_8.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_8.json @@ -1,5 +1,8 @@ { "label": "8\u0015D𛈘", + "mls_public_keys": { + "ed25519": "Ym05MElISmxZV3hzZVNCaElIQjFZbXhwWXlCclpYaz0=" + }, "prekeys": [ { "id": 4, diff --git a/libs/wire-api/test/golden/testObject_UpdateClient_user_9.json b/libs/wire-api/test/golden/testObject_UpdateClient_user_9.json index c10f3c29eb5..332b841ebed 100644 --- a/libs/wire-api/test/golden/testObject_UpdateClient_user_9.json +++ b/libs/wire-api/test/golden/testObject_UpdateClient_user_9.json @@ -1,5 +1,6 @@ { "label": "a彟\\ BS.readFile "test/resources/key_package_ref1" + kpRef MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 (KeyPackageData kpData) @?= ref diff --git a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs index b63079c2021..521db8f957c 100644 --- a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs +++ b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs @@ -29,6 +29,7 @@ import qualified Wire.API.Asset as Asset import qualified Wire.API.Call.Config as Call.Config import qualified Wire.API.Connection as Connection import qualified Wire.API.Conversation as Conversation +import qualified Wire.API.Conversation.Action as Conversation.Action import qualified Wire.API.Conversation.Bot as Conversation.Bot import qualified Wire.API.Conversation.Code as Conversation.Code import qualified Wire.API.Conversation.Member as Conversation.Member @@ -251,7 +252,7 @@ tests = testRoundTrip @User.DeleteUser, testRoundTrip @User.VerifyDeleteUser, testRoundTrip @User.DeletionCodeTimeout, - testRoundTrip @User.SndFactorPasswordChallengeAction, + testRoundTrip @User.VerificationAction, testRoundTrip @User.SendVerificationCode, testRoundTrip @User.Activation.ActivationKey, -- FUTUREWORK: this should probably be tested individually, @@ -317,7 +318,8 @@ tests = testRoundTrip @User.RichInfo.RichInfo, testRoundTrip @(User.Search.SearchResult User.Search.TeamContact), testRoundTrip @User.Search.TeamContact, - testRoundTrip @(Wrapped.Wrapped "some_int" Int) + testRoundTrip @(Wrapped.Wrapped "some_int" Int), + testRoundTrip @Conversation.Action.SomeConversationAction ] testRoundTrip :: diff --git a/libs/wire-api/wire-api.cabal b/libs/wire-api/wire-api.cabal index 9de414542fc..11c69b90574 100644 --- a/libs/wire-api/wire-api.cabal +++ b/libs/wire-api/wire-api.cabal @@ -418,7 +418,6 @@ test-suite wire-api-golden-tests Test.Wire.API.Golden.Generated.SFTServer_user Test.Wire.API.Golden.Generated.SimpleMember_user Test.Wire.API.Golden.Generated.SimpleMembers_user - Test.Wire.API.Golden.Generated.SndFactorPasswordChallengeAction_user Test.Wire.API.Golden.Generated.Team_team Test.Wire.API.Golden.Generated.TeamBinding_team Test.Wire.API.Golden.Generated.TeamContact_user @@ -460,6 +459,7 @@ test-suite wire-api-golden-tests Test.Wire.API.Golden.Generated.UserProfile_user Test.Wire.API.Golden.Generated.UserSSOId_user Test.Wire.API.Golden.Generated.UserUpdate_user + Test.Wire.API.Golden.Generated.VerificationAction_user Test.Wire.API.Golden.Generated.VerifyDeleteUser_user Test.Wire.API.Golden.Generated.ViewLegalHoldService_team Test.Wire.API.Golden.Generated.ViewLegalHoldServiceInfo_team @@ -476,6 +476,7 @@ test-suite wire-api-golden-tests Test.Wire.API.Golden.Manual.CreateScimToken Test.Wire.API.Golden.Manual.FeatureConfigEvent Test.Wire.API.Golden.Manual.GetPaginatedConversationIds + Test.Wire.API.Golden.Manual.GroupId Test.Wire.API.Golden.Manual.ListConversations Test.Wire.API.Golden.Manual.QualifiedUserClientPrekeyMap Test.Wire.API.Golden.Manual.SearchResultContact diff --git a/services/brig/brig.cabal b/services/brig/brig.cabal index f65f170d6ea..0c8e7d6d32b 100644 --- a/services/brig/brig.cabal +++ b/services/brig/brig.cabal @@ -60,7 +60,6 @@ library Brig.Data.Instances Brig.Data.LoginCode Brig.Data.MLS.KeyPackage - Brig.Data.MLS.KeyPackage.Instances Brig.Data.PasswordReset Brig.Data.Properties Brig.Data.Types @@ -500,6 +499,7 @@ executable brig-integration , cassandra-util , containers , cookie + , data-default , data-timeout , email-validate , exceptions @@ -623,6 +623,8 @@ executable brig-schema V65_FederatedConnections V66_PersonalFeatureConfCallInit V67_MLSKeyPackages + V68_AddMLSPublicKeys + V69_MLSKeyPackageRefMapping V9 Paths_brig hs-source-dirs: diff --git a/services/brig/brig.integration.yaml b/services/brig/brig.integration.yaml index 57a98b11fef..d5825b451a1 100644 --- a/services/brig/brig.integration.yaml +++ b/services/brig/brig.integration.yaml @@ -134,6 +134,7 @@ turn: optSettings: setActivationTimeout: 5 + setVerificationTimeout: 5 setTeamInvitationTimeout: 5 setExpiredUserCleanupTimeout: 1 setTwilio: test/resources/twilio-credentials.yaml diff --git a/services/brig/deb/opt/brig/templates/en/user/email/verification-delete-team-subject.txt b/services/brig/deb/opt/brig/templates/en/user/email/verification-delete-team-subject.txt new file mode 100644 index 00000000000..5687c98820a --- /dev/null +++ b/services/brig/deb/opt/brig/templates/en/user/email/verification-delete-team-subject.txt @@ -0,0 +1 @@ +your ${brand} verification code is ${code} \ No newline at end of file diff --git a/services/brig/deb/opt/brig/templates/en/user/email/verification-delete-team.html b/services/brig/deb/opt/brig/templates/en/user/email/verification-delete-team.html new file mode 100644 index 00000000000..89578fdd1c8 --- /dev/null +++ b/services/brig/deb/opt/brig/templates/en/user/email/verification-delete-team.html @@ -0,0 +1 @@ +your ${brand} verification code is ${code}

${brand_label_url}

Verify team deletion

${email} was used to delete your ${brand} team. Enter this code to verify your email and delete the team.

 

${code}

 

If you have any questions, please contact us.

                                                           
\ No newline at end of file diff --git a/services/brig/deb/opt/brig/templates/en/user/email/verification-delete-team.txt b/services/brig/deb/opt/brig/templates/en/user/email/verification-delete-team.txt new file mode 100644 index 00000000000..31374d5df0e --- /dev/null +++ b/services/brig/deb/opt/brig/templates/en/user/email/verification-delete-team.txt @@ -0,0 +1,17 @@ +[${brand_logo}] + +${brand_label_url} [${brand_url}] + +VERIFY TEAM DELETION +${email} was used to delete your ${brand} team. Enter this code to verify your +email and delete the team. + +${code} + +If you have any questions, please contact us [${support}]. + + +-------------------------------------------------------------------------------- + +Privacy policy and terms of use [${legal}] · Report Misuse [${misuse}] +${copyright}. ALL RIGHTS RESERVED. \ No newline at end of file diff --git a/services/brig/deb/opt/brig/templates/en/user/email/verification-login-subject.txt b/services/brig/deb/opt/brig/templates/en/user/email/verification-login-subject.txt new file mode 100644 index 00000000000..5687c98820a --- /dev/null +++ b/services/brig/deb/opt/brig/templates/en/user/email/verification-login-subject.txt @@ -0,0 +1 @@ +your ${brand} verification code is ${code} \ No newline at end of file diff --git a/services/brig/deb/opt/brig/templates/en/user/email/verification-login.html b/services/brig/deb/opt/brig/templates/en/user/email/verification-login.html new file mode 100644 index 00000000000..3187413c172 --- /dev/null +++ b/services/brig/deb/opt/brig/templates/en/user/email/verification-login.html @@ -0,0 +1 @@ +your ${brand} verification code is ${code}

${brand_label_url}

Verify login

${email} was used to log in to your ${brand} account. Enter this code to verify your email and log in.

 

${code}

 

If you have any questions, please contact us.

                                                           
\ No newline at end of file diff --git a/services/brig/deb/opt/brig/templates/en/user/email/verification-login.txt b/services/brig/deb/opt/brig/templates/en/user/email/verification-login.txt new file mode 100644 index 00000000000..e4e58b4f01f --- /dev/null +++ b/services/brig/deb/opt/brig/templates/en/user/email/verification-login.txt @@ -0,0 +1,17 @@ +[${brand_logo}] + +${brand_label_url} [${brand_url}] + +VERIFY LOGIN +${email} was used to log in to your ${brand} account. Enter this code to verify +your email and log in. + +${code} + +If you have any questions, please contact us [${support}]. + + +-------------------------------------------------------------------------------- + +Privacy policy and terms of use [${legal}] · Report Misuse [${misuse}] +${copyright}. ALL RIGHTS RESERVED. \ No newline at end of file diff --git a/services/brig/deb/opt/brig/templates/en/user/email/verification-scim-token-subject.txt b/services/brig/deb/opt/brig/templates/en/user/email/verification-scim-token-subject.txt new file mode 100644 index 00000000000..5687c98820a --- /dev/null +++ b/services/brig/deb/opt/brig/templates/en/user/email/verification-scim-token-subject.txt @@ -0,0 +1 @@ +your ${brand} verification code is ${code} \ No newline at end of file diff --git a/services/brig/deb/opt/brig/templates/en/user/email/verification-scim-token.html b/services/brig/deb/opt/brig/templates/en/user/email/verification-scim-token.html new file mode 100644 index 00000000000..d5faa9effb4 --- /dev/null +++ b/services/brig/deb/opt/brig/templates/en/user/email/verification-scim-token.html @@ -0,0 +1 @@ +your ${brand} verification code is ${code}

${brand_label_url}

Verify SCIM token creation

${email} was used to generate a SCIM token. Enter this code to verify your email and create the token.

 

${code}

 

If you have any questions, please contact us.

                                                           
\ No newline at end of file diff --git a/services/brig/deb/opt/brig/templates/en/user/email/verification-scim-token.txt b/services/brig/deb/opt/brig/templates/en/user/email/verification-scim-token.txt new file mode 100644 index 00000000000..94c3fd2b98b --- /dev/null +++ b/services/brig/deb/opt/brig/templates/en/user/email/verification-scim-token.txt @@ -0,0 +1,17 @@ +[${brand_logo}] + +${brand_label_url} [${brand_url}] + +VERIFY SCIM TOKEN CREATION +${email} was used to generate a SCIM token. Enter this code to verify your email +and create the token. + +${code} + +If you have any questions, please contact us [${support}]. + + +-------------------------------------------------------------------------------- + +Privacy policy and terms of use [${legal}] · Report Misuse [${misuse}] +${copyright}. ALL RIGHTS RESERVED. \ No newline at end of file diff --git a/services/brig/deb/opt/brig/templates/en/user/email/verification-subject.txt b/services/brig/deb/opt/brig/templates/en/user/email/verification-subject.txt index 4b2efa7858d..5cb510f23f5 100644 --- a/services/brig/deb/opt/brig/templates/en/user/email/verification-subject.txt +++ b/services/brig/deb/opt/brig/templates/en/user/email/verification-subject.txt @@ -1 +1 @@ -${brand} verification code \ No newline at end of file +${code} is your ${brand} verification code \ No newline at end of file diff --git a/services/brig/package.yaml b/services/brig/package.yaml index 43473e9945e..faf5d5df314 100644 --- a/services/brig/package.yaml +++ b/services/brig/package.yaml @@ -217,6 +217,7 @@ executables: - cassandra-util - containers - cookie + - data-default - data-timeout - email-validate - exceptions diff --git a/services/brig/schema/src/Main.hs b/services/brig/schema/src/Main.hs index 848d0004a03..511cb18358e 100644 --- a/services/brig/schema/src/Main.hs +++ b/services/brig/schema/src/Main.hs @@ -77,6 +77,8 @@ import qualified V64_ClientCapabilities import qualified V65_FederatedConnections import qualified V66_PersonalFeatureConfCallInit import qualified V67_MLSKeyPackages +import qualified V68_AddMLSPublicKeys +import qualified V69_MLSKeyPackageRefMapping import qualified V9 main :: IO () @@ -143,7 +145,9 @@ main = do V64_ClientCapabilities.migration, V65_FederatedConnections.migration, V66_PersonalFeatureConfCallInit.migration, - V67_MLSKeyPackages.migration + V67_MLSKeyPackages.migration, + V68_AddMLSPublicKeys.migration, + V69_MLSKeyPackageRefMapping.migration -- When adding migrations here, don't forget to update -- 'schemaVersion' in Brig.App diff --git a/services/brig/src/Brig/Data/MLS/KeyPackage/Instances.hs b/services/brig/schema/src/V68_AddMLSPublicKeys.hs similarity index 58% rename from services/brig/src/Brig/Data/MLS/KeyPackage/Instances.hs rename to services/brig/schema/src/V68_AddMLSPublicKeys.hs index 5add8976113..599ea5163dc 100644 --- a/services/brig/src/Brig/Data/MLS/KeyPackage/Instances.hs +++ b/services/brig/schema/src/V68_AddMLSPublicKeys.hs @@ -14,23 +14,26 @@ -- -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -{-# OPTIONS_GHC -Wno-orphans #-} -module Brig.Data.MLS.KeyPackage.Instances () where +module V68_AddMLSPublicKeys + ( migration, + ) +where -import Cassandra -import qualified Data.ByteString.Lazy as LBS +import Cassandra.Schema import Imports -import Wire.API.MLS.KeyPackage +import Text.RawString.QQ -instance Cql KeyPackageRef where - ctype = Tagged BlobColumn - toCql = CqlBlob . LBS.fromStrict . unKeyPackageRef - fromCql (CqlBlob b) = pure . KeyPackageRef . LBS.toStrict $ b - fromCql _ = Left "Expected CqlBlob" - -instance Cql KeyPackageData where - ctype = Tagged BlobColumn - toCql = CqlBlob . kpData - fromCql (CqlBlob b) = pure . KeyPackageData $ b - fromCql _ = Left "Expected CqlBlob" +migration :: Migration +migration = Migration 68 "Add MLS public keys" $ do + schema' + [r| + CREATE TABLE mls_public_keys + ( user uuid + , client text + , sig_scheme text + , key blob + , PRIMARY KEY (user, client, sig_scheme) + ) WITH compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} + AND gc_grace_seconds = 864000; + |] diff --git a/services/brig/schema/src/V69_MLSKeyPackageRefMapping.hs b/services/brig/schema/src/V69_MLSKeyPackageRefMapping.hs new file mode 100644 index 00000000000..34c95d70e14 --- /dev/null +++ b/services/brig/schema/src/V69_MLSKeyPackageRefMapping.hs @@ -0,0 +1,44 @@ +{-# LANGUAGE QuasiQuotes #-} + +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 Wire Swiss GmbH +-- +-- This program is free software: you can redistribute it and/or modify it under +-- the terms of the GNU Affero General Public License as published by the Free +-- Software Foundation, either version 3 of the License, or (at your option) any +-- later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Affero General Public License along +-- with this program. If not, see . + +module V69_MLSKeyPackageRefMapping + ( migration, + ) +where + +import Cassandra.Schema +import Imports +import Text.RawString.QQ + +migration :: Migration +migration = + Migration 69 "Add key package ref mapping" $ + schema' + [r| + CREATE TABLE mls_key_package_refs + ( ref blob + , domain text + , user uuid + , client text + , conv_domain text + , conv uuid + , PRIMARY KEY (ref) + ) WITH compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} + AND gc_grace_seconds = 864000; + |] diff --git a/services/brig/src/Brig/API/Client.hs b/services/brig/src/Brig/API/Client.hs index 27324a294b9..35a2b2df064 100644 --- a/services/brig/src/Brig/API/Client.hs +++ b/services/brig/src/Brig/API/Client.hs @@ -58,11 +58,14 @@ import Brig.Types import Brig.Types.Intra import Brig.Types.Team.LegalHold (LegalHoldClientRequest (..)) import Brig.Types.User.Event +import qualified Brig.User.Auth as UserAuth import qualified Brig.User.Auth.Cookie as Auth import Brig.User.Email +import Cassandra (MonadClient) import Control.Error import Control.Lens (view) import Data.ByteString.Conversion +import Data.Code as Code import Data.Domain (Domain) import Data.IP (IP) import Data.Id (ClientId, ConnId, UserId) @@ -81,14 +84,15 @@ import Wire.API.Federation.API.Brig (GetUserClients (GetUserClients)) import Wire.API.Federation.Error import qualified Wire.API.Message as Message import Wire.API.Team.LegalHold (LegalholdProtectee (..)) +import qualified Wire.API.User as Code import Wire.API.User.Client import Wire.API.UserMap (QualifiedUserMap (QualifiedUserMap, qualifiedUserMap), UserMap (userMap)) lookupLocalClient :: UserId -> ClientId -> (AppIO r) (Maybe Client) -lookupLocalClient = Data.lookupClient +lookupLocalClient uid = wrapClient . Data.lookupClient uid lookupLocalClients :: UserId -> (AppIO r) [Client] -lookupLocalClients = Data.lookupClients +lookupLocalClients = wrapClient . Data.lookupClients lookupPubClient :: Qualified UserId -> ClientId -> ExceptT ClientError (AppIO r) (Maybe PubClient) lookupPubClient qid cid = do @@ -117,15 +121,16 @@ lookupPubClientsBulk qualifiedUids = do pure $ QualifiedUserMap (Map.union localUserClientMap remoteUserClientMap) lookupLocalPubClientsBulk :: [UserId] -> ExceptT ClientError (AppIO r) (UserMap (Set PubClient)) -lookupLocalPubClientsBulk = Data.lookupPubClientsBulk +lookupLocalPubClientsBulk = lift . wrapClient . Data.lookupPubClientsBulk -- nb. We must ensure that the set of clients known to brig is always -- a superset of the clients known to galley. addClient :: UserId -> Maybe ConnId -> Maybe IP -> NewClient -> ExceptT ClientError (AppIO r) Client addClient u con ip new = do - acc <- lift (Data.lookupAccount u) >>= maybe (throwE (ClientUserNotFound u)) return + acc <- lift (wrapClient $ Data.lookupAccount u) >>= maybe (throwE (ClientUserNotFound u)) return + verifyCode (newClientVerificationCode new) (userId . accountUser $ acc) loc <- maybe (return Nothing) locationOf ip - maxPermClients <- fromMaybe Opt.defUserMaxPermClients <$> Opt.setUserMaxPermClients <$> view settings + maxPermClients <- fromMaybe Opt.defUserMaxPermClients . Opt.setUserMaxPermClients <$> view settings let caps :: Maybe (Set ClientCapability) caps = updlhdev $ newClientCapabilities new where @@ -134,7 +139,11 @@ addClient u con ip new = do then Just . maybe (Set.singleton lhcaps) (Set.insert lhcaps) else id lhcaps = ClientSupportsLegalholdImplicitConsent - (clt, old, count) <- Data.addClient u clientId' new maxPermClients loc caps !>> ClientDataError + (clt0, old, count) <- + wrapClientE + (Data.addClient u clientId' new maxPermClients loc caps) + !>> ClientDataError + let clt = clt0 {clientMLSPublicKeys = newClientMLSPublicKeys new} let usr = accountUser acc lift $ do for_ old $ execDelete u con @@ -149,7 +158,16 @@ addClient u con ip new = do where clientId' = clientIdFromPrekey (unpackLastPrekey $ newClientLastKey new) -updateClient :: UserId -> ClientId -> UpdateClient -> ExceptT ClientError (AppIO r) () + verifyCode :: Maybe Code.Value -> UserId -> ExceptT ClientError (AppIO r) () + verifyCode mbCode userId = + -- this only happens inside the login flow (in particular, when logging in from a new device) + -- the code obtained for logging in is used a second time for adding the device + UserAuth.verifyCode mbCode Code.Login userId `catchE` \case + VerificationCodeRequired -> throwE ClientCodeAuthenticationRequired + VerificationCodeNoPendingCode -> throwE ClientCodeAuthenticationFailed + VerificationCodeNoEmail -> throwE ClientCodeAuthenticationFailed + +updateClient :: MonadClient m => UserId -> ClientId -> UpdateClient -> ExceptT ClientError m () updateClient u c r = do client <- lift (Data.lookupClient u c) >>= maybe (throwE ClientNotFound) pure for_ (updateClientLabel r) $ lift . Data.updateClientLabel u c . Just @@ -160,12 +178,13 @@ updateClient u c r = do else throwE ClientCapabilitiesCannotBeRemoved let lk = maybeToList (unpackLastPrekey <$> updateClientLastKey r) Data.updatePrekeys u c (lk ++ updateClientPrekeys r) !>> ClientDataError + Data.addMLSPublicKeys u c (Map.assocs (updateClientMLSPublicKeys r)) !>> ClientDataError -- nb. We must ensure that the set of clients known to brig is always -- a superset of the clients known to galley. rmClient :: UserId -> ConnId -> ClientId -> Maybe PlainTextPassword -> ExceptT ClientError (AppIO r) () rmClient u con clt pw = - maybe (throwE ClientNotFound) fn =<< lift (Data.lookupClient u clt) + maybe (throwE ClientNotFound) fn =<< lift (wrapClient $ Data.lookupClient u clt) where fn client = do case clientType client of @@ -174,7 +193,7 @@ rmClient u con clt pw = -- Temporary clients don't need to re-auth TemporaryClientType -> pure () -- All other clients must authenticate - _ -> Data.reauthenticate u pw !>> ClientDataError . ClientReAuthError + _ -> wrapClientE (Data.reauthenticate u pw) !>> ClientDataError . ClientReAuthError lift $ execDelete u (Just con) client claimPrekey :: LegalholdProtectee -> UserId -> Domain -> ClientId -> ExceptT ClientError (AppIO r) (Maybe ClientPrekey) @@ -204,7 +223,7 @@ claimPrekeyBundle protectee domain uid = do claimLocalPrekeyBundle :: LegalholdProtectee -> UserId -> ExceptT ClientError (AppIO r) PrekeyBundle claimLocalPrekeyBundle protectee u = do - clients <- map clientId <$> Data.lookupClients u + clients <- map clientId <$> lift (wrapClient (Data.lookupClients u)) guardLegalhold protectee (mkUserClients [(u, clients)]) PrekeyBundle u . catMaybes <$> lift (mapM (Data.claimPrekey u) clients) @@ -271,9 +290,9 @@ claimLocalMultiPrekeyBundles protectee userClients = do execDelete :: UserId -> Maybe ConnId -> Client -> (AppIO r) () execDelete u con c = do Intra.rmClient u (clientId c) - for_ (clientCookie c) $ \l -> Auth.revokeCookies u [] [l] + for_ (clientCookie c) $ \l -> wrapClient $ Auth.revokeCookies u [] [l] Intra.onClientEvent u con (ClientRemoved u c) - Data.rmClient u (clientId c) + wrapClient $ Data.rmClient u (clientId c) -- | Defensive measure when no prekey is found for a -- requested client: Ensure that the client does indeed @@ -287,7 +306,7 @@ noPrekeys u c = do ~~ field "client" (toByteString c) ~~ msg (val "No prekey found. Ensuring client does not exist.") Intra.rmClient u c - client <- Data.lookupClient u c + client <- wrapClient $ Data.lookupClient u c for_ client $ \_ -> Log.err $ field "user" (toByteString u) @@ -314,7 +333,7 @@ legalHoldClientRequested targetUser (LegalHoldClientRequest _requester lastPreke removeLegalHoldClient :: UserId -> (AppIO r) () removeLegalHoldClient uid = do - clients <- Data.lookupClients uid + clients <- wrapClient $ Data.lookupClients uid -- Should only be one; but just in case we'll treat it as a list let legalHoldClients = filter ((== LegalHoldClientType) . clientType) clients -- maybe log if this isn't the case diff --git a/services/brig/src/Brig/API/Connection.hs b/services/brig/src/Brig/API/Connection.hs index 2cf4f49372a..579f5c19107 100644 --- a/services/brig/src/Brig/API/Connection.hs +++ b/services/brig/src/Brig/API/Connection.hs @@ -61,7 +61,7 @@ import Wire.API.Routes.Public.Util (ResponseForExistedCreated (..)) ensureIsActivated :: Local UserId -> MaybeT (AppIO r) () ensureIsActivated lusr = do - active <- lift $ Data.isActivated (tUnqualified lusr) + active <- lift . wrapClient $ Data.isActivated (tUnqualified lusr) guard active ensureNotSameTeam :: Local UserId -> Local UserId -> (ConnectionM r) () @@ -100,8 +100,8 @@ createConnectionToLocalUser self conn target = do ensureIsActivated target checkLegalholdPolicyConflict (tUnqualified self) (tUnqualified target) ensureNotSameTeam self target - s2o <- lift $ Data.lookupConnection self (qUntagged target) - o2s <- lift $ Data.lookupConnection target (qUntagged self) + s2o <- lift . wrapClient $ Data.lookupConnection self (qUntagged target) + o2s <- lift . wrapClient $ Data.lookupConnection target (qUntagged self) case update <$> s2o <*> o2s of Just rs -> rs @@ -115,11 +115,11 @@ createConnectionToLocalUser self conn target = do logConnection (tUnqualified self) (qUntagged target) . msg (val "Creating connection") qcnv <- Intra.createConnectConv (qUntagged self) (qUntagged target) Nothing (Just conn) - s2o' <- Data.insertConnection self (qUntagged target) SentWithHistory qcnv - o2s' <- Data.insertConnection target (qUntagged self) PendingWithHistory qcnv + s2o' <- wrapClient $ Data.insertConnection self (qUntagged target) SentWithHistory qcnv + o2s' <- wrapClient $ Data.insertConnection target (qUntagged self) PendingWithHistory qcnv e2o <- ConnectionUpdated o2s' (ucStatus <$> o2s) - <$> Data.lookupName (tUnqualified self) + <$> wrapClient (Data.lookupName (tUnqualified self)) let e2s = ConnectionUpdated s2o' (ucStatus <$> s2o) Nothing mapM_ (Intra.onConnectionEvent (tUnqualified self) (Just conn)) [e2o, e2s] return s2o' @@ -147,14 +147,14 @@ createConnectionToLocalUser self conn target = do logLocalConnection (tUnqualified self) (qUnqualified (ucTo s2o)) . msg (val "Accepting connection") cnv <- lift $ for (ucConvId s2o) $ Intra.acceptConnectConv self (Just conn) - s2o' <- lift $ Data.updateConnection s2o AcceptedWithHistory + s2o' <- lift . wrapClient $ Data.updateConnection s2o AcceptedWithHistory o2s' <- - lift $ + lift . wrapClient $ if (cnvType <$> cnv) == Just ConnectConv then Data.updateConnection o2s BlockedWithHistory else Data.updateConnection o2s AcceptedWithHistory e2o <- - lift $ + lift . wrapClient $ ConnectionUpdated o2s' (Just $ ucStatus o2s) <$> Data.lookupName (tUnqualified self) let e2s = ConnectionUpdated s2o' (Just $ ucStatus s2o) Nothing @@ -172,7 +172,7 @@ createConnectionToLocalUser self conn target = do return $ Existed s2o' change :: UserConnection -> RelationWithHistory -> ExceptT ConnectionError (AppIO r) (ResponseForExistedCreated UserConnection) - change c s = Existed <$> lift (Data.updateConnection c s) + change c s = Existed <$> lift (wrapClient $ Data.updateConnection c s) -- | Throw error if one user has a LH device and the other status `no_consent` or vice versa. -- @@ -292,14 +292,15 @@ updateConnectionToLocalUser self other newStatus conn = do -- crashes. when (ucStatus o2s `elem` [Sent, Pending]) . lift $ do o2s' <- - if (cnvType <$> cnv) /= Just ConnectConv - then Data.updateConnection o2s AcceptedWithHistory - else Data.updateConnection o2s BlockedWithHistory + wrapClient $ + if (cnvType <$> cnv) /= Just ConnectConv + then Data.updateConnection o2s AcceptedWithHistory + else Data.updateConnection o2s BlockedWithHistory e2o <- ConnectionUpdated o2s' (Just $ ucStatus o2s) - <$> Data.lookupName (tUnqualified self) + <$> wrapClient (Data.lookupName (tUnqualified self)) Intra.onConnectionEvent (tUnqualified self) conn e2o - lift $ Just <$> Data.updateConnection s2o AcceptedWithHistory + lift . wrapClient $ Just <$> Data.updateConnection s2o AcceptedWithHistory block :: UserConnection -> ExceptT ConnectionError (AppIO r) (Maybe UserConnection) block s2o = lift $ do @@ -307,7 +308,7 @@ updateConnectionToLocalUser self other newStatus conn = do logLocalConnection (tUnqualified self) (qUnqualified (ucTo s2o)) . msg (val "Blocking connection") traverse_ (Intra.blockConv self conn) (ucConvId s2o) - Just <$> Data.updateConnection s2o BlockedWithHistory + wrapClient $ Just <$> Data.updateConnection s2o BlockedWithHistory unblock :: UserConnection -> UserConnection -> Relation -> ExceptT ConnectionError (AppIO r) (Maybe UserConnection) unblock s2o o2s new = do @@ -320,15 +321,17 @@ updateConnectionToLocalUser self other newStatus conn = do cnv <- lift $ traverse (Intra.unblockConv self conn) (ucConvId s2o) when (ucStatus o2s == Sent && new == Accepted) . lift $ do o2s' <- - if (cnvType <$> cnv) /= Just ConnectConv - then Data.updateConnection o2s AcceptedWithHistory - else Data.updateConnection o2s BlockedWithHistory + wrapClient $ + if (cnvType <$> cnv) /= Just ConnectConv + then Data.updateConnection o2s AcceptedWithHistory + else Data.updateConnection o2s BlockedWithHistory e2o :: ConnectionEvent <- - ConnectionUpdated o2s' (Just $ ucStatus o2s) - <$> Data.lookupName (tUnqualified self) + wrapClient $ + ConnectionUpdated o2s' (Just $ ucStatus o2s) + <$> Data.lookupName (tUnqualified self) -- TODO: is this correct? shouldnt o2s be sent to other? Intra.onConnectionEvent (tUnqualified self) conn e2o - lift $ Just <$> Data.updateConnection s2o (mkRelationWithHistory (error "impossible") new) + lift . wrapClient $ Just <$> Data.updateConnection s2o (mkRelationWithHistory (error "impossible") new) cancel :: UserConnection -> UserConnection -> ExceptT ConnectionError (AppIO r) (Maybe UserConnection) cancel s2o o2s = do @@ -337,7 +340,7 @@ updateConnectionToLocalUser self other newStatus conn = do . msg (val "Cancelling connection") lfrom <- qualifyLocal (ucFrom s2o) lift $ traverse_ (Intra.blockConv lfrom conn) (ucConvId s2o) - o2s' <- lift $ Data.updateConnection o2s CancelledWithHistory + o2s' <- lift . wrapClient $ Data.updateConnection o2s CancelledWithHistory let e2o = ConnectionUpdated o2s' (Just $ ucStatus o2s) Nothing lift $ Intra.onConnectionEvent (tUnqualified self) conn e2o change s2o Cancelled @@ -345,14 +348,14 @@ updateConnectionToLocalUser self other newStatus conn = do change :: UserConnection -> Relation -> ExceptT ConnectionError (AppIO r) (Maybe UserConnection) change c s = do -- FUTUREWORK: refactor to total function. Gets only called with either Ignored, Accepted, Cancelled - lift $ Just <$> Data.updateConnection c (mkRelationWithHistory (error "impossible") s) + lift . wrapClient $ Just <$> Data.updateConnection c (mkRelationWithHistory (error "impossible") s) localConnection :: Local UserId -> Local UserId -> ExceptT ConnectionError (AppIO r) UserConnection localConnection la lb = do - lift (Data.lookupConnection la (qUntagged lb)) + lift (wrapClient $ Data.lookupConnection la (qUntagged lb)) >>= tryJust (NotConnected (tUnqualified la) (qUntagged lb)) mkRelationWithHistory :: HasCallStack => Relation -> Relation -> RelationWithHistory @@ -404,7 +407,7 @@ updateConnectionInternal = \case for_ [s2o, o2s] $ \(uconn :: UserConnection) -> lift $ do lfrom <- qualifyLocal (ucFrom uconn) traverse_ (Intra.blockConv lfrom Nothing) (ucConvId uconn) - uconn' <- Data.updateConnection uconn (mkRelationWithHistory (ucStatus uconn) MissingLegalholdConsent) + uconn' <- wrapClient $ Data.updateConnection uconn (mkRelationWithHistory (ucStatus uconn) MissingLegalholdConsent) let ev = ConnectionUpdated uconn' (Just $ ucStatus uconn) Nothing Intra.onConnectionEvent (tUnqualified self) Nothing ev @@ -428,7 +431,7 @@ updateConnectionInternal = \case where go :: Maybe UserId -> ExceptT ConnectionError (AppT r IO) () go mbStart = do - page <- lift $ Data.lookupLocalConnections user mbStart pageSize + page <- lift . wrapClient $ Data.lookupLocalConnections user mbStart pageSize handleConns (resultList page) case resultList page of (conn : rest) -> @@ -442,8 +445,8 @@ updateConnectionInternal = \case lfrom <- qualifyLocal (ucFrom uconnRev) void . lift . for (ucConvId uconn) $ Intra.unblockConv lfrom Nothing uconnRevRel :: RelationWithHistory <- relationWithHistory lfrom (ucTo uconnRev) - uconnRev' <- lift $ Data.updateConnection uconnRev (undoRelationHistory uconnRevRel) - connName <- lift $ Data.lookupName (tUnqualified lfrom) + uconnRev' <- lift . wrapClient $ Data.updateConnection uconnRev (undoRelationHistory uconnRevRel) + connName <- lift . wrapClient $ Data.lookupName (tUnqualified lfrom) let connEvent = ConnectionUpdated { ucConn = uconnRev', @@ -452,9 +455,12 @@ updateConnectionInternal = \case } lift $ Intra.onConnectionEvent (ucFrom uconn) Nothing connEvent - relationWithHistory :: Local UserId -> Qualified UserId -> ExceptT ConnectionError (AppIO r) RelationWithHistory + relationWithHistory :: + Local UserId -> + Qualified UserId -> + ExceptT ConnectionError (AppIO r) RelationWithHistory relationWithHistory self target = - lift (Data.lookupRelationWithHistory self target) + lift (wrapClient $ Data.lookupRelationWithHistory self target) >>= tryJust (NotConnected (tUnqualified self) target) undoRelationHistory :: RelationWithHistory -> RelationWithHistory @@ -477,16 +483,17 @@ updateConnectionInternal = \case createLocalConnectionUnchecked :: Local UserId -> Local UserId -> (AppIO r) () createLocalConnectionUnchecked self other = do qcnv <- liftIO $ qUntagged . qualifyAs self <$> (Id <$> UUID.nextRandom) - void $ Data.insertConnection self (qUntagged other) AcceptedWithHistory qcnv - void $ Data.insertConnection other (qUntagged self) AcceptedWithHistory qcnv + wrapClient $ do + void $ Data.insertConnection self (qUntagged other) AcceptedWithHistory qcnv + void $ Data.insertConnection other (qUntagged self) AcceptedWithHistory qcnv createRemoteConnectionUnchecked :: Local UserId -> Remote UserId -> (AppIO r) () createRemoteConnectionUnchecked self other = do qcnv <- liftIO $ qUntagged . qualifyAs self <$> (Id <$> UUID.nextRandom) - void $ Data.insertConnection self (qUntagged other) AcceptedWithHistory qcnv + void . wrapClient $ Data.insertConnection self (qUntagged other) AcceptedWithHistory qcnv lookupConnections :: UserId -> Maybe UserId -> Range 1 500 Int32 -> (AppIO r) UserConnectionList lookupConnections from start size = do lusr <- qualifyLocal from - rs <- Data.lookupLocalConnections lusr start size + rs <- wrapClient $ Data.lookupLocalConnections lusr start size return $! UserConnectionList (Data.resultList rs) (Data.resultHasMore rs) diff --git a/services/brig/src/Brig/API/Connection/Remote.hs b/services/brig/src/Brig/API/Connection/Remote.hs index a36cc48ab33..a064b6c6092 100644 --- a/services/brig/src/Brig/API/Connection/Remote.hs +++ b/services/brig/src/Brig/API/Connection/Remote.hs @@ -159,11 +159,12 @@ transitionTo self mzcon other Nothing (Just rel) actor = lift $ do -- create connection connection <- - Data.insertConnection - self - (qUntagged other) - (relationWithHistory rel) - qcnv + wrapClient $ + Data.insertConnection + self + (qUntagged other) + (relationWithHistory rel) + qcnv -- send event pushEvent self mzcon connection @@ -174,7 +175,7 @@ transitionTo self mzcon other (Just connection) (Just rel) actor = lift $ do void $ updateOne2OneConv self Nothing other (ucConvId connection) rel actor -- update connection - connection' <- Data.updateConnection connection (relationWithHistory rel) + connection' <- wrapClient $ Data.updateConnection connection (relationWithHistory rel) -- send event pushEvent self mzcon connection' @@ -256,7 +257,7 @@ createConnectionToRemoteUser :: Remote UserId -> (ConnectionM r) (ResponseForExistedCreated UserConnection) createConnectionToRemoteUser self zcon other = do - mconnection <- lift $ Data.lookupConnection self (qUntagged other) + mconnection <- lift . wrapClient $ Data.lookupConnection self (qUntagged other) fst <$> performLocalAction self (Just zcon) other mconnection LocalConnect updateConnectionToRemoteUser :: @@ -266,7 +267,7 @@ updateConnectionToRemoteUser :: Maybe ConnId -> (ConnectionM r) (Maybe UserConnection) updateConnectionToRemoteUser self other rel1 zcon = do - mconnection <- lift $ Data.lookupConnection self (qUntagged other) + mconnection <- lift . wrapClient $ Data.lookupConnection self (qUntagged other) action <- actionForTransition rel1 ?? InvalidTransition (tUnqualified self) diff --git a/services/brig/src/Brig/API/Connection/Util.hs b/services/brig/src/Brig/API/Connection/Util.hs index 49051bbd56b..60ff1d3bfb5 100644 --- a/services/brig/src/Brig/API/Connection/Util.hs +++ b/services/brig/src/Brig/API/Connection/Util.hs @@ -39,6 +39,6 @@ type ConnectionM r = ExceptT ConnectionError (AppIO r) checkLimit :: Local UserId -> ExceptT ConnectionError (AppIO r) () checkLimit u = noteT (TooManyConnections (tUnqualified u)) $ do - n <- lift $ Data.countConnections u [Accepted, Sent] + n <- lift . wrapClient $ Data.countConnections u [Accepted, Sent] l <- setUserMaxConnections <$> view settings guard (n < l) diff --git a/services/brig/src/Brig/API/Error.hs b/services/brig/src/Brig/API/Error.hs index f1f4ef97736..8b7feda0f34 100644 --- a/services/brig/src/Brig/API/Error.hs +++ b/services/brig/src/Brig/API/Error.hs @@ -178,6 +178,8 @@ loginError (LoginBlocked wait) = tooManyFailedLogins () [("Retry-After", toByteString' (retryAfterSeconds wait))] +loginError LoginCodeRequired = StdError loginCodeAuthenticationRequired +loginError LoginCodeInvalid = StdError loginCodeAuthenticationFailed authError :: AuthError -> Error authError AuthInvalidUser = StdError (errorDescriptionTypeToWai @BadCredentials) @@ -189,6 +191,9 @@ authError AuthPendingInvitation = StdError accountPending reauthError :: ReAuthError -> Error reauthError ReAuthMissingPassword = StdError (errorDescriptionTypeToWai @MissingAuth) reauthError (ReAuthError e) = authError e +reauthError ReAuthCodeVerificationRequired = StdError verificationCodeRequired +reauthError ReAuthCodeVerificationNoPendingCode = StdError verificationCodeNoPendingCode +reauthError ReAuthCodeVerificationNoEmail = StdError verificationCodeNoEmail zauthError :: ZAuth.Failure -> Error zauthError ZAuth.Expired = StdError authTokenExpired @@ -205,6 +210,8 @@ clientError ClientLegalHoldCannotBeAdded = StdError can'tAddLegalHoldClient clientError (ClientFederationError e) = fedError e clientError ClientCapabilitiesCannotBeRemoved = StdError clientCapabilitiesCannotBeRemoved clientError ClientMissingLegalholdConsent = StdError (errorDescriptionTypeToWai @MissingLegalholdConsent) +clientError ClientCodeAuthenticationFailed = StdError verificationCodeAuthFailed +clientError ClientCodeAuthenticationRequired = StdError verificationCodeRequired fedError :: FederationError -> Error fedError = StdError . federationErrorToWai @@ -217,6 +224,7 @@ clientDataError TooManyClients = StdError (errorDescriptionTypeToWai @TooManyCli clientDataError (ClientReAuthError e) = reauthError e clientDataError ClientMissingAuth = StdError (errorDescriptionTypeToWai @MissingAuth) clientDataError MalformedPrekeys = StdError (errorDescriptionTypeToWai @MalformedPrekeys) +clientDataError MLSPublicKeyDuplicate = StdError $ errorDescriptionTypeToWai @DuplicateMLSPublicKey deleteUserError :: DeleteUserError -> Error deleteUserError DeleteUserInvalid = StdError (errorDescriptionTypeToWai @InvalidUser) @@ -282,6 +290,12 @@ loginCodePending = Wai.mkError status403 "pending-login" "A login code is still loginCodeNotFound :: Wai.Error loginCodeNotFound = Wai.mkError status404 "no-pending-login" "No login code was found." +loginCodeAuthenticationFailed :: Wai.Error +loginCodeAuthenticationFailed = Wai.mkError status403 "code-authentication-failed" "The login code is not valid." + +loginCodeAuthenticationRequired :: Wai.Error +loginCodeAuthenticationRequired = Wai.mkError status403 "code-authentication-required" "A login verification code is required." + accountPending :: Wai.Error accountPending = Wai.mkError status403 "pending-activation" "Account pending activation." @@ -436,3 +450,15 @@ customerExtensionBlockedDomain domain = Wai.mkError (mkStatus 451 "Unavailable F "[Customer extension] the email domain " <> cs (show domain) <> " that you are attempting to register a user with has been \ \blocked for creating wire users. Please contact your IT department." + +verificationCodeRequired :: Wai.Error +verificationCodeRequired = Wai.mkError status403 "code-authentication-required" "Verification code required." + +verificationCodeNoPendingCode :: Wai.Error +verificationCodeNoPendingCode = Wai.mkError status403 "code-authentication-failed" "Code authentication failed (no such code)." + +verificationCodeNoEmail :: Wai.Error +verificationCodeNoEmail = Wai.mkError status403 "code-authentication-failed" "Code authentication failed (no such email)." + +verificationCodeAuthFailed :: Wai.Error +verificationCodeAuthFailed = Wai.mkError status403 "code-authentication-failed" "Code authentication failed." diff --git a/services/brig/src/Brig/API/Federation.hs b/services/brig/src/Brig/API/Federation.hs index d1a7caf1ba8..0dc5a991cd5 100644 --- a/services/brig/src/Brig/API/Federation.hs +++ b/services/brig/src/Brig/API/Federation.hs @@ -25,7 +25,7 @@ import Brig.API.Error (clientError) import Brig.API.Handler (Handler) import qualified Brig.API.User as API import Brig.API.Util (lookupSearchPolicy) -import Brig.App (qualifyLocal) +import Brig.App (qualifyLocal, wrapClient) import qualified Brig.Data.Connection as Data import qualified Brig.Data.User as Data import Brig.IO.Intra (notify) @@ -74,12 +74,12 @@ federationSitemap = sendConnectionAction :: Domain -> NewConnectionRequest -> (Handler r) NewConnectionResponse sendConnectionAction originDomain NewConnectionRequest {..} = do - active <- lift $ Data.isActivated ncrTo + active <- lift $ wrapClient $ Data.isActivated ncrTo if active then do self <- qualifyLocal ncrTo let other = toRemoteUnsafe originDomain ncrFrom - mconnection <- lift $ Data.lookupConnection self (qUntagged other) + mconnection <- lift . wrapClient $ Data.lookupConnection self (qUntagged other) maction <- lift $ performRemoteAction self other mconnection ncrAction pure $ NewConnectionResponseOk maction else pure NewConnectionResponseUserNotActivated @@ -96,7 +96,7 @@ getUserByHandle domain handle = do if not performHandleLookup then pure Nothing else lift $ do - maybeOwnerId <- API.lookupHandle handle + maybeOwnerId <- wrapClient $ API.lookupHandle handle case maybeOwnerId of Nothing -> pure Nothing @@ -150,7 +150,7 @@ searchUsers domain (SearchRequest searchTerm) = do exactHandleSearch n | n > 0 = do let maybeHandle = parseHandle searchTerm - maybeOwnerId <- maybe (pure Nothing) (lift . API.lookupHandle) maybeHandle + maybeOwnerId <- maybe (pure Nothing) (lift . wrapClient . API.lookupHandle) maybeHandle case maybeOwnerId of Nothing -> pure [] Just foundUser -> lift $ contactFromProfile <$$> API.lookupLocalProfiles Nothing [foundUser] diff --git a/services/brig/src/Brig/API/Internal.hs b/services/brig/src/Brig/API/Internal.hs index a2fc26916fb..d5d98c1bd09 100644 --- a/services/brig/src/Brig/API/Internal.hs +++ b/services/brig/src/Brig/API/Internal.hs @@ -30,11 +30,14 @@ import Brig.API.Error import Brig.API.Handler import Brig.API.Types import qualified Brig.API.User as API +import qualified Brig.API.User as Api import Brig.API.Util (validateHandle) import Brig.App +import qualified Brig.Code as Code import Brig.Data.Activation import qualified Brig.Data.Client as Data import qualified Brig.Data.Connection as Data +import qualified Brig.Data.MLS.KeyPackage as Data import qualified Brig.Data.User as Data import qualified Brig.IO.Intra as Intra import Brig.Options hiding (internalEvents, sesQueue) @@ -72,6 +75,8 @@ import Servant.Swagger.Internal.Orphans () import Servant.Swagger.UI import qualified System.Logger.Class as Log import Wire.API.ErrorDescription +import Wire.API.MLS.Credential +import Wire.API.MLS.KeyPackage import qualified Wire.API.Routes.Internal.Brig as BrigIRoutes import Wire.API.Routes.Internal.Brig.Connection import Wire.API.Routes.Named @@ -84,7 +89,7 @@ import Wire.API.User.RichInfo -- Sitemap (servant) servantSitemap :: ServerT BrigIRoutes.API (Handler r) -servantSitemap = ejpdAPI :<|> accountAPI +servantSitemap = ejpdAPI :<|> accountAPI :<|> mlsAPI :<|> getVerificationCode ejpdAPI :: ServerT BrigIRoutes.EJPD_API (Handler r) ejpdAPI = @@ -95,22 +100,39 @@ ejpdAPI = :<|> getConnectionsStatusUnqualified :<|> getConnectionsStatus +mlsAPI :: ServerT BrigIRoutes.MLSAPI (Handler r) +mlsAPI = getClientByKeyPackageRef + accountAPI :: ServerT BrigIRoutes.AccountAPI (Handler r) accountAPI = Named @"createUserNoVerify" createUserNoVerify -- | Responds with 'Nothing' if field is NULL in existing user or user does not exist. getAccountFeatureConfig :: UserId -> (Handler r) ApiFt.TeamFeatureStatusNoConfig getAccountFeatureConfig uid = - lift (Data.lookupFeatureConferenceCalling uid) + lift (wrapClient $ Data.lookupFeatureConferenceCalling uid) >>= maybe (view (settings . getAfcConferenceCallingDefNull)) pure putAccountFeatureConfig :: UserId -> ApiFt.TeamFeatureStatusNoConfig -> (Handler r) NoContent putAccountFeatureConfig uid status = - lift $ Data.updateFeatureConferenceCalling uid (Just status) $> NoContent + lift $ wrapClient $ Data.updateFeatureConferenceCalling uid (Just status) $> NoContent deleteAccountFeatureConfig :: UserId -> (Handler r) NoContent deleteAccountFeatureConfig uid = - lift $ Data.updateFeatureConferenceCalling uid Nothing $> NoContent + lift $ wrapClient $ Data.updateFeatureConferenceCalling uid Nothing $> NoContent + +getClientByKeyPackageRef :: KeyPackageRef -> Handler r (Maybe ClientIdentity) +getClientByKeyPackageRef = runMaybeT . mapMaybeT wrapClientE . Data.derefKeyPackage + +getVerificationCode :: UserId -> VerificationAction -> (Handler r) (Maybe Code.Value) +getVerificationCode uid action = do + user <- wrapClientE $ Api.lookupUser NoPendingInvitations uid + maybe (pure Nothing) (lookupCode action) (userEmail =<< user) + where + lookupCode :: VerificationAction -> Email -> (Handler r) (Maybe Code.Value) + lookupCode a e = do + key <- Code.mkKey (Code.ForEmail e) + code <- wrapClientE $ Code.lookup key (Code.scopeFromAction a) + pure $ Code.codeValue <$> code swaggerDocsAPI :: Servant.Server BrigIRoutes.SwaggerDocsAPI swaggerDocsAPI = swaggerSchemaUIServer BrigIRoutes.swaggerDoc @@ -307,7 +329,7 @@ internalListClientsH (_ ::: req) = do internalListClients :: UserSet -> (AppIO r) UserClients internalListClients (UserSet usrs) = do UserClients . Map.fromList - <$> API.lookupUsersClientIds (Set.toList usrs) + <$> wrapClient (API.lookupUsersClientIds (Set.toList usrs)) internalListFullClientsH :: JSON ::: JsonRequest UserSet -> (Handler r) Response internalListFullClientsH (_ ::: req) = @@ -315,7 +337,7 @@ internalListFullClientsH (_ ::: req) = internalListFullClients :: UserSet -> (AppIO r) UserClientsFull internalListFullClients (UserSet usrs) = - UserClientsFull <$> Data.lookupClientsBulk (Set.toList usrs) + UserClientsFull <$> wrapClient (Data.lookupClientsBulk (Set.toList usrs)) createUserNoVerify :: NewUser -> (Handler r) (Either RegisterError SelfProfile) createUserNoVerify uData = lift . runExceptT $ do @@ -338,7 +360,7 @@ deleteUserNoVerifyH uid = do deleteUserNoVerify :: UserId -> (Handler r) () deleteUserNoVerify uid = do void $ - lift (API.lookupAccount uid) + lift (wrapClient $ API.lookupAccount uid) >>= ifNothing (errorDescriptionTypeToWai @UserNotFound) lift $ API.deleteUserNoVerify uid @@ -369,11 +391,11 @@ listActivatedAccounts elh includePendingInvitations = do case elh of Left us -> byIds (fromList us) Right hs -> do - us <- mapM (API.lookupHandle) (fromList hs) + us <- mapM (wrapClient . API.lookupHandle) (fromList hs) byIds (catMaybes us) where byIds :: [UserId] -> (AppIO r) [UserAccount] - byIds uids = API.lookupAccounts uids >>= filterM accountValid + byIds uids = wrapClient (API.lookupAccounts uids) >>= filterM accountValid accountValid :: UserAccount -> (AppIO r) Bool accountValid account = case userIdentity . accountUser $ account of @@ -382,7 +404,7 @@ listActivatedAccounts elh includePendingInvitations = do case (accountStatus account, includePendingInvitations, emailIdentity ident) of (PendingInvitation, False, _) -> pure False (PendingInvitation, True, Just email) -> do - hasInvitation <- isJust <$> lookupInvitationByEmail email + hasInvitation <- isJust <$> wrapClient (lookupInvitationByEmail email) unless hasInvitation $ do -- user invited via scim should expire together with its invitation API.deleteUserNoVerify (userId . accountUser $ account) @@ -406,10 +428,10 @@ getActivationCodeH (_ ::: emailOrPhone) = do getActivationCode :: Either Email Phone -> (Handler r) GetActivationCodeResp getActivationCode emailOrPhone = do - apair <- lift $ API.lookupActivationCode emailOrPhone + apair <- lift . wrapClient $ API.lookupActivationCode emailOrPhone maybe (throwStd activationKeyNotFound) (return . GetActivationCodeResp) apair -data GetActivationCodeResp = GetActivationCodeResp (ActivationKey, ActivationCode) +newtype GetActivationCodeResp = GetActivationCodeResp (ActivationKey, ActivationCode) instance ToJSON GetActivationCodeResp where toJSON (GetActivationCodeResp (k, c)) = object ["key" .= k, "code" .= c] @@ -422,7 +444,7 @@ getPasswordResetCode :: Either Email Phone -> (AppIO r) (Maybe GetPasswordResetC getPasswordResetCode emailOrPhone = do GetPasswordResetCodeResp <$$> API.lookupPasswordResetCode emailOrPhone -data GetPasswordResetCodeResp = GetPasswordResetCodeResp (PasswordResetKey, PasswordResetCode) +newtype GetPasswordResetCodeResp = GetPasswordResetCodeResp (PasswordResetKey, PasswordResetCode) instance ToJSON GetPasswordResetCodeResp where toJSON (GetPasswordResetCodeResp (k, c)) = object ["key" .= k, "code" .= c] @@ -435,14 +457,14 @@ changeAccountStatusH (usr ::: req) = do getAccountStatusH :: JSON ::: UserId -> (Handler r) Response getAccountStatusH (_ ::: usr) = do - status <- lift $ API.lookupStatus usr + status <- lift $ wrapClient $ API.lookupStatus usr return $ case status of Just s -> json $ AccountStatusResp s Nothing -> setStatus status404 empty getConnectionsStatusUnqualified :: ConnectionsStatusRequest -> Maybe Relation -> (Handler r) [ConnectionStatus] getConnectionsStatusUnqualified ConnectionsStatusRequest {csrFrom, csrTo} flt = lift $ do - r <- maybe (API.lookupConnectionStatus' csrFrom) (API.lookupConnectionStatus csrFrom) csrTo + r <- wrapClient $ maybe (API.lookupConnectionStatus' csrFrom) (API.lookupConnectionStatus csrFrom) csrTo return $ maybe r (filterByRelation r) flt where filterByRelation l rel = filter ((== rel) . csStatus) l @@ -509,7 +531,7 @@ addPhonePrefixH (_ ::: req) = do updateSSOIdH :: UserId ::: JSON ::: JsonRequest UserSSOId -> (Handler r) Response updateSSOIdH (uid ::: _ ::: req) = do ssoid :: UserSSOId <- parseJsonBody req - success <- lift $ Data.updateSSOId uid (Just ssoid) + success <- lift $ wrapClient $ Data.updateSSOId uid (Just ssoid) if success then do lift $ Intra.onUserEvent uid Nothing (UserUpdated ((emptyUserUpdatedData uid) {eupSSOId = Just ssoid})) @@ -518,7 +540,7 @@ updateSSOIdH (uid ::: _ ::: req) = do deleteSSOIdH :: UserId ::: JSON -> (Handler r) Response deleteSSOIdH (uid ::: _) = do - success <- lift $ Data.updateSSOId uid Nothing + success <- lift $ wrapClient $ Data.updateSSOId uid Nothing if success then do lift $ Intra.onUserEvent uid Nothing (UserUpdated ((emptyUserUpdatedData uid) {eupSSOIdRemoved = True})) @@ -528,7 +550,7 @@ deleteSSOIdH (uid ::: _) = do updateManagedByH :: UserId ::: JSON ::: JsonRequest ManagedByUpdate -> (Handler r) Response updateManagedByH (uid ::: _ ::: req) = do ManagedByUpdate managedBy <- parseJsonBody req - lift $ Data.updateManagedBy uid managedBy + lift $ wrapClient $ Data.updateManagedBy uid managedBy return empty updateRichInfoH :: UserId ::: JSON ::: JsonRequest RichInfoUpdate -> (Handler r) Response @@ -542,20 +564,20 @@ updateRichInfo uid rup = do when (richInfoSize (RichInfo (mkRichInfoAssocList richInfo)) > maxSize) $ throwStd tooLargeRichInfo -- FUTUREWORK: send an event -- Intra.onUserEvent uid (Just conn) (richInfoUpdate uid ri) - lift $ Data.updateRichInfo uid (mkRichInfoAssocList richInfo) + lift $ wrapClient $ Data.updateRichInfo uid (mkRichInfoAssocList richInfo) getRichInfoH :: UserId -> (Handler r) Response getRichInfoH uid = json <$> getRichInfo uid getRichInfo :: UserId -> (Handler r) RichInfo -getRichInfo uid = RichInfo . fromMaybe mempty <$> lift (API.lookupRichInfo uid) +getRichInfo uid = RichInfo . fromMaybe mempty <$> lift (wrapClient $ API.lookupRichInfo uid) getRichInfoMultiH :: List UserId -> (Handler r) Response getRichInfoMultiH uids = json <$> getRichInfoMulti (List.fromList uids) getRichInfoMulti :: [UserId] -> (Handler r) [(UserId, RichInfo)] getRichInfoMulti uids = - lift (API.lookupRichInfoMultiUsers uids) + lift (wrapClient $ API.lookupRichInfoMultiUsers uids) updateHandleH :: UserId ::: JSON ::: JsonRequest HandleUpdate -> (Handler r) Response updateHandleH (uid ::: _ ::: body) = empty <$ (updateHandle uid =<< parseJsonBody body) @@ -578,7 +600,7 @@ updateUserName uid (NameUpdate nameUpd) = do uupAssets = Nothing, uupAccentId = Nothing } - lift (Data.lookupUser WithPendingInvitations uid) >>= \case + lift (wrapClient $ Data.lookupUser WithPendingInvitations uid) >>= \case Just _ -> API.updateUser uid Nothing uu API.AllowSCIMUpdates !>> updateProfileError Nothing -> throwStd (errorDescriptionTypeToWai @InvalidUser) @@ -591,7 +613,7 @@ checkHandleInternalH = getContactListH :: JSON ::: UserId -> (Handler r) Response getContactListH (_ ::: uid) = do - contacts <- lift $ API.lookupContactList uid + contacts <- lift . wrapClient $ API.lookupContactList uid return $ json $ UserIds contacts -- Utilities diff --git a/services/brig/src/Brig/API/MLS/KeyPackages.hs b/services/brig/src/Brig/API/MLS/KeyPackages.hs index 0e81942d5d0..3d9bfa50874 100644 --- a/services/brig/src/Brig/API/MLS/KeyPackages.hs +++ b/services/brig/src/Brig/API/MLS/KeyPackages.hs @@ -45,7 +45,7 @@ uploadKeyPackages :: Local UserId -> ClientId -> KeyPackageUpload -> Handler r ( uploadKeyPackages lusr cid (kpuKeyPackages -> kps) = do let identity = mkClientIdentity (qUntagged lusr) cid kps' <- traverse (validateKeyPackageData identity) kps - lift $ Data.insertKeyPackages (tUnqualified lusr) cid kps' + lift . wrapClient $ Data.insertKeyPackages (tUnqualified lusr) cid kps' claimKeyPackages :: Local UserId -> Qualified UserId -> Handler r KeyPackageBundle claimKeyPackages lusr = @@ -56,7 +56,7 @@ claimKeyPackages lusr = claimLocalKeyPackages :: Local UserId -> Local UserId -> Handler r KeyPackageBundle claimLocalKeyPackages lusr target = do - clients <- map clientId <$> Data.lookupClients (tUnqualified target) + clients <- map clientId <$> wrapClientE (Data.lookupClients (tUnqualified target)) withExceptT clientError $ guardLegalhold (ProtectedUser (tUnqualified lusr)) (mkUserClients [(tUnqualified target, clients)]) lift $ @@ -65,11 +65,11 @@ claimLocalKeyPackages lusr target = do mkEntry :: ClientId -> AppIO r (Maybe KeyPackageBundleEntry) mkEntry c = runMaybeT $ - KeyPackageBundleEntry (qUntagged target) c - <$> Data.claimKeyPackage (tUnqualified target) c + uncurry (KeyPackageBundleEntry (qUntagged target) c) + <$> wrapClientM (Data.claimKeyPackage target c) countKeyPackages :: Local UserId -> ClientId -> Handler r KeyPackageCount countKeyPackages lusr c = lift $ KeyPackageCount . fromIntegral - <$> Data.countKeyPackages (tUnqualified lusr) c + <$> wrapClient (Data.countKeyPackages (tUnqualified lusr) c) diff --git a/services/brig/src/Brig/API/MLS/KeyPackages/Validation.hs b/services/brig/src/Brig/API/MLS/KeyPackages/Validation.hs index 83a9f1e94e6..02d823ed027 100644 --- a/services/brig/src/Brig/API/MLS/KeyPackages/Validation.hs +++ b/services/brig/src/Brig/API/MLS/KeyPackages/Validation.hs @@ -28,6 +28,7 @@ where import Brig.API.Error import Brig.API.Handler import Brig.App +import qualified Brig.Data.Client as Data import Brig.Options import Control.Applicative import Control.Lens (view) @@ -51,9 +52,25 @@ validateKeyPackageData identity kpd = do (throwErrorDescription (mlsProtocolError "Unsupported ciphersuite")) pure $ cipherSuiteTag (kpCipherSuite (kpTBS kp)) + + -- validate signature scheme + let ss = csSignatureScheme cs + when (signatureScheme ss /= bcSignatureScheme (kpCredential (kpTBS kp))) $ + throwErrorDescription $ + mlsProtocolError "Signature scheme incompatible with ciphersuite" + + -- authenticate signature key + key <- + fmap LBS.toStrict $ + maybe + (throwErrorDescription (mlsProtocolError "No key associated to the given identity and signature scheme")) + pure + =<< lift (wrapClient (Data.lookupMLSPublicKey (ciUser identity) (ciClient identity) ss)) + when (key /= bcSignatureKey (kpCredential (kpTBS kp))) $ + throwErrorDescription $ + mlsProtocolError "Unrecognised signature key" + -- validate signature - -- FUTUREWORK: authenticate signature key - let key = bcSignatureKey (kpCredential (kpTBS kp)) unless (csVerifySignature cs key (LBS.toStrict tbs) (kpSignature kp)) $ throwErrorDescription (mlsProtocolError "Invalid signature") -- validate credential and extensions diff --git a/services/brig/src/Brig/API/Properties.hs b/services/brig/src/Brig/API/Properties.hs index 48d5af9e9c1..e899f12e704 100644 --- a/services/brig/src/Brig/API/Properties.hs +++ b/services/brig/src/Brig/API/Properties.hs @@ -38,15 +38,15 @@ import Imports setProperty :: UserId -> ConnId -> PropertyKey -> PropertyValue -> ExceptT PropertiesDataError (AppIO r) () setProperty u c k v = do - Data.insertProperty u k v + wrapClientE $ Data.insertProperty u k v lift $ Intra.onPropertyEvent u c (PropertySet u k v) deleteProperty :: UserId -> ConnId -> PropertyKey -> (AppIO r) () deleteProperty u c k = do - Data.deleteProperty u k + wrapClient $ Data.deleteProperty u k Intra.onPropertyEvent u c (PropertyDeleted u k) clearProperties :: UserId -> ConnId -> (AppIO r) () clearProperties u c = do - Data.clearProperties u + wrapClient $ Data.clearProperties u Intra.onPropertyEvent u c (PropertiesCleared u) diff --git a/services/brig/src/Brig/API/Public.hs b/services/brig/src/Brig/API/Public.hs index 453adbb7513..f5d884f4ff8 100644 --- a/services/brig/src/Brig/API/Public.hs +++ b/services/brig/src/Brig/API/Public.hs @@ -38,8 +38,10 @@ import Brig.API.Util import qualified Brig.API.Util as API import Brig.App import qualified Brig.Calling.API as Calling +import qualified Brig.Code as Code import qualified Brig.Data.Connection as Data import qualified Brig.Data.User as Data +import qualified Brig.Data.UserKey as UserKey import qualified Brig.Docs.Swagger import qualified Brig.IO.Intra as Intra import Brig.Options hiding (internalEvents, sesQueue) @@ -63,7 +65,6 @@ import Control.Monad.Catch (throwM) import Data.Aeson hiding (json) import qualified Data.ByteString.Lazy as Lazy import qualified Data.ByteString.Lazy.Char8 as LBS -import qualified Data.Code as Code import Data.CommaSeparatedList (CommaSeparatedList (fromCommaSeparatedList)) import Data.Containers.ListUtils (nubOrd) import Data.Domain @@ -173,7 +174,7 @@ servantSitemap = userAPI :<|> selfAPI :<|> accountAPI :<|> clientAPI :<|> prekey :<|> Named @"get-user-by-handle-qualified" Handle.getHandleInfo :<|> Named @"list-users-by-unqualified-ids-or-handles" listUsersByUnqualifiedIdsOrHandles :<|> Named @"list-users-by-ids-or-handles" listUsersByIdsOrHandles - :<|> Named @"send-verification-code" (const sendVerificationCode) + :<|> Named @"send-verification-code" sendVerificationCode selfAPI :: ServerT SelfAPI (Handler r) selfAPI = @@ -521,19 +522,19 @@ clearPropertiesH (u ::: c) = lift (API.clearProperties u c) >> return empty getPropertyH :: UserId ::: Public.PropertyKey ::: JSON -> (Handler r) Response getPropertyH (u ::: k ::: _) = do - val <- lift $ API.lookupProperty u k + val <- lift . wrapClient $ API.lookupProperty u k return $ case val of Nothing -> setStatus status404 empty Just v -> json (v :: Public.PropertyValue) listPropertyKeysH :: UserId ::: JSON -> (Handler r) Response listPropertyKeysH (u ::: _) = do - keys <- lift (API.lookupPropertyKeys u) + keys <- lift $ wrapClient (API.lookupPropertyKeys u) pure $ json (keys :: [Public.PropertyKey]) listPropertyKeysAndValuesH :: UserId ::: JSON -> (Handler r) Response listPropertyKeysAndValuesH (u ::: _) = do - keysAndVals <- lift (API.lookupPropertyKeysAndValues u) + keysAndVals <- lift $ wrapClient (API.lookupPropertyKeysAndValues u) pure $ json (keysAndVals :: Public.PropertyKeysAndValues) getPrekeyUnqualifiedH :: UserId -> UserId -> ClientId -> (Handler r) Public.ClientPrekey @@ -588,7 +589,7 @@ deleteClient usr con clt body = API.rmClient usr con clt (Public.rmPassword body) !>> clientError updateClient :: UserId -> ClientId -> Public.UpdateClient -> (Handler r) () -updateClient usr clt upd = API.updateClient usr clt upd !>> clientError +updateClient usr clt upd = wrapClientE (API.updateClient usr clt upd) !>> clientError listClients :: UserId -> (Handler r) [Public.Client] listClients zusr = @@ -638,18 +639,18 @@ getRichInfo self user = do -- other user selfUser <- ifNothing (errorDescriptionTypeToWai @UserNotFound) - =<< lift (Data.lookupUser NoPendingInvitations self) + =<< lift (wrapClient $ Data.lookupUser NoPendingInvitations self) otherUser <- ifNothing (errorDescriptionTypeToWai @UserNotFound) - =<< lift (Data.lookupUser NoPendingInvitations user) + =<< lift (wrapClient $ Data.lookupUser NoPendingInvitations user) case (Public.userTeam selfUser, Public.userTeam otherUser) of (Just t1, Just t2) | t1 == t2 -> pure () _ -> throwStd insufficientTeamPermissions -- Query rich info - fromMaybe mempty <$> lift (API.lookupRichInfo user) + fromMaybe mempty <$> lift (wrapClient $ API.lookupRichInfo user) getClientPrekeys :: UserId -> ClientId -> (Handler r) [Public.PrekeyId] -getClientPrekeys usr clt = lift (API.lookupPrekeyIds usr clt) +getClientPrekeys usr clt = lift (wrapClient $ API.lookupPrekeyIds usr clt) -- | docs/reference/user/registration.md {#RefRegistration} createUser :: Public.NewUserPublic -> (Handler r) (Either Public.RegisterError Public.RegisterSuccess) @@ -688,7 +689,7 @@ createUser (Public.NewUserPublic new) = lift . runExceptT $ do for_ (liftM2 (,) userEmail epair) $ \(e, p) -> sendActivationEmail e userDisplayName p (Just userLocale) newUserTeam for_ (liftM2 (,) userPhone ppair) $ \(p, c) -> - sendActivationSms p c (Just userLocale) + wrapClient $ sendActivationSms p c (Just userLocale) for_ (liftM3 (,,) userEmail (createdUserTeam result) newUserTeam) $ \(e, ct, ut) -> sendWelcomeEmail e ct ut (Just userLocale) cok <- @@ -765,7 +766,7 @@ listUsersByIdsOrHandles self q = do where getIds :: [Handle] -> (Handler r) [Qualified UserId] getIds localHandles = do - localUsers <- catMaybes <$> traverse (lift . API.lookupHandle) localHandles + localUsers <- catMaybes <$> traverse (lift . wrapClient . API.lookupHandle) localHandles domain <- viewFederationDomain pure $ map (`Qualified` domain) localUsers byIds :: Local UserId -> [Qualified UserId] -> (Handler r) [Public.UserProfile] @@ -785,9 +786,9 @@ updateUser uid conn uu = do changePhone :: UserId -> ConnId -> Public.PhoneUpdate -> (Handler r) (Maybe Public.ChangePhoneError) changePhone u _ (Public.puPhone -> phone) = lift . exceptTToMaybe $ do (adata, pn) <- API.changePhone u phone - loc <- lift $ API.lookupLocale u + loc <- lift $ wrapClient $ API.lookupLocale u let apair = (activationKey adata, activationCode adata) - lift $ sendActivationSms pn apair loc + lift . wrapClient $ sendActivationSms pn apair loc removePhone :: UserId -> ConnId -> (Handler r) (Maybe Public.RemoveIdentityError) removePhone self conn = @@ -798,7 +799,7 @@ removeEmail self conn = lift . exceptTToMaybe $ API.removeEmail self conn checkPasswordExists :: UserId -> (Handler r) Bool -checkPasswordExists = fmap isJust . lift . API.lookupPassword +checkPasswordExists = fmap isJust . lift . wrapClient . API.lookupPassword changePassword :: UserId -> Public.PasswordChange -> (Handler r) (Maybe Public.ChangePasswordError) changePassword u cp = lift . exceptTToMaybe $ API.changePassword u cp @@ -819,7 +820,7 @@ checkHandlesH :: JSON ::: UserId ::: JsonRequest Public.CheckHandles -> (Handler checkHandlesH (_ ::: _ ::: req) = do Public.CheckHandles hs num <- parseJsonBody req let handles = mapMaybe parseHandle (fromRange hs) - free <- lift $ API.checkHandles handles (fromRange num) + free <- lift . wrapClient $ API.checkHandles handles (fromRange num) return $ json (free :: [Handle]) -- | This endpoint returns UserHandleInfo instead of UserProfile for backwards @@ -845,10 +846,10 @@ beginPasswordReset :: Public.NewPasswordReset -> (Handler r) () beginPasswordReset (Public.NewPasswordReset target) = do checkWhitelist target (u, pair) <- API.beginPasswordReset target !>> pwResetError - loc <- lift $ API.lookupLocale u + loc <- lift $ wrapClient $ API.lookupLocale u lift $ case target of Left email -> sendPasswordResetMail email pair loc - Right phone -> sendPasswordResetSms phone pair loc + Right phone -> wrapClient $ sendPasswordResetSms phone pair loc completePasswordResetH :: JSON ::: JsonRequest Public.CompletePasswordReset -> (Handler r) Response completePasswordResetH (_ ::: req) = do @@ -941,7 +942,7 @@ listConnections uid Public.GetMultiTablePageRequest {..} = do localsAndRemotes :: Local UserId -> Maybe C.PagingState -> Range 1 500 Int32 -> (Handler r) Public.ConnectionsPage localsAndRemotes self pagingState size = do - localPage <- pageToConnectionsPage Public.PagingLocals <$> Data.lookupLocalConnectionsPage self pagingState (rcast size) + localPage <- lift $ pageToConnectionsPage Public.PagingLocals <$> wrapClient (Data.lookupLocalConnectionsPage self pagingState (rcast size)) let remainingSize = fromRange size - fromIntegral (length (Public.mtpResults localPage)) if Public.mtpHasMore localPage || remainingSize <= 0 then pure localPage {Public.mtpHasMore = True} -- We haven't checked the remotes yet, so has_more must always be True here. @@ -951,7 +952,8 @@ listConnections uid Public.GetMultiTablePageRequest {..} = do remotesOnly :: Local UserId -> Maybe C.PagingState -> Int32 -> (Handler r) Public.ConnectionsPage remotesOnly self pagingState size = - pageToConnectionsPage Public.PagingRemotes <$> Data.lookupRemoteConnectionsPage self pagingState size + lift . wrapClient $ + pageToConnectionsPage Public.PagingRemotes <$> Data.lookupRemoteConnectionsPage self pagingState size getLocalConnection :: UserId -> UserId -> (Handler r) (Maybe Public.UserConnection) getLocalConnection self other = do @@ -961,7 +963,7 @@ getLocalConnection self other = do getConnection :: UserId -> Qualified UserId -> (Handler r) (Maybe Public.UserConnection) getConnection self other = do lself <- qualifyLocal self - lift $ Data.lookupConnection lself other + lift . wrapClient $ Data.lookupConnection lself other deleteUser :: UserId -> @@ -978,9 +980,9 @@ verifyDeleteUserH (r ::: _) = do updateUserEmail :: UserId -> UserId -> Public.EmailUpdate -> (Handler r) () updateUserEmail zuserId emailOwnerId (Public.EmailUpdate email) = do - maybeZuserTeamId <- lift $ Data.lookupUserTeam zuserId + maybeZuserTeamId <- lift $ wrapClient $ Data.lookupUserTeam zuserId whenM (not <$> assertHasPerm maybeZuserTeamId) $ throwStd insufficientTeamPermissions - maybeEmailOwnerTeamId <- lift $ Data.lookupUserTeam emailOwnerId + maybeEmailOwnerTeamId <- lift $ wrapClient $ Data.lookupUserTeam emailOwnerId checkSameTeam maybeZuserTeamId maybeEmailOwnerTeamId void $ API.changeSelfEmail emailOwnerId email API.AllowSCIMUpdates where @@ -1026,7 +1028,7 @@ activateH (k ::: c) = do activate :: Public.Activate -> (Handler r) ActivationRespWithStatus activate (Public.Activate tgt code dryrun) | dryrun = do - API.preverify tgt code !>> actError + wrapClientE (API.preverify tgt code) !>> actError return ActivationRespDryRun | otherwise = do result <- API.activate tgt code Nothing !>> actError @@ -1037,12 +1039,43 @@ activate (Public.Activate tgt code dryrun) respond (Just ident) first = ActivationResp $ Public.ActivationResponse ident first respond Nothing _ = ActivationRespSuccessNoIdent --- Verification - -sendVerificationCode :: (Handler r) () -sendVerificationCode = - case Public.TeamFeatureSndFPasswordChallengeNotImplemented of +sendVerificationCode :: Public.SendVerificationCode -> (Handler r) () +sendVerificationCode req = do + let email = Public.svcEmail req + let action = Public.svcAction req + mbAccount <- getAccount email + featureEnabled <- getFeatureStatus mbAccount + case (mbAccount, featureEnabled) of + (Just account, True) -> do + gen <- Code.mk6DigitGen $ Code.ForEmail email + timeout <- setVerificationTimeout <$> view settings + code <- + Code.generate + gen + (Code.scopeFromAction action) + (Code.Retries 3) + timeout + (Just $ toUUID $ Public.userId $ accountUser account) + wrapClientE $ Code.insert code + sendMail email (Code.codeValue code) (Just $ Public.userLocale $ accountUser account) action _ -> pure () + where + getAccount :: Public.Email -> (Handler r) (Maybe UserAccount) + getAccount email = lift $ do + mbUserId <- wrapClient . UserKey.lookupKey $ UserKey.userEmailKey email + join <$> wrapClient (Data.lookupAccount `traverse` mbUserId) + + sendMail :: Public.Email -> Code.Value -> Maybe Public.Locale -> Public.VerificationAction -> (Handler r) () + sendMail email value mbLocale = + lift . \case + Public.CreateScimToken -> sendCreateScimTokenVerificationMail email value mbLocale + Public.Login -> sendLoginVerificationMail email value mbLocale + Public.DeleteTeam -> sendTeamDeletionVerificationMail email value mbLocale + + getFeatureStatus :: Maybe UserAccount -> (Handler r) Bool + getFeatureStatus mbAccount = do + mbStatusEnabled <- lift $ Intra.getVerificationCodeEnabled `traverse` (Public.userTeam <$> accountUser =<< mbAccount) + pure $ fromMaybe False mbStatusEnabled -- Deprecated diff --git a/services/brig/src/Brig/API/Types.hs b/services/brig/src/Brig/API/Types.hs index 663411d7937..a188a3df7fe 100644 --- a/services/brig/src/Brig/API/Types.hs +++ b/services/brig/src/Brig/API/Types.hs @@ -151,6 +151,13 @@ data LoginError | LoginPendingActivation | LoginThrottled RetryAfter | LoginBlocked RetryAfter + | LoginCodeRequired + | LoginCodeInvalid + +data VerificationCodeError + = VerificationCodeRequired + | VerificationCodeNoPendingCode + | VerificationCodeNoEmail data ChangeEmailError = InvalidNewEmail !Email !String @@ -176,6 +183,8 @@ data ClientError | ClientFederationError FederationError | ClientCapabilitiesCannotBeRemoved | ClientMissingLegalholdConsent + | ClientCodeAuthenticationFailed + | ClientCodeAuthenticationRequired data DeleteUserError = DeleteUserInvalid @@ -192,7 +201,7 @@ data AccountStatusError -- Exceptions -- | A user name was unexpectedly not found for an existing user ID. -data UserDisplayNameNotFound = UserDisplayNameNotFound !UserId +newtype UserDisplayNameNotFound = UserDisplayNameNotFound UserId deriving (Typeable) instance Exception UserDisplayNameNotFound @@ -200,7 +209,7 @@ instance Exception UserDisplayNameNotFound instance Show UserDisplayNameNotFound where show (UserDisplayNameNotFound uid) = "User name not found for user: " ++ show uid -data UserProfileNotFound = UserProfileNotFound !UserId +newtype UserProfileNotFound = UserProfileNotFound UserId deriving (Typeable) instance Exception UserProfileNotFound diff --git a/services/brig/src/Brig/API/User.hs b/services/brig/src/Brig/API/User.hs index 1598be9006a..9cf528bebc5 100644 --- a/services/brig/src/Brig/API/User.hs +++ b/services/brig/src/Brig/API/User.hs @@ -127,6 +127,7 @@ import Brig.User.Handle import Brig.User.Handle.Blacklist import Brig.User.Phone import qualified Brig.User.Search.TeamSize as TeamSize +import Cassandra import Control.Arrow ((&&&)) import Control.Error import Control.Lens (view, (^.)) @@ -184,8 +185,8 @@ identityErrorToBrigError = \case verifyUniquenessAndCheckBlacklist :: UserKey -> ExceptT IdentityError (AppIO r) () verifyUniquenessAndCheckBlacklist uk = do - checkKey Nothing uk - blacklisted <- lift $ Blacklist.exists uk + wrapClientE $ checkKey Nothing uk + blacklisted <- lift $ wrapClient $ Blacklist.exists uk when blacklisted $ throwE (foldKey (const IdentityErrorBlacklistedEmail) (const IdentityErrorBlacklistedPhone) uk) where @@ -216,7 +217,7 @@ createUser new = do Nothing -> pure (Nothing, Nothing, Nothing) let mbInv = Team.inInvitation . fst <$> teamInvitation - mbExistingAccount <- lift $ join <$> for mbInv (\(Id uuid) -> Data.lookupAccount (Id uuid)) + mbExistingAccount <- lift $ join <$> for mbInv (\(Id uuid) -> wrapClient $ Data.lookupAccount (Id uuid)) let (new', mbHandle) = case mbExistingAccount of Nothing -> @@ -241,13 +242,13 @@ createUser new = do -- Create account account <- lift $ do - (account, pw) <- newAccount new' mbInv tid mbHandle + (account, pw) <- wrapClient $ newAccount new' mbInv tid mbHandle let uid = userId (accountUser account) Log.debug $ field "user" (toByteString uid) . field "action" (Log.val "User.createUser") Log.info $ field "user" (toByteString uid) . msg (val "Creating user") - Data.insertAccount account Nothing pw False + wrapClient $ Data.insertAccount account Nothing pw False Intra.createSelfConv uid Intra.onUserEvent uid Nothing (UserCreated (accountUser account)) @@ -308,7 +309,7 @@ createUser new = do maybe (throwE RegisterErrorInvalidPhone) return - =<< lift (validatePhone p) + =<< lift (wrapClient $ validatePhone p) for_ (catMaybes [userEmailKey <$> email, userPhoneKey <$> phone]) $ \k -> verifyUniquenessAndCheckBlacklist k !>> identityErrorToRegisterError @@ -318,9 +319,9 @@ createUser new = do findTeamInvitation :: Maybe UserKey -> InvitationCode -> ExceptT RegisterError (AppIO r) (Maybe (Team.Invitation, Team.InvitationInfo, TeamId)) findTeamInvitation Nothing _ = throwE RegisterErrorMissingIdentity findTeamInvitation (Just e) c = - lift (Team.lookupInvitationInfo c) >>= \case + lift (wrapClient $ Team.lookupInvitationInfo c) >>= \case Just ii -> do - inv <- lift $ Team.lookupInvitation (Team.iiTeam ii) (Team.iiInvId ii) + inv <- lift . wrapClient $ Team.lookupInvitation (Team.iiTeam ii) (Team.iiInvId ii) case (inv, Team.inInviteeEmail <$> inv) of (Just invite, Just em) | e == userEmailKey em -> do @@ -351,7 +352,7 @@ createUser new = do ExceptT RegisterError (AppT r IO) () acceptTeamInvitation account inv ii uk ident = do let uid = userId (accountUser account) - ok <- lift $ Data.claimKey uk uid + ok <- lift . wrapClient $ Data.claimKey uk uid unless ok $ throwE RegisterErrorUserKeyExists let minvmeta :: (Maybe (UserId, UTCTimeMillis), Team.Role) @@ -360,14 +361,15 @@ createUser new = do unless added $ throwE RegisterErrorTooManyTeamMembers lift $ do - activateUser uid ident -- ('insertAccount' sets column activated to False; here it is set to True.) + wrapClient $ activateUser uid ident -- ('insertAccount' sets column activated to False; here it is set to True.) void $ onActivated (AccountActivated account) Log.info $ field "user" (toByteString uid) . field "team" (toByteString $ Team.iiTeam ii) . msg (val "Accepting invitation") - Data.usersPendingActivationRemove uid - Team.deleteInvitation (Team.inTeam inv) (Team.inInvitation inv) + wrapClient $ do + Data.usersPendingActivationRemove uid + Team.deleteInvitation (Team.inTeam inv) (Team.inInvitation inv) addUserToTeamSSO :: UserAccount -> TeamId -> UserIdentity -> ExceptT RegisterError (AppIO r) CreateUserTeam addUserToTeamSSO account tid ident = do @@ -376,7 +378,7 @@ createUser new = do unless added $ throwE RegisterErrorTooManyTeamMembers lift $ do - activateUser uid ident + wrapClient $ activateUser uid ident void $ onActivated (AccountActivated account) Log.info $ field "user" (toByteString uid) @@ -391,7 +393,7 @@ createUser new = do fmap join . for (userEmailKey <$> email) $ \ek -> case newUserEmailCode new of Nothing -> do timeout <- setActivationTimeout <$> view settings - edata <- lift $ Data.newActivation ek timeout (Just uid) + edata <- lift . wrapClient $ Data.newActivation ek timeout (Just uid) Log.info $ field "user" (toByteString uid) . field "activation.key" (toByteString $ activationKey edata) @@ -410,7 +412,7 @@ createUser new = do fmap join . for (userPhoneKey <$> phone) $ \pk -> case newUserPhoneCode new of Nothing -> do timeout <- setActivationTimeout <$> view settings - pdata <- lift $ Data.newActivation pk timeout (Just uid) + pdata <- lift . wrapClient $ Data.newActivation pk timeout (Just uid) Log.info $ field "user" (toByteString uid) . field "activation.key" (toByteString $ activationKey pdata) @@ -424,7 +426,7 @@ createUser new = do initAccountFeatureConfig :: UserId -> (AppIO r) () initAccountFeatureConfig uid = do mbCciDefNew <- view (settings . getAfcConferenceCallingDefNewMaybe) - forM_ mbCciDefNew $ Data.updateFeatureConferenceCalling uid . Just + forM_ mbCciDefNew $ wrapClient . Data.updateFeatureConferenceCalling uid . Just -- | 'createUser' is becoming hard to maintian, and instead of adding more case distinctions -- all over the place there, we add a new function that handles just the one new flow where @@ -434,7 +436,7 @@ createUserInviteViaScim uid (NewUserScimInvitation tid loc name rawEmail) = do email <- either (const . throwE . Error.StdError $ errorDescriptionTypeToWai @InvalidEmail) pure (validateEmail rawEmail) let emKey = userEmailKey email verifyUniquenessAndCheckBlacklist emKey !>> identityErrorToBrigError - account <- lift $ newAccountInviteViaScim uid tid loc name email + account <- lift . wrapClient $ newAccountInviteViaScim uid tid loc name email Log.debug $ field "user" (toByteString . userId . accountUser $ account) . field "action" (Log.val "User.createUserInviteViaScim") -- add the expiry table entry first! (if brig creates an account, and then crashes before @@ -443,14 +445,14 @@ createUserInviteViaScim uid (NewUserScimInvitation tid loc name rawEmail) = do ttl <- setTeamInvitationTimeout <$> view settings now <- liftIO =<< view currentTime pure $ addUTCTime (realToFrac ttl) now - lift $ Data.usersPendingActivationAdd (UserPendingActivation uid expiresAt) + lift . wrapClient $ Data.usersPendingActivationAdd (UserPendingActivation uid expiresAt) let activated = -- treating 'PendingActivation' as 'Active', but then 'Brig.Data.User.toIdentity' -- would not produce an identity, and so we won't have the email address to construct -- the SCIM user. True - lift $ Data.insertAccount account Nothing Nothing activated + lift . wrapClient $ Data.insertAccount account Nothing Nothing activated return account @@ -471,7 +473,7 @@ checkRestrictedUserCreation new = do updateUser :: UserId -> Maybe ConnId -> UserUpdate -> AllowSCIMUpdates -> ExceptT UpdateProfileError (AppIO r) () updateUser uid mconn uu allowScim = do for_ (uupName uu) $ \newName -> do - mbUser <- lift $ Data.lookupUser WithPendingInvitations uid + mbUser <- lift . wrapClient $ Data.lookupUser WithPendingInvitations uid user <- maybe (throwE ProfileNotFound) pure mbUser unless ( userManagedBy user /= ManagedByScim @@ -480,7 +482,7 @@ updateUser uid mconn uu allowScim = do ) $ throwE DisplayNameManagedByScim lift $ do - Data.updateUser uid uu + wrapClient $ Data.updateUser uid uu Intra.onUserEvent uid mconn (profileUpdated uid uu) ------------------------------------------------------------------------------- @@ -488,7 +490,7 @@ updateUser uid mconn uu allowScim = do changeLocale :: UserId -> ConnId -> LocaleUpdate -> (AppIO r) () changeLocale uid conn (LocaleUpdate loc) = do - Data.updateLocale uid loc + wrapClient $ Data.updateLocale uid loc Intra.onUserEvent uid (Just conn) (localeUpdate uid loc) ------------------------------------------------------------------------------- @@ -496,7 +498,7 @@ changeLocale uid conn (LocaleUpdate loc) = do changeManagedBy :: UserId -> ConnId -> ManagedByUpdate -> (AppIO r) () changeManagedBy uid conn (ManagedByUpdate mb) = do - Data.updateManagedBy uid mb + wrapClient $ Data.updateManagedBy uid mb Intra.onUserEvent uid (Just conn) (managedByUpdate uid mb) -------------------------------------------------------------------------------- @@ -506,7 +508,7 @@ changeHandle :: UserId -> Maybe ConnId -> Handle -> AllowSCIMUpdates -> ExceptT changeHandle uid mconn hdl allowScim = do when (isBlacklistedHandle hdl) $ throwE ChangeHandleInvalid - usr <- lift $ Data.lookupUser WithPendingInvitations uid + usr <- lift $ wrapClient $ Data.lookupUser WithPendingInvitations uid case usr of Nothing -> throwE ChangeHandleNoIdentity Just u -> do @@ -521,7 +523,7 @@ changeHandle uid mconn hdl allowScim = do claim u = do unless (isJust (userIdentity u)) $ throwE ChangeHandleNoIdentity - claimed <- lift $ claimHandle (userId u) (userHandle u) hdl + claimed <- lift . wrapClient $ claimHandle (userId u) (userHandle u) hdl unless claimed $ throwE ChangeHandleExists lift $ Intra.onUserEvent uid mconn (handleUpdated uid hdl) @@ -537,7 +539,7 @@ data CheckHandleResp checkHandle :: Text -> API.Handler r CheckHandleResp checkHandle uhandle = do xhandle <- validateHandle uhandle - owner <- lift $ lookupHandle xhandle + owner <- lift . wrapClient $ lookupHandle xhandle if | isJust owner -> -- Handle is taken (=> getHandleInfo will return 200) @@ -556,7 +558,7 @@ checkHandle uhandle = do -------------------------------------------------------------------------------- -- Check Handles -checkHandles :: [Handle] -> Word -> (AppIO r) [Handle] +checkHandles :: MonadClient m => [Handle] -> Word -> m [Handle] checkHandles check num = reverse <$> collectFree [] check num where collectFree free _ 0 = return free @@ -601,15 +603,15 @@ changeEmail u email allowScim = do return (validateEmail email) let ek = userEmailKey em - blacklisted <- lift $ Blacklist.exists ek + blacklisted <- lift . wrapClient $ Blacklist.exists ek when blacklisted $ throwE (ChangeBlacklistedEmail email) - available <- lift $ Data.keyAvailable ek (Just u) + available <- lift . wrapClient $ Data.keyAvailable ek (Just u) unless available $ throwE $ EmailExists email - usr <- maybe (throwM $ UserProfileNotFound u) return =<< lift (Data.lookupUser WithPendingInvitations u) - case join (emailIdentity <$> userIdentity usr) of + usr <- maybe (throwM $ UserProfileNotFound u) return =<< lift (wrapClient $ Data.lookupUser WithPendingInvitations u) + case emailIdentity =<< userIdentity usr of -- The user already has an email address and the new one is exactly the same Just current | current == em -> return ChangeEmailIdempotent _ -> do @@ -619,7 +621,7 @@ changeEmail u email allowScim = do ) $ throwE EmailManagedByScim timeout <- setActivationTimeout <$> view settings - act <- lift $ Data.newActivation ek timeout (Just u) + act <- lift . wrapClient $ Data.newActivation ek timeout (Just u) return $ ChangeEmailNeedsActivation (usr, act, em) ------------------------------------------------------------------------------- @@ -631,20 +633,20 @@ changePhone u phone = do maybe (throwE InvalidNewPhone) return - =<< lift (validatePhone phone) + =<< lift (wrapClient $ validatePhone phone) let pk = userPhoneKey canonical - available <- lift $ Data.keyAvailable pk (Just u) + available <- lift . wrapClient $ Data.keyAvailable pk (Just u) unless available $ throwE PhoneExists timeout <- setActivationTimeout <$> view settings - blacklisted <- lift $ Blacklist.exists pk + blacklisted <- lift . wrapClient $ Blacklist.exists pk when blacklisted $ throwE BlacklistedNewPhone -- check if any prefixes of this phone number are blocked - prefixExcluded <- lift $ Blacklist.existsAnyPrefix canonical + prefixExcluded <- lift . wrapClient $ Blacklist.existsAnyPrefix canonical when prefixExcluded $ throwE BlacklistedNewPhone - act <- lift $ Data.newActivation pk timeout (Just u) + act <- lift . wrapClient $ Data.newActivation pk timeout (Just u) return (act, canonical) ------------------------------------------------------------------------------- @@ -655,8 +657,8 @@ removeEmail uid conn = do ident <- lift $ fetchUserIdentity uid case ident of Just (FullIdentity e _) -> lift $ do - deleteKey $ userEmailKey e - Data.deleteEmail uid + wrapClient . deleteKey $ userEmailKey e + wrapClient $ Data.deleteEmail uid Intra.onUserEvent uid (Just conn) (emailRemoved uid e) Just _ -> throwE LastIdentity Nothing -> throwE NoIdentity @@ -669,12 +671,12 @@ removePhone uid conn = do ident <- lift $ fetchUserIdentity uid case ident of Just (FullIdentity _ p) -> do - pw <- lift $ Data.lookupPassword uid + pw <- lift . wrapClient $ Data.lookupPassword uid unless (isJust pw) $ throwE NoPassword lift $ do - deleteKey $ userPhoneKey p - Data.deletePhone uid + wrapClient . deleteKey $ userPhoneKey p + wrapClient $ Data.deletePhone uid Intra.onUserEvent uid (Just conn) (phoneRemoved uid p) Just _ -> throwE LastIdentity Nothing -> throwE NoIdentity @@ -682,10 +684,10 @@ removePhone uid conn = do ------------------------------------------------------------------------------- -- Forcefully revoke a verified identity -revokeIdentity :: Either Email Phone -> (AppIO r) () +revokeIdentity :: Either Email Phone -> AppIO r () revokeIdentity key = do let uk = either userEmailKey userPhoneKey key - mu <- Data.lookupKey uk + mu <- wrapClient $ Data.lookupKey uk case mu of Nothing -> return () Just u -> @@ -693,18 +695,20 @@ revokeIdentity key = do Just (FullIdentity _ _) -> revokeKey u uk Just (EmailIdentity e) | Left e == key -> do revokeKey u uk - Data.deactivateUser u + wrapClient $ Data.deactivateUser u Just (PhoneIdentity p) | Right p == key -> do revokeKey u uk - Data.deactivateUser u + wrapClient $ Data.deactivateUser u _ -> return () where + revokeKey :: UserId -> UserKey -> AppIO r () revokeKey u uk = do - deleteKey uk - foldKey - (\(_ :: Email) -> Data.deleteEmail u) - (\(_ :: Phone) -> Data.deletePhone u) - uk + wrapClient $ deleteKey uk + wrapClient $ + foldKey + (\(_ :: Email) -> Data.deleteEmail u) + (\(_ :: Phone) -> Data.deletePhone u) + uk Intra.onUserEvent u Nothing $ foldKey (emailRemoved u) @@ -719,15 +723,15 @@ changeAccountStatus usrs status = do e <- ask ev <- case status of Active -> return UserResumed - Suspended -> liftIO $ mapConcurrently (runAppT e . revokeAllCookies) usrs >> return UserSuspended + Suspended -> liftIO $ mapConcurrently (runAppT e . wrapClient . revokeAllCookies) usrs >> return UserSuspended Deleted -> throwE InvalidAccountStatus Ephemeral -> throwE InvalidAccountStatus PendingInvitation -> throwE InvalidAccountStatus - liftIO $ mapConcurrently_ (runAppT e . (update ev)) usrs + liftIO $ mapConcurrently_ (runAppT e . update ev) usrs where update :: (UserId -> UserEvent) -> UserId -> (AppIO r) () update ev u = do - Data.updateStatus u status + wrapClient $ Data.updateStatus u status Intra.onUserEvent u Nothing (ev u) suspendAccount :: HasCallStack => List1 UserId -> (AppIO r) () @@ -757,12 +761,12 @@ activateWithCurrency :: Maybe Currency.Alpha -> ExceptT ActivationError (AppIO r) ActivationResult activateWithCurrency tgt code usr cur = do - key <- mkActivationKey tgt + key <- wrapClientE $ mkActivationKey tgt Log.info $ field "activation.key" (toByteString key) . field "activation.code" (toByteString code) . msg (val "Activating") - event <- Data.activateKey key code usr + event <- wrapClientE $ Data.activateKey key code usr case event of Nothing -> return ActivationPass Just e -> do @@ -776,7 +780,13 @@ activateWithCurrency tgt code usr cur = do tid <- Intra.getTeamId uid for_ tid $ \t -> Intra.changeTeamStatus t Team.Active cur -preverify :: ActivationTarget -> ActivationCode -> ExceptT ActivationError (AppIO r) () +preverify :: + ( MonadClient m, + MonadReader Env m + ) => + ActivationTarget -> + ActivationCode -> + ExceptT ActivationError m () preverify tgt code = do key <- mkActivationKey tgt void $ Data.verifyCode key code @@ -804,14 +814,14 @@ sendActivationCode emailOrPhone loc call = case emailOrPhone of (const . throwE . InvalidRecipient $ userEmailKey email) (return . userEmailKey) (validateEmail email) - exists <- lift $ isJust <$> Data.lookupKey ek + exists <- lift $ isJust <$> wrapClient (Data.lookupKey ek) when exists $ throwE $ UserKeyInUse ek - blacklisted <- lift $ Blacklist.exists ek + blacklisted <- lift . wrapClient $ Blacklist.exists ek when blacklisted $ throwE (ActivationBlacklistedUserKey ek) - uc <- lift $ Data.lookupActivationCode ek + uc <- lift . wrapClient $ Data.lookupActivationCode ek case uc of Nothing -> sendVerificationEmail ek Nothing -- Fresh code request, no user Just (Nothing, c) -> sendVerificationEmail ek (Just c) -- Re-requesting existing code @@ -822,26 +832,26 @@ sendActivationCode emailOrPhone loc call = case emailOrPhone of maybe (throwE $ InvalidRecipient (userPhoneKey phone)) return - =<< lift (validatePhone phone) + =<< lift (wrapClient $ validatePhone phone) let pk = userPhoneKey canonical - exists <- lift $ isJust <$> Data.lookupKey pk + exists <- lift $ isJust <$> wrapClient (Data.lookupKey pk) when exists $ throwE $ UserKeyInUse pk - blacklisted <- lift $ Blacklist.exists pk + blacklisted <- lift . wrapClient $ Blacklist.exists pk when blacklisted $ throwE (ActivationBlacklistedUserKey pk) -- check if any prefixes of this phone number are blocked - prefixExcluded <- lift $ Blacklist.existsAnyPrefix canonical + prefixExcluded <- lift . wrapClient $ Blacklist.existsAnyPrefix canonical when prefixExcluded $ throwE (ActivationBlacklistedUserKey pk) - c <- lift $ fmap snd <$> Data.lookupActivationCode pk - p <- mkPair pk c Nothing + c <- lift . wrapClient $ fmap snd <$> Data.lookupActivationCode pk + p <- wrapClientE $ mkPair pk c Nothing void . forPhoneKey pk $ \ph -> lift $ if call - then sendActivationCall ph p loc - else sendActivationSms ph p loc + then wrapClient $ sendActivationCall ph p loc + else wrapClient $ sendActivationSms ph p loc where notFound = throwM . UserDisplayNameNotFound mkPair k c u = do @@ -852,15 +862,15 @@ sendActivationCode emailOrPhone loc call = case emailOrPhone of dat <- Data.newActivation k timeout u return (activationKey dat, activationCode dat) sendVerificationEmail ek uc = do - p <- mkPair ek uc Nothing + p <- wrapClientE $ mkPair ek uc Nothing void . forEmailKey ek $ \em -> lift $ sendVerificationMail em p loc sendActivationEmail ek uc uid = do -- FUTUREWORK(fisx): we allow for 'PendingInvitations' here, but I'm not sure this -- top-level function isn't another piece of a deprecated onboarding flow? - u <- maybe (notFound uid) return =<< lift (Data.lookupUser WithPendingInvitations uid) - p <- mkPair ek (Just uc) (Just uid) + u <- maybe (notFound uid) return =<< lift (wrapClient $ Data.lookupUser WithPendingInvitations uid) + p <- wrapClientE $ mkPair ek (Just uc) (Just uid) let ident = userIdentity u name = userDisplayName u loc' = loc <|> Just (userLocale u) @@ -878,7 +888,7 @@ sendActivationCode emailOrPhone loc call = case emailOrPhone of _otherwise -> sendActivationMail em name p loc' ident -mkActivationKey :: ActivationTarget -> ExceptT ActivationError (AppIO r) ActivationKey +mkActivationKey :: (MonadClient m, MonadReader Env m) => ActivationTarget -> ExceptT ActivationError m ActivationKey mkActivationKey (ActivateKey k) = return k mkActivationKey (ActivateEmail e) = do ek <- @@ -900,44 +910,44 @@ mkActivationKey (ActivatePhone p) = do changePassword :: UserId -> PasswordChange -> ExceptT ChangePasswordError (AppIO r) () changePassword uid cp = do - activated <- lift $ Data.isActivated uid + activated <- lift . wrapClient $ Data.isActivated uid unless activated $ throwE ChangePasswordNoIdentity - currpw <- lift $ Data.lookupPassword uid + currpw <- lift . wrapClient $ Data.lookupPassword uid let newpw = cpNewPassword cp case (currpw, cpOldPassword cp) of - (Nothing, _) -> lift $ Data.updatePassword uid newpw + (Nothing, _) -> lift . wrapClient $ Data.updatePassword uid newpw (Just _, Nothing) -> throwE InvalidCurrentPassword (Just pw, Just pw') -> do unless (verifyPassword pw' pw) $ throwE InvalidCurrentPassword when (verifyPassword newpw pw) $ throwE ChangePasswordMustDiffer - lift $ Data.updatePassword uid newpw >> revokeAllCookies uid + lift $ wrapClient (Data.updatePassword uid newpw) >> wrapClient (revokeAllCookies uid) beginPasswordReset :: Either Email Phone -> ExceptT PasswordResetError (AppIO r) (UserId, PasswordResetPair) beginPasswordReset target = do let key = either userEmailKey userPhoneKey target - user <- lift (Data.lookupKey key) >>= maybe (throwE InvalidPasswordResetKey) return + user <- lift (wrapClient $ Data.lookupKey key) >>= maybe (throwE InvalidPasswordResetKey) return Log.debug $ field "user" (toByteString user) . field "action" (Log.val "User.beginPasswordReset") - status <- lift $ Data.lookupStatus user + status <- lift . wrapClient $ Data.lookupStatus user unless (status == Just Active) $ throwE InvalidPasswordResetKey - code <- lift $ Data.lookupPasswordResetCode user + code <- lift . wrapClient $ Data.lookupPasswordResetCode user when (isJust code) $ throwE (PasswordResetInProgress Nothing) - (user,) <$> lift (Data.createPasswordResetCode user target) + (user,) <$> lift (wrapClient $ Data.createPasswordResetCode user target) completePasswordReset :: PasswordResetIdentity -> PasswordResetCode -> PlainTextPassword -> ExceptT PasswordResetError (AppIO r) () completePasswordReset ident code pw = do key <- mkPasswordResetKey ident - muid :: Maybe UserId <- lift $ Data.verifyPasswordResetCode (key, code) + muid :: Maybe UserId <- lift . wrapClient $ Data.verifyPasswordResetCode (key, code) case muid of Nothing -> throwE InvalidPasswordResetCode Just uid -> do Log.debug $ field "user" (toByteString uid) . field "action" (Log.val "User.completePasswordReset") checkNewIsDifferent uid pw - lift $ do + lift . wrapClient $ do Data.updatePassword uid pw Data.deletePasswordResetCode key revokeAllCookies uid @@ -946,7 +956,7 @@ completePasswordReset ident code pw = do -- If the two are the same, throw an error. If no current password can be found, do nothing. checkNewIsDifferent :: UserId -> PlainTextPassword -> ExceptT PasswordResetError (AppIO r) () checkNewIsDifferent uid pw = do - mcurrpw <- lift $ Data.lookupPassword uid + mcurrpw <- lift . wrapClient $ Data.lookupPassword uid case mcurrpw of Just currpw | verifyPassword pw currpw -> throwE ResetPasswordMustDiffer _ -> pure () @@ -954,8 +964,8 @@ checkNewIsDifferent uid pw = do mkPasswordResetKey :: PasswordResetIdentity -> ExceptT PasswordResetError (AppIO r) PasswordResetKey mkPasswordResetKey ident = case ident of PasswordResetIdentityKey k -> return k - PasswordResetEmailIdentity e -> user (userEmailKey e) >>= liftIO . Data.mkPasswordResetKey - PasswordResetPhoneIdentity p -> user (userPhoneKey p) >>= liftIO . Data.mkPasswordResetKey + PasswordResetEmailIdentity e -> wrapClientE (user (userEmailKey e)) >>= liftIO . Data.mkPasswordResetKey + PasswordResetPhoneIdentity p -> wrapClientE (user (userPhoneKey p)) >>= liftIO . Data.mkPasswordResetKey where user uk = lift (Data.lookupKey uk) >>= maybe (throwE InvalidPasswordResetKey) return @@ -972,7 +982,7 @@ mkPasswordResetKey ident = case ident of -- TODO: communicate deletions of SSO users to SSO service. deleteUser :: UserId -> Maybe PlainTextPassword -> ExceptT DeleteUserError (AppIO r) (Maybe Timeout) deleteUser uid pwd = do - account <- lift $ Data.lookupAccount uid + account <- lift . wrapClient $ Data.lookupAccount uid case account of Nothing -> throwE DeleteUserInvalid Just a -> case accountStatus a of @@ -1006,7 +1016,7 @@ deleteUser uid pwd = do Log.info $ field "user" (toByteString uid) . msg (val "Attempting account deletion with a password") - actual <- lift $ Data.lookupPassword uid + actual <- lift . wrapClient $ Data.lookupPassword uid case actual of Nothing -> throwE DeleteUserInvalidPassword Just p -> do @@ -1015,7 +1025,7 @@ deleteUser uid pwd = do lift $ deleteAccount a >> return Nothing sendCode a target = do gen <- Code.mkGen (either Code.ForEmail Code.ForPhone target) - pending <- lift $ Code.lookup (Code.genKey gen) Code.AccountDeletion + pending <- lift . wrapClient $ Code.lookup (Code.genKey gen) Code.AccountDeletion case pending of Just c -> throwE $! DeleteUserPendingCode (Code.codeTTL c) Nothing -> do @@ -1029,16 +1039,16 @@ deleteUser uid pwd = do (Code.Retries 3) (Code.Timeout 600) (Just (toUUID uid)) - Code.insert c + wrapClientE $ Code.insert c let k = Code.codeKey c let v = Code.codeValue c let l = userLocale (accountUser a) let n = userDisplayName (accountUser a) either (\e -> lift $ sendDeletionEmail n e k v l) - (\p -> lift $ sendDeletionSms p k v l) + (\p -> lift $ wrapClient $ sendDeletionSms p k v l) target - `onException` Code.delete k Code.AccountDeletion + `onException` wrapClientE (Code.delete k Code.AccountDeletion) return $! Just $! Code.codeTTL c -- | Conclude validation and scheduling of user's deletion request that was initiated in @@ -1047,11 +1057,11 @@ verifyDeleteUser :: VerifyDeleteUser -> ExceptT DeleteUserError (AppIO r) () verifyDeleteUser d = do let key = verifyDeleteUserKey d let code = verifyDeleteUserCode d - c <- lift $ Code.verify key Code.AccountDeletion code + c <- lift . wrapClient $ Code.verify key Code.AccountDeletion code a <- maybe (throwE DeleteUserInvalidCode) return (Code.codeAccount =<< c) - account <- lift $ Data.lookupAccount (Id a) + account <- lift . wrapClient $ Data.lookupAccount (Id a) for_ account $ lift . deleteAccount - lift $ Code.delete key Code.AccountDeletion + lift . wrapClient $ Code.delete key Code.AccountDeletion -- | Internal deletion without validation. Called via @delete /i/user/:uid@, or indirectly -- via deleting self. @@ -1062,21 +1072,21 @@ deleteAccount account@(accountUser -> user) = do let uid = userId user Log.info $ field "user" (toByteString uid) . msg (val "Deleting account") -- Free unique keys - for_ (userEmail user) $ deleteKey . userEmailKey - for_ (userPhone user) $ deleteKey . userPhoneKey - for_ (userHandle user) $ freeHandle (userId user) + for_ (userEmail user) $ wrapClient . deleteKey . userEmailKey + for_ (userPhone user) $ wrapClient . deleteKey . userPhoneKey + for_ (userHandle user) $ wrapClient . freeHandle (userId user) -- Wipe data - Data.clearProperties uid + wrapClient $ Data.clearProperties uid tombstone <- mkTombstone - Data.insertAccount tombstone Nothing Nothing False + wrapClient $ Data.insertAccount tombstone Nothing Nothing False Intra.rmUser uid (userAssets user) - Data.lookupClients uid >>= mapM_ (Data.rmClient uid . clientId) + wrapClient (Data.lookupClients uid) >>= mapM_ (wrapClient . Data.rmClient uid . clientId) luid <- qualifyLocal uid Intra.onUserEvent uid Nothing (UserDeleted (qUntagged luid)) -- Note: Connections can only be deleted afterwards, since -- they need to be notified. Data.deleteConnections uid - revokeAllCookies uid + wrapClient $ revokeAllCookies uid where mkTombstone = do defLoc <- setDefaultUserLocale <$> view settings @@ -1098,7 +1108,10 @@ deleteAccount account@(accountUser -> user) = do ------------------------------------------------------------------------------- -- Lookups -lookupActivationCode :: Either Email Phone -> (AppIO r) (Maybe ActivationPair) +lookupActivationCode :: + (MonadIO m, MonadClient m) => + Either Email Phone -> + m (Maybe ActivationPair) lookupActivationCode emailOrPhone = do let uk = either userEmailKey userPhoneKey emailOrPhone k <- liftIO $ Data.mkActivationKey uk @@ -1108,12 +1121,12 @@ lookupActivationCode emailOrPhone = do lookupPasswordResetCode :: Either Email Phone -> (AppIO r) (Maybe PasswordResetPair) lookupPasswordResetCode emailOrPhone = do let uk = either userEmailKey userPhoneKey emailOrPhone - usr <- Data.lookupKey uk + usr <- wrapClient $ Data.lookupKey uk case usr of Nothing -> return Nothing Just u -> do k <- liftIO $ Data.mkPasswordResetKey u - c <- Data.lookupPasswordResetCode u + c <- wrapClient $ Data.lookupPasswordResetCode u return $ (k,) <$> c deleteUserNoVerify :: UserId -> (AppIO r) () @@ -1131,7 +1144,7 @@ deleteUsersNoVerify uids = do -- | Garbage collect users if they're ephemeral and they have expired. -- Always returns the user (deletion itself is delayed) userGC :: User -> (AppIO r) User -userGC u = case (userExpire u) of +userGC u = case userExpire u of Nothing -> return u (Just (fromUTCTimeMillis -> e)) -> do now <- liftIO =<< view currentTime @@ -1159,8 +1172,8 @@ lookupProfiles :: [Qualified UserId] -> ExceptT FederationError (AppIO r) [UserProfile] lookupProfiles self others = - fmap concat $ - traverseConcurrentlyWithErrors + concat + <$> traverseConcurrentlyWithErrors (lookupProfilesFromDomain self) (bucketQualified others) @@ -1186,9 +1199,9 @@ lookupLocalProfiles :: [UserId] -> (AppIO r) [UserProfile] lookupLocalProfiles requestingUser others = do - users <- Data.lookupUsers NoPendingInvitations others >>= mapM userGC + users <- wrapClient (Data.lookupUsers NoPendingInvitations others) >>= mapM userGC css <- case requestingUser of - Just localReqUser -> toMap <$> Data.lookupConnectionStatus (map userId users) [localReqUser] + Just localReqUser -> toMap <$> wrapClient (Data.lookupConnectionStatus (map userId users) [localReqUser]) Nothing -> mempty emailVisibility' <- view (settings . emailVisibility) emailVisibility'' <- case emailVisibility' of @@ -1208,7 +1221,7 @@ lookupLocalProfiles requestingUser others = do -- FUTUREWORK: it is an internal error for the two lookups (for 'User' and 'TeamMember') -- to return 'Nothing'. we could throw errors here if that happens, rather than just -- returning an empty profile list from 'lookupProfiles'. - mUser <- Data.lookupUser NoPendingInvitations selfId + mUser <- wrapClient $ Data.lookupUser NoPendingInvitations selfId case userTeam =<< mUser of Nothing -> pure Nothing Just tid -> (tid,) <$$> Intra.getTeamMember selfId tid @@ -1224,7 +1237,7 @@ lookupLocalProfiles requestingUser others = do in baseProfile {profileEmail = profileEmail'} getLegalHoldStatus :: UserId -> (AppIO r) (Maybe UserLegalHoldStatus) -getLegalHoldStatus uid = traverse (getLegalHoldStatus' . accountUser) =<< lookupAccount uid +getLegalHoldStatus uid = traverse (getLegalHoldStatus' . accountUser) =<< wrapClient (lookupAccount uid) getLegalHoldStatus' :: User -> (AppIO r) UserLegalHoldStatus getLegalHoldStatus' user = @@ -1261,9 +1274,9 @@ getEmailForProfile _ EmailVisibleToSelf' = Nothing lookupAccountsByIdentity :: Either Email Phone -> Bool -> (AppIO r) [UserAccount] lookupAccountsByIdentity emailOrPhone includePendingInvitations = do let uk = either userEmailKey userPhoneKey emailOrPhone - activeUid <- Data.lookupKey uk - uidFromKey <- (>>= fst) <$> Data.lookupActivationCode uk - result <- Data.lookupAccounts (nub $ catMaybes [activeUid, uidFromKey]) + activeUid <- wrapClient $ Data.lookupKey uk + uidFromKey <- (>>= fst) <$> wrapClient (Data.lookupActivationCode uk) + result <- wrapClient $ Data.lookupAccounts (nub $ catMaybes [activeUid, uidFromKey]) if includePendingInvitations then pure result else pure $ filter ((/= PendingInvitation) . accountStatus) result @@ -1271,23 +1284,23 @@ lookupAccountsByIdentity emailOrPhone includePendingInvitations = do isBlacklisted :: Either Email Phone -> (AppIO r) Bool isBlacklisted emailOrPhone = do let uk = either userEmailKey userPhoneKey emailOrPhone - Blacklist.exists uk + wrapClient $ Blacklist.exists uk blacklistInsert :: Either Email Phone -> (AppIO r) () blacklistInsert emailOrPhone = do let uk = either userEmailKey userPhoneKey emailOrPhone - Blacklist.insert uk + wrapClient $ Blacklist.insert uk blacklistDelete :: Either Email Phone -> (AppIO r) () blacklistDelete emailOrPhone = do let uk = either userEmailKey userPhoneKey emailOrPhone - Blacklist.delete uk + wrapClient $ Blacklist.delete uk phonePrefixGet :: PhonePrefix -> (AppIO r) [ExcludedPrefix] -phonePrefixGet prefix = Blacklist.getAllPrefixes prefix +phonePrefixGet = wrapClient . Blacklist.getAllPrefixes phonePrefixDelete :: PhonePrefix -> (AppIO r) () -phonePrefixDelete = Blacklist.deletePrefix +phonePrefixDelete = wrapClient . Blacklist.deletePrefix phonePrefixInsert :: ExcludedPrefix -> (AppIO r) () -phonePrefixInsert = Blacklist.insertPrefix +phonePrefixInsert = wrapClient . Blacklist.insertPrefix diff --git a/services/brig/src/Brig/API/Util.hs b/services/brig/src/Brig/API/Util.hs index b6af3b39a2a..419f37899db 100644 --- a/services/brig/src/Brig/API/Util.hs +++ b/services/brig/src/Brig/API/Util.hs @@ -32,7 +32,7 @@ import Brig.API.Error import qualified Brig.API.Error as Error import Brig.API.Handler import Brig.API.Types -import Brig.App (AppIO, settings) +import Brig.App (AppIO, settings, wrapClient) import qualified Brig.Data.User as Data import Brig.Options (FederationDomainConfig, federationDomainConfigs) import qualified Brig.Options as Opts @@ -58,7 +58,7 @@ import Wire.API.User.Search (FederatedUserSearchPolicy (NoSearch)) lookupProfilesMaybeFilterSameTeamOnly :: UserId -> [UserProfile] -> (Handler r) [UserProfile] lookupProfilesMaybeFilterSameTeamOnly self us = do - selfTeam <- lift $ Data.lookupUserTeam self + selfTeam <- lift $ wrapClient $ Data.lookupUserTeam self return $ case selfTeam of Just team -> filter (\x -> profileTeam x == Just team) us Nothing -> us @@ -72,7 +72,7 @@ fetchUserIdentity uid = -- | Obtain a profile for a user as he can see himself. lookupSelfProfile :: UserId -> (AppIO r) (Maybe SelfProfile) -lookupSelfProfile = fmap (fmap mk) . Data.lookupAccount +lookupSelfProfile = fmap (fmap mk) . wrapClient . Data.lookupAccount where mk a = SelfProfile (accountUser a) @@ -93,8 +93,10 @@ traverseConcurrentlyWithErrors :: t a -> ExceptT e (AppIO r) (t b) traverseConcurrentlyWithErrors f = - ExceptT . try . (traverse (either throwIO pure) =<<) - . pooledMapConcurrentlyN 8 (runExceptT . f) + ExceptT . try + . ( traverse (either throwIO pure) + <=< pooledMapConcurrentlyN 8 (runExceptT . f) + ) exceptTToMaybe :: Monad m => ExceptT e m () -> m (Maybe e) exceptTToMaybe = (pure . either Just (const Nothing)) <=< runExceptT diff --git a/services/brig/src/Brig/AWS/SesNotification.hs b/services/brig/src/Brig/AWS/SesNotification.hs index f4b10a0180f..3027bcf5e5c 100644 --- a/services/brig/src/Brig/AWS/SesNotification.hs +++ b/services/brig/src/Brig/AWS/SesNotification.hs @@ -38,7 +38,7 @@ onEvent (MailComplaint es) = onComplaint es onPermanentBounce :: [Email] -> (AppIO r) () onPermanentBounce = mapM_ $ \e -> do logEmailEvent "Permanent bounce" e - Blacklist.insert (userEmailKey e) + wrapClient $ Blacklist.insert (userEmailKey e) onTransientBounce :: [Email] -> (AppIO r) () onTransientBounce = mapM_ (logEmailEvent "Transient bounce") @@ -49,7 +49,7 @@ onUndeterminedBounce = mapM_ (logEmailEvent "Undetermined bounce") onComplaint :: [Email] -> (AppIO r) () onComplaint = mapM_ $ \e -> do logEmailEvent "Complaint" e - Blacklist.insert (userEmailKey e) + wrapClient $ Blacklist.insert (userEmailKey e) logEmailEvent :: Text -> Email -> (AppIO r) () logEmailEvent t e = Log.info $ field "email" (fromEmail e) ~~ msg t diff --git a/services/brig/src/Brig/App.hs b/services/brig/src/Brig/App.hs index 7f718d0ed27..2094f9a2bd4 100644 --- a/services/brig/src/Brig/App.hs +++ b/services/brig/src/Brig/App.hs @@ -66,6 +66,14 @@ module Brig.App locationOf, viewFederationDomain, qualifyLocal, + + -- * Crutches that should be removed once Brig has been completely + + -- * transitioned to Polysemy + wrapClient, + wrapClientE, + wrapClientM, + runAppIOLifted, ) where @@ -87,7 +95,7 @@ import Brig.User.Search.Index (IndexEnv (..), MonadIndexIO (..), runIndexIO) import Brig.User.Template import Brig.ZAuth (MonadZAuth (..), runZAuth) import qualified Brig.ZAuth as ZAuth -import Cassandra (Keyspace (Keyspace), MonadClient, runClient) +import Cassandra (Keyspace (Keyspace), runClient) import qualified Cassandra as Cas import Cassandra.Schema (versionCheck) import qualified Cassandra.Settings as Cas @@ -136,7 +144,7 @@ import Util.Options import Wire.API.User.Identity (Email) schemaVersion :: Int32 -schemaVersion = 67 +schemaVersion = 69 ------------------------------------------------------------------------------- -- Environment @@ -423,13 +431,13 @@ initCredentials secretFile = do dat <- loadSecret secretFile return $ either (\e -> error $ "Could not load secrets from " ++ show secretFile ++ ": " ++ e) id dat -userTemplates :: Monad m => Maybe Locale -> AppT r m (Locale, UserTemplates) +userTemplates :: MonadReader Env m => Maybe Locale -> m (Locale, UserTemplates) userTemplates l = forLocale l <$> view usrTemplates -providerTemplates :: Monad m => Maybe Locale -> AppT r m (Locale, ProviderTemplates) +providerTemplates :: MonadReader Env m => Maybe Locale -> m (Locale, ProviderTemplates) providerTemplates l = forLocale l <$> view provTemplates -teamTemplates :: Monad m => Maybe Locale -> AppT r m (Locale, TeamTemplates) +teamTemplates :: MonadReader Env m => Maybe Locale -> m (Locale, TeamTemplates) teamTemplates l = forLocale l <$> view tmTemplates closeEnv :: Env -> IO () @@ -462,16 +470,19 @@ newtype AppT r m a = AppT type AppIO r = AppT r IO -instance MonadIO m => MonadLogger (AppT r m) where +instance MonadIO m => MonadLogger (ReaderT Env m) where log l m = do g <- view applog r <- view requestId Log.log g l $ field "request" (unRequestId r) ~~ m +instance MonadIO m => MonadLogger (AppT r m) where + log l = AppT . LC.log l + instance MonadIO m => MonadLogger (ExceptT err (AppT r m)) where log l m = lift (LC.log l m) -instance (Monad m, MonadIO m) => MonadHttp (AppT r m) where +instance MonadIO m => MonadHttp (AppT r m) where handleRequestWithCont req handler = do manager <- view httpManager liftIO $ withResponse req manager handler @@ -482,13 +493,27 @@ instance MonadIO m => MonadZAuth (AppT r m) where instance MonadIO m => MonadZAuth (ExceptT err (AppT r m)) where liftZAuth = lift . liftZAuth -instance (MonadThrow m, MonadCatch m, MonadIO m) => MonadClient (AppT r m) where - liftClient m = view casClient >>= \c -> runClient c m - localState f = local (over casClient f) +-- | The function serves as a crutch while Brig is being polysemised. Use it +-- whenever the compiler complains that there is no instance of `MonadClient` +-- for `AppIO r`. It can be removed once there is no `AppT` anymore. +wrapClient :: ReaderT Env Cas.Client a -> AppT r IO a +wrapClient m = do + c <- view casClient + env <- ask + runClient c $ runReaderT m env -instance MonadIndexIO (AppIO r) where +wrapClientE :: ExceptT e (ReaderT Env Cas.Client) a -> ExceptT e (AppT r IO) a +wrapClientE = mapExceptT wrapClient + +wrapClientM :: MaybeT (ReaderT Env Cas.Client) b -> MaybeT (AppT r IO) b +wrapClientM = mapMaybeT wrapClient + +instance MonadIO m => MonadIndexIO (ReaderT Env m) where liftIndexIO m = view indexEnv >>= \e -> runIndexIO e m +instance MonadIndexIO (AppIO r) where + liftIndexIO m = AppT $ liftIndexIO m + instance (MonadIndexIO (AppT r m), Monad m) => MonadIndexIO (ExceptT err (AppT r m)) where liftIndexIO m = view indexEnv >>= \e -> runIndexIO e m @@ -504,6 +529,9 @@ instance MonadUnliftIO m => MonadUnliftIO (AppT r m) where runAppT :: Env -> AppT r m a -> m a runAppT e (AppT ma) = runReaderT ma e +runAppIOLifted :: MonadIO m => Env -> AppIO r a -> m a +runAppIOLifted e = liftIO . runAppT e + runAppResourceT :: ResourceT (AppIO r) a -> (AppIO r) a runAppResourceT ma = do e <- ask diff --git a/services/brig/src/Brig/Code.hs b/services/brig/src/Brig/Code.hs index 52998da9593..05fbb72f12b 100644 --- a/services/brig/src/Brig/Code.hs +++ b/services/brig/src/Brig/Code.hs @@ -42,11 +42,14 @@ module Brig.Code codeValue, codeTTL, codeAccount, + scopeFromAction, -- * Generation Gen (genKey), mkGen, generate, + mk6DigitGen, + mkKey, -- * Storage insert, @@ -70,9 +73,10 @@ import qualified Data.Text.Encoding as Text import Data.UUID (UUID) import Imports hiding (lookup) import OpenSSL.BN (randIntegerZeroToNMinusOne) -import OpenSSL.EVP.Digest (digestBS, getDigestByName) +import OpenSSL.EVP.Digest (Digest, digestBS, getDigestByName) import OpenSSL.Random (randBytes) import Text.Printf (printf) +import qualified Wire.API.User as User -------------------------------------------------------------------------------- -- Code @@ -103,6 +107,12 @@ codeForPhone c | ForPhone p <- codeFor c = Just p | otherwise = Nothing +scopeFromAction :: User.VerificationAction -> Scope +scopeFromAction = \case + User.CreateScimToken -> CreateScimToken + User.Login -> AccountLogin + User.DeleteTeam -> DeleteTeam + -- | The same 'Key' can exist with different 'Value's in different -- 'Scope's at the same time. data Scope @@ -111,6 +121,8 @@ data Scope | PasswordReset | AccountLogin | AccountApproval + | CreateScimToken + | DeleteTeam deriving (Eq, Show) instance Cql Scope where @@ -121,12 +133,16 @@ instance Cql Scope where toCql PasswordReset = CqlInt 3 toCql AccountLogin = CqlInt 4 toCql AccountApproval = CqlInt 5 + toCql CreateScimToken = CqlInt 6 + toCql DeleteTeam = CqlInt 7 fromCql (CqlInt 1) = return AccountDeletion fromCql (CqlInt 2) = return IdentityVerification fromCql (CqlInt 3) = return PasswordReset fromCql (CqlInt 4) = return AccountLogin fromCql (CqlInt 5) = return AccountApproval + fromCql (CqlInt 6) = return CreateScimToken + fromCql (CqlInt 7) = return DeleteTeam fromCql _ = Left "fromCql: Scope: int expected" newtype Retries = Retries {numRetries :: Word8} @@ -150,23 +166,46 @@ data Gen = Gen genValue :: IO Value } --- | Initialise a 'Code' 'Gen'erator for a given natural key. +mkKey :: MonadIO m => CodeFor -> m Key +mkKey cfor = liftIO $ do + Just sha256 <- getDigestByName "SHA256" + let uniqueK = case cfor of + ForEmail e -> emailKeyUniq (mkEmailKey e) + ForPhone p -> phoneKeyUniq (mkPhoneKey p) + pure $ mkKey' sha256 (Text.encodeUtf8 uniqueK) + +-- | Initialise a 'Code' 'Gen'erator for a given natural key. This generates a link for emails and a 6-digit code for phone. See also: `mk6DigitGen`. mkGen :: MonadIO m => CodeFor -> m Gen mkGen cfor = liftIO $ do Just sha256 <- getDigestByName "SHA256" return (initGen sha256 cfor) where - initGen d (ForEmail e) = - let key = mkKey d (Text.encodeUtf8 (emailKeyUniq (mkEmailKey e))) - val = Value . unsafeRange . Ascii.encodeBase64Url <$> randBytes 15 - in Gen cfor key val - initGen d (ForPhone p) = - let key = mkKey d (Text.encodeUtf8 (phoneKeyUniq (mkPhoneKey p))) - val = - Value . unsafeRange . Ascii.unsafeFromText . Text.pack . printf "%06d" - <$> randIntegerZeroToNMinusOne (10 ^ (6 :: Int)) - in Gen cfor key val - mkKey d = Key . unsafeRange . Ascii.encodeBase64Url . BS.take 15 . digestBS d + initGen d (ForEmail e) = mkEmailLinkGen e d + initGen d _ = mk6DigitGen' cfor d + +-- | Initialise a 'Code' 'Gen'erator for a given natural key. This generates a 6-digit code, matter whether it is sent to a phone or to an email address. See also: `mkGen`. +mk6DigitGen :: MonadIO m => CodeFor -> m Gen +mk6DigitGen cfor = liftIO $ do + Just sha256 <- getDigestByName "SHA256" + return $ mk6DigitGen' cfor sha256 + +mk6DigitGen' :: CodeFor -> Digest -> Gen +mk6DigitGen' cfor d = + let uniqueK = case cfor of + ForEmail e -> emailKeyUniq (mkEmailKey e) + ForPhone p -> phoneKeyUniq (mkPhoneKey p) + key = mkKey' d $ Text.encodeUtf8 uniqueK + val = Value . unsafeRange . Ascii.unsafeFromText . Text.pack . printf "%06d" <$> randIntegerZeroToNMinusOne (10 ^ (6 :: Int)) + in Gen cfor key val + +mkEmailLinkGen :: Email -> Digest -> Gen +mkEmailLinkGen e d = + let key = mkKey' d (Text.encodeUtf8 (emailKeyUniq (mkEmailKey e))) + val = Value . unsafeRange . Ascii.encodeBase64Url <$> randBytes 15 + in Gen (ForEmail e) key val + +mkKey' :: Digest -> ByteString -> Key +mkKey' d = Key . unsafeRange . Ascii.encodeBase64Url . BS.take 15 . digestBS d -- | Generate a new 'Code'. generate :: diff --git a/services/brig/src/Brig/Data/Activation.hs b/services/brig/src/Brig/Data/Activation.hs index 41c99b08b0e..728f3f3c4cf 100644 --- a/services/brig/src/Brig/Data/Activation.hs +++ b/services/brig/src/Brig/Data/Activation.hs @@ -31,7 +31,7 @@ module Brig.Data.Activation ) where -import Brig.App (AppIO) +import Brig.App (Env) import Brig.Data.PasswordReset import Brig.Data.User import Brig.Data.UserKey @@ -86,10 +86,11 @@ maxAttempts = 3 -- docs/reference/user/activation.md {#RefActivationSubmit} activateKey :: + (MonadClient m, MonadReader Env m) => ActivationKey -> ActivationCode -> Maybe UserId -> - ExceptT ActivationError (AppIO r) (Maybe ActivationEvent) + ExceptT ActivationError m (Maybe ActivationEvent) activateKey k c u = verifyCode k c >>= pickUser >>= activate where pickUser (uk, u') = maybe (throwE invalidUser) (return . (uk,)) (u <|> u') @@ -111,10 +112,10 @@ activateKey k c u = verifyCode k c >>= pickUser >>= activate (\(e :: Email) -> (Just e /= userEmail usr,) . fmap userEmailKey . userEmail) (\(p :: Phone) -> (Just p /= userPhone usr,) . fmap userPhoneKey . userPhone) key - $ usr + usr in handleExistingIdentity uid profileNeedsUpdate oldKey key handleExistingIdentity uid profileNeedsUpdate oldKey key - | oldKey == Just key && (not profileNeedsUpdate) = return Nothing + | oldKey == Just key && not profileNeedsUpdate = return Nothing -- activating existing key and exactly same profile -- (can happen when a user clicks on activation links more than once) | oldKey == Just key && profileNeedsUpdate = do @@ -135,12 +136,13 @@ activateKey k c u = verifyCode k c >>= pickUser >>= activate -- | Create a new pending activation for a given 'UserKey'. newActivation :: + (MonadIO m, MonadClient m) => UserKey -> -- | The timeout for the activation code. Timeout -> -- | The user with whom to associate the activation code. Maybe UserId -> - (AppIO r) Activation + m Activation newActivation uk timeout u = do (typ, key, code) <- liftIO $ @@ -159,16 +161,17 @@ newActivation uk timeout u = do <$> randIntegerZeroToNMinusOne 1000000 -- | Lookup an activation code and it's associated owner (if any) for a 'UserKey'. -lookupActivationCode :: UserKey -> (AppIO r) (Maybe (Maybe UserId, ActivationCode)) +lookupActivationCode :: MonadClient m => UserKey -> m (Maybe (Maybe UserId, ActivationCode)) lookupActivationCode k = liftIO (mkActivationKey k) >>= retry x1 . query1 codeSelect . params LocalQuorum . Identity -- | Verify an activation code. verifyCode :: + MonadClient m => ActivationKey -> ActivationCode -> - ExceptT ActivationError (AppIO r) (UserKey, Maybe UserId) + ExceptT ActivationError m (UserKey, Maybe UserId) verifyCode key code = do s <- lift . retry x1 . query1 keySelect $ params LocalQuorum (Identity key) case s of @@ -196,7 +199,7 @@ mkActivationKey k = do let bs = digestBS d' (T.encodeUtf8 $ keyText k) return . ActivationKey $ Ascii.encodeBase64Url bs -deleteActivationPair :: ActivationKey -> (AppIO r) () +deleteActivationPair :: MonadClient m => ActivationKey -> m () deleteActivationPair = write keyDelete . params LocalQuorum . Identity invalidUser :: ActivationError diff --git a/services/brig/src/Brig/Data/Blacklist.hs b/services/brig/src/Brig/Data/Blacklist.hs index b99b866d8ef..4ebe61fd7e6 100644 --- a/services/brig/src/Brig/Data/Blacklist.hs +++ b/services/brig/src/Brig/Data/Blacklist.hs @@ -42,8 +42,8 @@ insert uk = retry x5 $ write keyInsert (params LocalQuorum (Identity $ keyText u exists :: MonadClient m => UserKey -> m Bool exists uk = - return . isJust =<< fmap runIdentity - <$> retry x1 (query1 keySelect (params LocalQuorum (Identity $ keyText uk))) + (return . isJust) . fmap runIdentity + =<< retry x1 (query1 keySelect (params LocalQuorum (Identity $ keyText uk))) delete :: MonadClient m => UserKey -> m () delete uk = retry x5 $ write keyDelete (params LocalQuorum (Identity $ keyText uk)) diff --git a/services/brig/src/Brig/Data/Client.hs b/services/brig/src/Brig/Data/Client.hs index 1f8095a0fdc..563d80de1be 100644 --- a/services/brig/src/Brig/Data/Client.hs +++ b/services/brig/src/Brig/Data/Client.hs @@ -31,13 +31,17 @@ module Brig.Data.Client lookupClientsBulk, lookupClientIds, lookupUsersClientIds, - Brig.Data.Client.updateClientLabel, - Brig.Data.Client.updateClientCapabilities, + updateClientLabel, + updateClientCapabilities, -- * Prekeys claimPrekey, updatePrekeys, lookupPrekeyIds, + + -- * MLS public keys + addMLSPublicKeys, + lookupMLSPublicKey, ) where @@ -46,15 +50,15 @@ import qualified Amazonka.DynamoDB as AWS import qualified Amazonka.DynamoDB.Lens as AWS import Bilge.Retry (httpHandlers) import Brig.AWS -import Brig.App (AppIO, awsEnv, currentTime, metrics, randomPrekeyLocalLock) +import Brig.App (AppIO, Env, awsEnv, currentTime, metrics, randomPrekeyLocalLock, wrapClient) import Brig.Data.Instances () import Brig.Data.User (AuthError (..), ReAuthError (..)) import qualified Brig.Data.User as User -import Brig.Types import Brig.Types.Instances () import Brig.Types.User.Auth (CookieLabel) import Brig.User.Auth.DB.Instances () import Cassandra as C hiding (Client) +import Cassandra.Settings as C hiding (Client) import Control.Error import qualified Control.Exception.Lens as EL import Control.Lens @@ -63,6 +67,7 @@ import Control.Monad.Random (randomRIO) import Control.Retry import qualified Data.ByteString.Base64 as B64 import Data.ByteString.Conversion (toByteString, toByteString') +import qualified Data.ByteString.Lazy as LBS import qualified Data.HashMap.Strict as HashMap import Data.Id import Data.Json.Util (UTCTimeMillis, toUTCTimeMillis) @@ -78,7 +83,9 @@ import qualified System.CryptoBox as CryptoBox import System.Logger.Class (field, msg, val) import qualified System.Logger.Class as Log import UnliftIO (pooledMapConcurrentlyN) -import Wire.API.User.Client (ClientCapability, ClientCapabilityList (ClientCapabilityList)) +import Wire.API.MLS.Credential +import Wire.API.User.Client hiding (UpdateClient (..)) +import Wire.API.User.Client.Prekey import Wire.API.UserMap (UserMap (..)) data ClientDataError @@ -86,15 +93,17 @@ data ClientDataError | ClientReAuthError !ReAuthError | ClientMissingAuth | MalformedPrekeys + | MLSPublicKeyDuplicate addClient :: + (MonadClient m, MonadReader Brig.App.Env m) => UserId -> ClientId -> NewClient -> Int -> Maybe Location -> Maybe (Imports.Set ClientCapability) -> - ExceptT ClientDataError (AppIO r) (Client, [Client], Word) + ExceptT ClientDataError m (Client, [Client], Word) addClient u newId c maxPermClients loc cps = do clients <- lookupClients u let typed = filter ((== newClientType c) . clientType) clients @@ -120,7 +129,7 @@ addClient u newId c maxPermClients loc cps = do exists :: Client -> Bool exists = (==) newId . clientId - insert :: ExceptT ClientDataError (AppIO r) Client + insert :: (MonadClient m, MonadReader Brig.App.Env m) => ExceptT ClientDataError m Client insert = do -- Is it possible to do this somewhere else? Otherwise we could use `MonadClient` instead now <- toUTCTimeMillis <$> (liftIO =<< view currentTime) @@ -131,11 +140,25 @@ addClient u newId c maxPermClients loc cps = do mdl = newClientModel c prm = (u, newId, now, newClientType c, newClientLabel c, newClientClass c, newClientCookie c, lat, lon, mdl, C.Set . Set.toList <$> cps) retry x5 $ write insertClient (params LocalQuorum prm) - return $! Client newId (newClientType c) now (newClientClass c) (newClientLabel c) (newClientCookie c) loc mdl (ClientCapabilityList $ fromMaybe mempty cps) + addMLSPublicKeys u newId (Map.assocs (newClientMLSPublicKeys c)) + return + $! Client + { clientId = newId, + clientType = newClientType c, + clientTime = now, + clientClass = newClientClass c, + clientLabel = newClientLabel c, + clientCookie = newClientCookie c, + clientLocation = loc, + clientModel = mdl, + clientCapabilities = ClientCapabilityList (fromMaybe mempty cps), + clientMLSPublicKeys = mempty + } lookupClient :: MonadClient m => UserId -> ClientId -> m (Maybe Client) -lookupClient u c = - fmap toClient +lookupClient u c = do + keys <- retry x1 (query selectMLSPublicKeys (params LocalQuorum (u, c))) + fmap (toClient keys) <$> retry x1 (query1 selectClient (params LocalQuorum (u, c))) lookupClientsBulk :: (MonadClient m) => [UserId] -> m (Map UserId (Imports.Set Client)) @@ -144,10 +167,7 @@ lookupClientsBulk uids = liftClient $ do pure $ Map.fromList userClientTuples where getClientSetWithUser :: MonadClient m => UserId -> m (UserId, Imports.Set Client) - getClientSetWithUser u = (u,) . Set.fromList <$> executeQuery u - - executeQuery :: MonadClient m => UserId -> m [Client] - executeQuery u = toClient <$$> retry x1 (query selectClients (params LocalQuorum (Identity u))) + getClientSetWithUser u = fmap ((u,) . Set.fromList) . lookupClients $ u lookupPubClientsBulk :: (MonadClient m) => [UserId] -> m (UserMap (Imports.Set PubClient)) lookupPubClientsBulk uids = liftClient $ do @@ -161,9 +181,9 @@ lookupPubClientsBulk uids = liftClient $ do executeQuery u = retry x1 (query selectPubClients (params LocalQuorum (Identity u))) lookupClients :: MonadClient m => UserId -> m [Client] -lookupClients u = - map toClient - <$> retry x1 (query selectClients (params LocalQuorum (Identity u))) +lookupClients u = do + keys <- retry x1 (query selectMLSPublicKeysByUser (params LocalQuorum (Identity u))) + toClient keys <$$> retry x1 (query selectClients (params LocalQuorum (Identity u))) lookupClientIds :: MonadClient m => UserId -> m [ClientId] lookupClientIds u = @@ -184,7 +204,15 @@ lookupPrekeyIds u c = hasClient :: MonadClient m => UserId -> ClientId -> m Bool hasClient u d = isJust <$> retry x1 (query1 checkClient (params LocalQuorum (u, d))) -rmClient :: UserId -> ClientId -> (AppIO r) () +rmClient :: + ( MonadClient m, + MonadReader Brig.App.Env m, + MonadUnliftIO m, + MonadCatch m + ) => + UserId -> + ClientId -> + m () rmClient u c = do retry x5 $ write removeClient (params LocalQuorum (u, c)) retry x5 $ write removeClientKeys (params LocalQuorum (u, c)) @@ -212,20 +240,20 @@ updatePrekeys u c pks = do Success n -> return (CryptoBox.prekeyId n == keyId (prekeyId a)) _ -> return False -claimPrekey :: UserId -> ClientId -> (AppIO r) (Maybe ClientPrekey) +claimPrekey :: UserId -> ClientId -> AppIO r (Maybe ClientPrekey) claimPrekey u c = view randomPrekeyLocalLock >>= \case -- Use random prekey selection strategy Just localLock -> withLocalLock localLock $ do - prekeys <- retry x1 $ query userPrekeys (params LocalQuorum (u, c)) + prekeys <- wrapClient . retry x1 $ query userPrekeys (params LocalQuorum (u, c)) prekey <- pickRandomPrekey prekeys - removeAndReturnPreKey prekey + wrapClient $ removeAndReturnPreKey prekey -- Use DynamoDB based optimistic locking strategy - Nothing -> withOptLock u c $ do + Nothing -> withOptLock u c . wrapClient $ do prekey <- retry x1 $ query1 userPrekey (params LocalQuorum (u, c)) removeAndReturnPreKey prekey where - removeAndReturnPreKey :: Maybe (PrekeyId, Text) -> (AppIO r) (Maybe ClientPrekey) + removeAndReturnPreKey :: (MonadClient f, Log.MonadLogger f) => Maybe (PrekeyId, Text) -> f (Maybe ClientPrekey) removeAndReturnPreKey (Just (i, k)) = do if i /= lastPrekeyId then retry x1 $ write removePrekey (params LocalQuorum (u, c, i)) @@ -237,16 +265,48 @@ claimPrekey u c = return $ Just (ClientPrekey c (Prekey i k)) removeAndReturnPreKey Nothing = return Nothing - pickRandomPrekey :: [(PrekeyId, Text)] -> (AppIO r) (Maybe (PrekeyId, Text)) - pickRandomPrekey [] = return Nothing + pickRandomPrekey :: MonadIO f => [(PrekeyId, Text)] -> f (Maybe (PrekeyId, Text)) + pickRandomPrekey [] = pure Nothing -- unless we only have one key left - pickRandomPrekey [pk] = return $ Just pk + pickRandomPrekey [pk] = pure $ Just pk -- pick among list of keys, except lastPrekeyId pickRandomPrekey pks = do let pks' = filter (\k -> fst k /= lastPrekeyId) pks ind <- liftIO $ randomRIO (0, length pks' - 1) return $ atMay pks' ind +lookupMLSPublicKey :: + MonadClient m => + UserId -> + ClientId -> + SignatureSchemeTag -> + m (Maybe LByteString) +lookupMLSPublicKey u c ss = + (fromBlob . runIdentity) <$$> retry x1 (query1 selectMLSPublicKey (params LocalQuorum (u, c, ss))) + +addMLSPublicKeys :: + MonadClient m => + UserId -> + ClientId -> + [(SignatureSchemeTag, ByteString)] -> + ExceptT ClientDataError m () +addMLSPublicKeys u c = traverse_ (uncurry (addMLSPublicKey u c)) + +addMLSPublicKey :: + MonadClient m => + UserId -> + ClientId -> + SignatureSchemeTag -> + ByteString -> + ExceptT ClientDataError m () +addMLSPublicKey u c ss pk = do + rows <- trans insertMLSPublicKeys (params LocalQuorum (u, c, ss, Blob (LBS.fromStrict pk))) + case rows of + [row] + | C.fromRow 0 row /= Right (Just True) -> + throwE MLSPublicKeyDuplicate + _ -> pure () + ------------------------------------------------------------------------------- -- Queries @@ -295,11 +355,38 @@ removePrekey = "DELETE FROM prekeys where user = ? and client = ? and key = ?" checkClient :: PrepQuery R (UserId, ClientId) (Identity ClientId) checkClient = "SELECT client from clients where user = ? and client = ?" +selectMLSPublicKey :: PrepQuery R (UserId, ClientId, SignatureSchemeTag) (Identity Blob) +selectMLSPublicKey = "SELECT key from mls_public_keys where user = ? and client = ? and sig_scheme = ?" + +selectMLSPublicKeys :: PrepQuery R (UserId, ClientId) (SignatureSchemeTag, Blob) +selectMLSPublicKeys = "SELECT sig_scheme, key from mls_public_keys where user = ? and client = ?" + +selectMLSPublicKeysByUser :: PrepQuery R (Identity UserId) (SignatureSchemeTag, Blob) +selectMLSPublicKeysByUser = "SELECT sig_scheme, key from mls_public_keys where user = ?" + +insertMLSPublicKeys :: PrepQuery W (UserId, ClientId, SignatureSchemeTag, Blob) Row +insertMLSPublicKeys = + "INSERT INTO mls_public_keys (user, client, sig_scheme, key) \ + \VALUES (?, ?, ?, ?) IF NOT EXISTS" + ------------------------------------------------------------------------------- -- Conversions -toClient :: (ClientId, ClientType, UTCTimeMillis, Maybe Text, Maybe ClientClass, Maybe CookieLabel, Maybe Latitude, Maybe Longitude, Maybe Text, Maybe (C.Set ClientCapability)) -> Client -toClient (cid, cty, tme, lbl, cls, cok, lat, lon, mdl, cps) = +toClient :: + [(SignatureSchemeTag, Blob)] -> + ( ClientId, + ClientType, + UTCTimeMillis, + Maybe Text, + Maybe ClientClass, + Maybe CookieLabel, + Maybe Latitude, + Maybe Longitude, + Maybe Text, + Maybe (C.Set ClientCapability) + ) -> + Client +toClient keys (cid, cty, tme, lbl, cls, cok, lat, lon, mdl, cps) = Client { clientId = cid, clientType = cty, @@ -309,7 +396,8 @@ toClient (cid, cty, tme, lbl, cls, cok, lat, lon, mdl, cps) = clientCookie = cok, clientLocation = location <$> lat <*> lon, clientModel = mdl, - clientCapabilities = ClientCapabilityList $ maybe Set.empty (Set.fromList . C.fromSet) cps + clientCapabilities = ClientCapabilityList $ maybe Set.empty (Set.fromList . C.fromSet) cps, + clientMLSPublicKeys = fmap (LBS.toStrict . fromBlob) (Map.fromList keys) } toPubClient :: (ClientId, Maybe ClientClass) -> PubClient @@ -330,7 +418,14 @@ ddbKey u c = AWS.S (UUID.toText (toUUID u) <> "." <> client c) key :: UserId -> ClientId -> HashMap Text AWS.AttributeValue key u c = HashMap.singleton ddbClient (ddbKey u c) -deleteOptLock :: UserId -> ClientId -> (AppIO r) () +deleteOptLock :: + ( MonadReader Brig.App.Env m, + MonadUnliftIO m, + MonadCatch m + ) => + UserId -> + ClientId -> + m () deleteOptLock u c = do t <- view (awsEnv . prekeyTable) e <- view (awsEnv . amazonkaEnv) @@ -409,6 +504,6 @@ withOptLock u c ma = go (10 :: Int) return Nothing handleErr _ = return Nothing -withLocalLock :: MVar () -> (AppIO r) a -> (AppIO r) a +withLocalLock :: (MonadMask m, MonadIO m) => MVar () -> m a -> m a withLocalLock l ma = do (takeMVar l *> ma) `finally` putMVar l () diff --git a/services/brig/src/Brig/Data/Connection.hs b/services/brig/src/Brig/Data/Connection.hs index fe43894b4f4..891fea43b5b 100644 --- a/services/brig/src/Brig/Data/Connection.hs +++ b/services/brig/src/Brig/Data/Connection.hs @@ -48,7 +48,7 @@ module Brig.Data.Connection ) where -import Brig.App (AppIO, qualifyLocal) +import Brig.App import Brig.Data.Instances () import Brig.Data.Types as T import Brig.Types @@ -69,11 +69,12 @@ import Wire.API.Connection import Wire.API.Routes.Internal.Brig.Connection insertConnection :: + MonadClient m => Local UserId -> Qualified UserId -> RelationWithHistory -> Qualified ConvId -> - (AppIO r) UserConnection + m UserConnection insertConnection self target rel qcnv@(Qualified cnv cdomain) = do now <- toUTCTimeMillis <$> liftIO getCurrentTime let local (tUnqualified -> ltarget) = @@ -92,7 +93,7 @@ insertConnection self target rel qcnv@(Qualified cnv cdomain) = do ucConvId = Just qcnv } -updateConnection :: UserConnection -> RelationWithHistory -> (AppIO r) UserConnection +updateConnection :: (MonadClient m, MonadReader Env m) => UserConnection -> RelationWithHistory -> m UserConnection updateConnection c status = do self <- qualifyLocal (ucFrom c) now <- updateConnectionStatus self (ucTo c) status @@ -102,7 +103,7 @@ updateConnection c status = do ucLastUpdate = now } -updateConnectionStatus :: Local UserId -> Qualified UserId -> RelationWithHistory -> (AppIO r) UTCTimeMillis +updateConnectionStatus :: MonadClient m => Local UserId -> Qualified UserId -> RelationWithHistory -> m UTCTimeMillis updateConnectionStatus self target status = do now <- toUTCTimeMillis <$> liftIO getCurrentTime let local (tUnqualified -> ltarget) = @@ -115,7 +116,7 @@ updateConnectionStatus self target status = do pure now -- | Lookup the connection from a user 'A' to a user 'B' (A -> B). -lookupConnection :: Local UserId -> Qualified UserId -> (AppIO r) (Maybe UserConnection) +lookupConnection :: MonadClient m => Local UserId -> Qualified UserId -> m (Maybe UserConnection) lookupConnection self target = runMaybeT $ do let local (tUnqualified -> ltarget) = do (_, _, rel, time, mcnv) <- @@ -139,11 +140,12 @@ lookupConnection self target = runMaybeT $ do -- | 'lookupConnection' with more 'Relation' info. lookupRelationWithHistory :: + MonadClient m => -- | User 'A' Local UserId -> -- | User 'B' Qualified UserId -> - (AppIO r) (Maybe RelationWithHistory) + m (Maybe RelationWithHistory) lookupRelationWithHistory self target = do let local (tUnqualified -> ltarget) = query1 relationSelect (params LocalQuorum (tUnqualified self, ltarget)) @@ -151,14 +153,19 @@ lookupRelationWithHistory self target = do query1 remoteRelationSelect (params LocalQuorum (tUnqualified self, domain, rtarget)) runIdentity <$$> retry x1 (foldQualified self local remote target) -lookupRelation :: Local UserId -> Qualified UserId -> (AppIO r) Relation +lookupRelation :: MonadClient m => Local UserId -> Qualified UserId -> m Relation lookupRelation self target = lookupRelationWithHistory self target <&> \case Nothing -> Cancelled Just relh -> (relationDropHistory relh) -- | For a given user 'A', lookup their outgoing connections (A -> X) to other users. -lookupLocalConnections :: Local UserId -> Maybe UserId -> Range 1 500 Int32 -> (AppIO r) (ResultPage UserConnection) +lookupLocalConnections :: + MonadClient m => + Local UserId -> + Maybe UserId -> + Range 1 500 Int32 -> + m (ResultPage UserConnection) lookupLocalConnections lfrom start (fromRange -> size) = toResult <$> case start of Just u -> @@ -196,48 +203,48 @@ lookupRemoteConnectionsPage self pagingState size = (paramsPagingState LocalQuorum (Identity (tUnqualified self)) size pagingState) -- | Lookup all relations between two sets of users (cartesian product). -lookupConnectionStatus :: [UserId] -> [UserId] -> (AppIO r) [ConnectionStatus] +lookupConnectionStatus :: MonadClient m => [UserId] -> [UserId] -> m [ConnectionStatus] lookupConnectionStatus from to = map toConnectionStatus <$> retry x1 (query connectionStatusSelect (params LocalQuorum (from, to))) -- | Lookup all relations between two sets of users (cartesian product). -lookupConnectionStatus' :: [UserId] -> (AppIO r) [ConnectionStatus] +lookupConnectionStatus' :: MonadClient m => [UserId] -> m [ConnectionStatus] lookupConnectionStatus' from = map toConnectionStatus <$> retry x1 (query connectionStatusSelect' (params LocalQuorum (Identity from))) -lookupLocalConnectionStatuses :: [UserId] -> Local [UserId] -> (AppIO r) [ConnectionStatusV2] +lookupLocalConnectionStatuses :: [UserId] -> Local [UserId] -> AppIO r [ConnectionStatusV2] lookupLocalConnectionStatuses froms tos = do concat <$> pooledMapConcurrentlyN 16 lookupStatuses froms where lookupStatuses :: UserId -> (AppIO r) [ConnectionStatusV2] lookupStatuses from = map (uncurry $ toConnectionStatusV2 from (tDomain tos)) - <$> retry x1 (query relationsSelect (params LocalQuorum (from, tUnqualified tos))) + <$> wrapClient (retry x1 (query relationsSelect (params LocalQuorum (from, tUnqualified tos)))) -lookupRemoteConnectionStatuses :: [UserId] -> Remote [UserId] -> (AppIO r) [ConnectionStatusV2] +lookupRemoteConnectionStatuses :: [UserId] -> Remote [UserId] -> AppIO r [ConnectionStatusV2] lookupRemoteConnectionStatuses froms tos = do concat <$> pooledMapConcurrentlyN 16 lookupStatuses froms where - lookupStatuses :: UserId -> (AppIO r) [ConnectionStatusV2] + lookupStatuses :: UserId -> AppIO r [ConnectionStatusV2] lookupStatuses from = map (uncurry $ toConnectionStatusV2 from (tDomain tos)) - <$> retry x1 (query remoteRelationsSelect (params LocalQuorum (from, tDomain tos, tUnqualified tos))) + <$> wrapClient (retry x1 (query remoteRelationsSelect (params LocalQuorum (from, tDomain tos, tUnqualified tos)))) -lookupAllStatuses :: Local [UserId] -> (AppIO r) [ConnectionStatusV2] +lookupAllStatuses :: Local [UserId] -> AppIO r [ConnectionStatusV2] lookupAllStatuses lfroms = do let froms = tUnqualified lfroms concat <$> pooledMapConcurrentlyN 16 lookupAndCombine froms where - lookupAndCombine :: UserId -> (AppIO r) [ConnectionStatusV2] - lookupAndCombine u = (<>) <$> lookupLocalStatuses u <*> lookupRemoteStatuses u + lookupAndCombine :: UserId -> AppIO r [ConnectionStatusV2] + lookupAndCombine u = wrapClient $ (<>) <$> lookupLocalStatuses u <*> lookupRemoteStatuses u - lookupLocalStatuses :: UserId -> (AppIO r) [ConnectionStatusV2] + lookupLocalStatuses :: MonadClient m => UserId -> m [ConnectionStatusV2] lookupLocalStatuses from = map (uncurry $ toConnectionStatusV2 from (tDomain lfroms)) <$> retry x1 (query relationsSelectAll (params LocalQuorum (Identity from))) - lookupRemoteStatuses :: UserId -> (AppIO r) [ConnectionStatusV2] + lookupRemoteStatuses :: MonadClient m => UserId -> m [ConnectionStatusV2] lookupRemoteStatuses from = map (\(d, u, r) -> toConnectionStatusV2 from d u r) <$> retry x1 (query remoteRelationsSelectAll (params LocalQuorum (Identity from))) @@ -248,20 +255,20 @@ lookupRemoteConnectedUsersC u maxResults = .| C.map (map (uncurry toRemoteUnsafe)) -- | See 'lookupContactListWithRelation'. -lookupContactList :: UserId -> (AppIO r) [UserId] +lookupContactList :: MonadClient m => UserId -> m [UserId] lookupContactList u = fst <$$> (filter ((== AcceptedWithHistory) . snd) <$> lookupContactListWithRelation u) -- | For a given user 'A', lookup the list of users that form his contact list, -- i.e. the users to whom 'A' has an outgoing 'Accepted' relation (A -> B). -lookupContactListWithRelation :: UserId -> (AppIO r) [(UserId, RelationWithHistory)] +lookupContactListWithRelation :: MonadClient m => UserId -> m [(UserId, RelationWithHistory)] lookupContactListWithRelation u = retry x1 (query contactsSelect (params LocalQuorum (Identity u))) -- | Count the number of connections a user has in a specific relation status. -- (If you want to distinguish 'RelationWithHistory', write a new function.) -- Note: The count is eventually consistent. -countConnections :: Local UserId -> [Relation] -> (AppIO r) Int64 +countConnections :: MonadClient m => Local UserId -> [Relation] -> m Int64 countConnections u r = do rels <- retry x1 . query selectStatus $ params One (Identity (tUnqualified u)) relsRemote <- retry x1 . query selectStatusRemote $ params One (Identity (tUnqualified u)) @@ -274,23 +281,27 @@ countConnections u r = do selectStatusRemote :: QueryString R (Identity UserId) (Identity RelationWithHistory) selectStatusRemote = "SELECT status FROM connection_remote WHERE left = ?" - count n (Identity s) | (relationDropHistory s) `elem` r = n + 1 + count n (Identity s) | relationDropHistory s `elem` r = n + 1 count n _ = n -deleteConnections :: UserId -> (AppIO r) () +deleteConnections :: UserId -> AppIO r () deleteConnections u = do - runConduit $ - paginateC contactsSelect (paramsP LocalQuorum (Identity u) 100) x1 - .| C.mapM_ (pooledMapConcurrentlyN_ 16 delete) - retry x1 . write connectionClear $ params LocalQuorum (Identity u) - retry x1 . write remoteConnectionClear $ params LocalQuorum (Identity u) + e <- ask + fmap + wrapClient + runConduit + $ paginateC contactsSelect (paramsP LocalQuorum (Identity u) 100) x1 + .| C.mapM_ (runAppIOLifted e . pooledMapConcurrentlyN_ 16 delete) + wrapClient $ do + retry x1 . write connectionClear $ params LocalQuorum (Identity u) + retry x1 . write remoteConnectionClear $ params LocalQuorum (Identity u) where - delete (other, _status) = write connectionDelete $ params LocalQuorum (other, u) + delete (other, _status) = wrapClient $ write connectionDelete $ params LocalQuorum (other, u) -deleteRemoteConnections :: Remote UserId -> Range 1 1000 [UserId] -> (AppIO r) () +deleteRemoteConnections :: Remote UserId -> Range 1 1000 [UserId] -> AppIO r () deleteRemoteConnections (qUntagged -> Qualified remoteUser remoteDomain) (fromRange -> locals) = pooledForConcurrentlyN_ 16 locals $ \u -> - write remoteConnectionDelete $ params LocalQuorum (u, remoteDomain, remoteUser) + wrapClient $ write remoteConnectionDelete $ params LocalQuorum (u, remoteDomain, remoteUser) -- Queries diff --git a/services/brig/src/Brig/Data/Instances.hs b/services/brig/src/Brig/Data/Instances.hs index b25d83d452c..730bbc9d787 100644 --- a/services/brig/src/Brig/Data/Instances.hs +++ b/services/brig/src/Brig/Data/Instances.hs @@ -30,14 +30,21 @@ import Cassandra.CQL import Control.Error (note) import Data.Aeson (eitherDecode, encode) import qualified Data.Aeson as JSON +import Data.ByteString.Conversion +import qualified Data.ByteString.Lazy as LBS import Data.Domain (Domain, domainText, mkDomain) import Data.Handle (Handle (..)) import Data.Id () import Data.Range () import Data.String.Conversions (LBS, ST, cs) +import qualified Data.Text as T import Data.Text.Ascii () +import Data.Text.Encoding (encodeUtf8) import Imports +import Wire.API.Asset (AssetKey, assetKeyToText, nilAssetKey) import Wire.API.Connection (RelationWithHistory (..)) +import Wire.API.MLS.Credential +import Wire.API.MLS.KeyPackage import Wire.API.User.RichInfo deriving instance Cql Name @@ -126,6 +133,14 @@ instance Cql Pict where toCql = toCql . map (Blob . JSON.encode) . fromPict +instance Cql AssetKey where + ctype = Tagged TextColumn + toCql = CqlText . assetKeyToText + + -- if the asset key is invalid we will return the nil asset key (`3-1-00000000-0000-0000-0000-000000000000`) + fromCql (CqlText txt) = pure $ fromRight nilAssetKey $ runParser parser $ encodeUtf8 txt + fromCql _ = Left "AssetKey: Expected CqlText" + instance Cql AssetSize where ctype = Tagged IntColumn @@ -171,7 +186,7 @@ instance Cql Asset where toCql (ImageAsset k s) = CqlUdt [ ("typ", CqlInt 0), - ("key", CqlText k), + ("key", toCql k), ("size", toCql s) ] @@ -264,3 +279,23 @@ instance Cql Domain where toCql = CqlText . domainText fromCql (CqlText txt) = mkDomain txt fromCql _ = Left "Domain: Text expected" + +instance Cql SignatureSchemeTag where + ctype = Tagged TextColumn + toCql = CqlText . signatureSchemeName + fromCql (CqlText name) = + note ("Unexpected signature scheme: " <> T.unpack name) $ + signatureSchemeFromName name + fromCql _ = Left "SignatureScheme: Text expected" + +instance Cql KeyPackageRef where + ctype = Tagged BlobColumn + toCql = CqlBlob . LBS.fromStrict . unKeyPackageRef + fromCql (CqlBlob b) = pure . KeyPackageRef . LBS.toStrict $ b + fromCql _ = Left "Expected CqlBlob" + +instance Cql KeyPackageData where + ctype = Tagged BlobColumn + toCql = CqlBlob . kpData + fromCql (CqlBlob b) = pure . KeyPackageData $ b + fromCql _ = Left "Expected CqlBlob" diff --git a/services/brig/src/Brig/Data/LoginCode.hs b/services/brig/src/Brig/Data/LoginCode.hs index d5627e7e81b..4ac76a2c425 100644 --- a/services/brig/src/Brig/Data/LoginCode.hs +++ b/services/brig/src/Brig/Data/LoginCode.hs @@ -26,7 +26,7 @@ module Brig.Data.LoginCode ) where -import Brig.App (AppIO, currentTime) +import Brig.App (Env, currentTime) import Brig.Data.Instances () import Brig.Types.Code (Timeout (..)) import Brig.Types.User.Auth @@ -48,7 +48,7 @@ maxAttempts = 3 ttl :: NominalDiffTime ttl = 600 -createLoginCode :: UserId -> (AppIO r) PendingLoginCode +createLoginCode :: (MonadClient m, MonadReader Env m) => UserId -> m PendingLoginCode createLoginCode u = do now <- liftIO =<< view currentTime code <- liftIO genCode @@ -57,7 +57,7 @@ createLoginCode u = do where genCode = LoginCode . T.pack . printf "%06d" <$> randIntegerZeroToNMinusOne 1000000 -verifyLoginCode :: UserId -> LoginCode -> (AppIO r) Bool +verifyLoginCode :: (MonadClient m, MonadReader Env m) => UserId -> LoginCode -> m Bool verifyLoginCode u c = do code <- retry x1 (query1 codeSelect (params LocalQuorum (Identity u))) now <- liftIO =<< view currentTime @@ -67,7 +67,7 @@ verifyLoginCode u c = do Just (_, _, _) -> deleteLoginCode u >> return False Nothing -> return False -lookupLoginCode :: UserId -> (AppIO r) (Maybe PendingLoginCode) +lookupLoginCode :: (MonadReader Env m, MonadClient m) => UserId -> m (Maybe PendingLoginCode) lookupLoginCode u = do now <- liftIO =<< view currentTime validate now =<< retry x1 (query1 codeSelect (params LocalQuorum (Identity u))) @@ -77,10 +77,10 @@ lookupLoginCode u = do pending c now t = PendingLoginCode c (timeout now t) timeout now t = Timeout (t `diffUTCTime` now) -deleteLoginCode :: UserId -> (AppIO r) () +deleteLoginCode :: MonadClient m => UserId -> m () deleteLoginCode u = retry x5 . write codeDelete $ params LocalQuorum (Identity u) -insertLoginCode :: UserId -> LoginCode -> Int32 -> UTCTime -> (AppIO r) () +insertLoginCode :: MonadClient m => UserId -> LoginCode -> Int32 -> UTCTime -> m () insertLoginCode u c n t = retry x5 . write codeInsert $ params LocalQuorum (u, c, n, t, round ttl) -- Queries diff --git a/services/brig/src/Brig/Data/MLS/KeyPackage.hs b/services/brig/src/Brig/Data/MLS/KeyPackage.hs index 25f3eaabccd..08ca3c66d43 100644 --- a/services/brig/src/Brig/Data/MLS/KeyPackage.hs +++ b/services/brig/src/Brig/Data/MLS/KeyPackage.hs @@ -19,18 +19,21 @@ module Brig.Data.MLS.KeyPackage ( insertKeyPackages, claimKeyPackage, countKeyPackages, + derefKeyPackage, ) where import Brig.App -import Brig.Data.MLS.KeyPackage.Instances () import Cassandra import Control.Error import Control.Lens import Control.Monad.Random (randomRIO) +import Data.Domain import Data.Functor import Data.Id +import Data.Qualified import Imports +import Wire.API.MLS.Credential import Wire.API.MLS.KeyPackage insertKeyPackages :: MonadClient m => UserId -> ClientId -> [(KeyPackageRef, KeyPackageData)] -> m () @@ -43,16 +46,27 @@ insertKeyPackages uid cid kps = retry x5 . batch $ do q :: PrepQuery W (UserId, ClientId, KeyPackageData, KeyPackageRef) () q = "INSERT INTO mls_key_packages (user, client, data, ref) VALUES (?, ?, ?, ?)" -claimKeyPackage :: UserId -> ClientId -> MaybeT (AppIO r) KeyPackageData -claimKeyPackage u c = MaybeT $ do +claimKeyPackage :: + ( MonadReader Env m, + MonadUnliftIO m, + MonadClient m + ) => + Local UserId -> + ClientId -> + MaybeT m (KeyPackageRef, KeyPackageData) +claimKeyPackage u c = do -- FUTUREWORK: investigate better locking strategies - lock <- view keyPackageLocalLock - withMVar lock . const $ do - kps <- retry x1 $ query lookupQuery (params LocalQuorum (u, c)) + lock <- lift $ view keyPackageLocalLock + -- get a random key package and delete it + (ref, kpd) <- MaybeT . withMVar lock . const $ do + kps <- retry x1 $ query lookupQuery (params LocalQuorum (tUnqualified u, c)) mk <- liftIO (pick kps) for mk $ \(ref, kpd) -> do - retry x5 $ write deleteQuery (params LocalQuorum (u, c, ref)) - pure kpd + retry x5 $ write deleteQuery (params LocalQuorum (tUnqualified u, c, ref)) + pure (ref, kpd) + -- add key package ref to mapping table + lift $ write insertQuery (params LocalQuorum (ref, tDomain u, tUnqualified u, c)) + pure (ref, kpd) where lookupQuery :: PrepQuery R (UserId, ClientId) (KeyPackageRef, KeyPackageData) lookupQuery = "SELECT ref, data FROM mls_key_packages WHERE user = ? AND client = ?" @@ -60,6 +74,9 @@ claimKeyPackage u c = MaybeT $ do deleteQuery :: PrepQuery W (UserId, ClientId, KeyPackageRef) () deleteQuery = "DELETE FROM mls_key_packages WHERE user = ? AND client = ? AND ref = ?" + insertQuery :: PrepQuery W (KeyPackageRef, Domain, UserId, ClientId) () + insertQuery = "INSERT INTO mls_key_package_refs (ref, domain, user, client) VALUES (?, ?, ?, ?)" + countKeyPackages :: MonadClient m => UserId -> ClientId -> m Int64 countKeyPackages u c = retry x1 $ sum . fmap runIdentity <$> query1 q (params LocalQuorum (u, c)) @@ -67,6 +84,14 @@ countKeyPackages u c = q :: PrepQuery R (UserId, ClientId) (Identity Int64) q = "SELECT COUNT(*) FROM mls_key_packages WHERE user = ? AND client = ?" +derefKeyPackage :: MonadClient m => KeyPackageRef -> MaybeT m ClientIdentity +derefKeyPackage ref = do + (d, u, c) <- MaybeT . retry x1 $ query1 q (params LocalQuorum (Identity ref)) + pure $ ClientIdentity d u c + where + q :: PrepQuery R (Identity KeyPackageRef) (Domain, UserId, ClientId) + q = "SELECT domain, user, client from mls_key_package_refs WHERE ref = ?" + -------------------------------------------------------------------------------- -- Utilities diff --git a/services/brig/src/Brig/Data/PasswordReset.hs b/services/brig/src/Brig/Data/PasswordReset.hs index 942d14f4065..9ddf1f9978f 100644 --- a/services/brig/src/Brig/Data/PasswordReset.hs +++ b/services/brig/src/Brig/Data/PasswordReset.hs @@ -26,7 +26,7 @@ module Brig.Data.PasswordReset ) where -import Brig.App (AppIO, currentTime) +import Brig.App (Env, currentTime) import Brig.Data.Instances () import Brig.Types import Cassandra @@ -48,7 +48,7 @@ maxAttempts = 3 ttl :: NominalDiffTime ttl = 3600 -- 60 minutes -createPasswordResetCode :: UserId -> Either Email Phone -> (AppIO r) PasswordResetPair +createPasswordResetCode :: (MonadClient m, MonadReader Env m) => UserId -> Either Email Phone -> m PasswordResetPair createPasswordResetCode u target = do key <- liftIO $ mkPasswordResetKey u now <- liftIO =<< view currentTime @@ -61,7 +61,7 @@ createPasswordResetCode u target = do PasswordResetCode . Ascii.unsafeFromText . pack . printf "%06d" <$> randIntegerZeroToNMinusOne 1000000 -lookupPasswordResetCode :: UserId -> (AppIO r) (Maybe PasswordResetCode) +lookupPasswordResetCode :: (MonadClient m, MonadReader Env m) => UserId -> m (Maybe PasswordResetCode) lookupPasswordResetCode u = do key <- liftIO $ mkPasswordResetKey u now <- liftIO =<< view currentTime @@ -70,7 +70,7 @@ lookupPasswordResetCode u = do validate now (Just (c, _, _, Just t)) | t > now = return $ Just c validate _ _ = return Nothing -verifyPasswordResetCode :: PasswordResetPair -> (AppIO r) (Maybe UserId) +verifyPasswordResetCode :: (MonadClient m, MonadReader Env m) => PasswordResetPair -> m (Maybe UserId) verifyPasswordResetCode (k, c) = do now <- liftIO =<< view currentTime code <- retry x1 (query1 codeSelect (params LocalQuorum (Identity k))) @@ -84,7 +84,7 @@ verifyPasswordResetCode (k, c) = do where countdown = retry x5 . write codeInsert . params LocalQuorum -deletePasswordResetCode :: PasswordResetKey -> (AppIO r) () +deletePasswordResetCode :: MonadClient m => PasswordResetKey -> m () deletePasswordResetCode k = retry x5 . write codeDelete $ params LocalQuorum (Identity k) mkPasswordResetKey :: (MonadIO m) => UserId -> m PasswordResetKey diff --git a/services/brig/src/Brig/Data/Properties.hs b/services/brig/src/Brig/Data/Properties.hs index 5ec00292621..336cb2d2da9 100644 --- a/services/brig/src/Brig/Data/Properties.hs +++ b/services/brig/src/Brig/Data/Properties.hs @@ -26,7 +26,6 @@ module Brig.Data.Properties ) where -import Brig.App (AppIO) import Brig.Data.Instances () import Brig.Types.Properties import Cassandra @@ -40,30 +39,35 @@ maxProperties = 16 data PropertiesDataError = TooManyProperties -insertProperty :: UserId -> PropertyKey -> PropertyValue -> ExceptT PropertiesDataError (AppIO r) () +insertProperty :: + MonadClient m => + UserId -> + PropertyKey -> + PropertyValue -> + ExceptT PropertiesDataError m () insertProperty u k v = do n <- lift . fmap (maybe 0 runIdentity) . retry x1 $ query1 propertyCount (params LocalQuorum (Identity u)) unless (n < maxProperties) $ throwE TooManyProperties lift . retry x5 $ write propertyInsert (params LocalQuorum (u, k, v)) -deleteProperty :: UserId -> PropertyKey -> (AppIO r) () +deleteProperty :: MonadClient m => UserId -> PropertyKey -> m () deleteProperty u k = retry x5 $ write propertyDelete (params LocalQuorum (u, k)) -clearProperties :: UserId -> (AppIO r) () +clearProperties :: MonadClient m => UserId -> m () clearProperties u = retry x5 $ write propertyReset (params LocalQuorum (Identity u)) -lookupProperty :: UserId -> PropertyKey -> (AppIO r) (Maybe PropertyValue) +lookupProperty :: MonadClient m => UserId -> PropertyKey -> m (Maybe PropertyValue) lookupProperty u k = fmap runIdentity <$> retry x1 (query1 propertySelect (params LocalQuorum (u, k))) -lookupPropertyKeys :: UserId -> (AppIO r) [PropertyKey] +lookupPropertyKeys :: MonadClient m => UserId -> m [PropertyKey] lookupPropertyKeys u = map runIdentity <$> retry x1 (query propertyKeysSelect (params LocalQuorum (Identity u))) -lookupPropertyKeysAndValues :: UserId -> (AppIO r) PropertyKeysAndValues +lookupPropertyKeysAndValues :: MonadClient m => UserId -> m PropertyKeysAndValues lookupPropertyKeysAndValues u = PropertyKeysAndValues <$> retry x1 (query propertyKeysValuesSelect (params LocalQuorum (Identity u))) diff --git a/services/brig/src/Brig/Data/User.hs b/services/brig/src/Brig/Data/User.hs index 3959dfc3fb0..5cad66315d6 100644 --- a/services/brig/src/Brig/Data/User.hs +++ b/services/brig/src/Brig/Data/User.hs @@ -70,7 +70,7 @@ module Brig.Data.User ) where -import Brig.App (AppIO, currentTime, settings, viewFederationDomain, zauthEnv) +import Brig.App (Env, currentTime, settings, viewFederationDomain, zauthEnv) import Brig.Data.Instances () import Brig.Options import Brig.Password @@ -107,6 +107,9 @@ data AuthError data ReAuthError = ReAuthError !AuthError | ReAuthMissingPassword + | ReAuthCodeVerificationRequired + | ReAuthCodeVerificationNoPendingCode + | ReAuthCodeVerificationNoEmail -- | Preconditions: -- @@ -116,7 +119,7 @@ data ReAuthError -- Condition (2.) is essential for maintaining handle uniqueness. It is guaranteed by the -- fact that we're setting getting @mbHandle@ from table @"user"@, and when/if it was added -- there, it was claimed properly. -newAccount :: NewUser -> Maybe InvitationId -> Maybe TeamId -> Maybe Handle -> (AppIO r) (UserAccount, Maybe Password) +newAccount :: (MonadClient m, MonadReader Env m) => NewUser -> Maybe InvitationId -> Maybe TeamId -> Maybe Handle -> m (UserAccount, Maybe Password) newAccount u inv tid mbHandle = do defLoc <- setDefaultUserLocale <$> view settings domain <- viewFederationDomain @@ -152,7 +155,7 @@ newAccount u inv tid mbHandle = do managedBy = fromMaybe defaultManagedBy (newUserManagedBy u) user uid domain l e = User uid (Qualified uid domain) ident name pict assets colour False l Nothing mbHandle e tid managedBy -newAccountInviteViaScim :: UserId -> TeamId -> Maybe Locale -> Name -> Email -> (AppIO r) UserAccount +newAccountInviteViaScim :: (MonadClient m, MonadReader Env m) => UserId -> TeamId -> Maybe Locale -> Name -> Email -> m UserAccount newAccountInviteViaScim uid tid locale name email = do defLoc <- setDefaultUserLocale <$> view settings domain <- viewFederationDomain @@ -176,7 +179,7 @@ newAccountInviteViaScim uid tid locale name email = do ManagedByScim -- | Mandatory password authentication. -authenticate :: UserId -> PlainTextPassword -> ExceptT AuthError (AppIO r) () +authenticate :: MonadClient m => UserId -> PlainTextPassword -> ExceptT AuthError m () authenticate u pw = lift (lookupAuth u) >>= \case Nothing -> throwE AuthInvalidUser @@ -192,7 +195,7 @@ authenticate u pw = -- | Password reauthentication. If the account has a password, reauthentication -- is mandatory. If the account has no password and no password is given, -- reauthentication is a no-op. -reauthenticate :: (MonadClient m) => UserId -> Maybe PlainTextPassword -> ExceptT ReAuthError m () +reauthenticate :: MonadClient m => UserId -> Maybe PlainTextPassword -> ExceptT ReAuthError m () reauthenticate u pw = lift (lookupAuth u) >>= \case Nothing -> throwE (ReAuthError AuthInvalidUser) @@ -210,6 +213,7 @@ reauthenticate u pw = throwE (ReAuthError AuthInvalidCredentials) insertAccount :: + MonadClient m => UserAccount -> -- | If a bot: conversation and team -- (if a team conversation) @@ -217,7 +221,7 @@ insertAccount :: Maybe Password -> -- | Whether the user is activated Bool -> - (AppIO r) () + m () insertAccount (UserAccount u status) mbConv password activated = retry x5 . batch $ do setType BatchLogged setConsistency LocalQuorum @@ -260,10 +264,10 @@ insertAccount (UserAccount u status) mbConv password activated = retry x5 . batc "INSERT INTO service_team (provider, service, user, conv, team) \ \VALUES (?, ?, ?, ?, ?)" -updateLocale :: UserId -> Locale -> (AppIO r) () +updateLocale :: MonadClient m => UserId -> Locale -> m () updateLocale u (Locale l c) = write userLocaleUpdate (params LocalQuorum (l, c, u)) -updateUser :: UserId -> UserUpdate -> (AppIO r) () +updateUser :: MonadClient m => UserId -> UserUpdate -> m () updateUser u UserUpdate {..} = retry x5 . batch $ do setType BatchLogged setConsistency LocalQuorum @@ -272,13 +276,13 @@ updateUser u UserUpdate {..} = retry x5 . batch $ do for_ uupAssets $ \a -> addPrepQuery userAssetsUpdate (a, u) for_ uupAccentId $ \c -> addPrepQuery userAccentIdUpdate (c, u) -updateEmail :: UserId -> Email -> (AppIO r) () +updateEmail :: MonadClient m => UserId -> Email -> m () updateEmail u e = retry x5 $ write userEmailUpdate (params LocalQuorum (e, u)) -updatePhone :: UserId -> Phone -> (AppIO r) () +updatePhone :: MonadClient m => UserId -> Phone -> m () updatePhone u p = retry x5 $ write userPhoneUpdate (params LocalQuorum (p, u)) -updateSSOId :: UserId -> Maybe UserSSOId -> (AppIO r) Bool +updateSSOId :: MonadClient m => UserId -> Maybe UserSSOId -> m Bool updateSSOId u ssoid = do mteamid <- lookupUserTeam u case mteamid of @@ -287,36 +291,36 @@ updateSSOId u ssoid = do pure True Nothing -> pure False -updateManagedBy :: UserId -> ManagedBy -> (AppIO r) () +updateManagedBy :: MonadClient m => UserId -> ManagedBy -> m () updateManagedBy u h = retry x5 $ write userManagedByUpdate (params LocalQuorum (h, u)) -updateHandle :: UserId -> Handle -> (AppIO r) () +updateHandle :: MonadClient m => UserId -> Handle -> m () updateHandle u h = retry x5 $ write userHandleUpdate (params LocalQuorum (h, u)) -updatePassword :: UserId -> PlainTextPassword -> (AppIO r) () +updatePassword :: MonadClient m => UserId -> PlainTextPassword -> m () updatePassword u t = do p <- liftIO $ mkSafePassword t retry x5 $ write userPasswordUpdate (params LocalQuorum (p, u)) -updateRichInfo :: UserId -> RichInfoAssocList -> (AppIO r) () +updateRichInfo :: MonadClient m => UserId -> RichInfoAssocList -> m () updateRichInfo u ri = retry x5 $ write userRichInfoUpdate (params LocalQuorum (ri, u)) -updateFeatureConferenceCalling :: UserId -> Maybe ApiFt.TeamFeatureStatusNoConfig -> (AppIO r) (Maybe ApiFt.TeamFeatureStatusNoConfig) +updateFeatureConferenceCalling :: MonadClient m => UserId -> Maybe ApiFt.TeamFeatureStatusNoConfig -> m (Maybe ApiFt.TeamFeatureStatusNoConfig) updateFeatureConferenceCalling uid mbStatus = do let flag = ApiFt.tfwoStatus <$> mbStatus retry x5 $ write update (params LocalQuorum (flag, uid)) pure mbStatus where update :: PrepQuery W (Maybe ApiFt.TeamFeatureStatusValue, UserId) () - update = fromString $ "update user set feature_conference_calling = ? where id = ?" + update = fromString "update user set feature_conference_calling = ? where id = ?" -deleteEmail :: UserId -> (AppIO r) () +deleteEmail :: MonadClient m => UserId -> m () deleteEmail u = retry x5 $ write userEmailDelete (params LocalQuorum (Identity u)) -deletePhone :: UserId -> (AppIO r) () +deletePhone :: MonadClient m => UserId -> m () deletePhone u = retry x5 $ write userPhoneDelete (params LocalQuorum (Identity u)) -deleteServiceUser :: ProviderId -> ServiceId -> BotId -> (AppIO r) () +deleteServiceUser :: MonadClient m => ProviderId -> ServiceId -> BotId -> m () deleteServiceUser pid sid bid = do lookupServiceUser pid sid bid >>= \case Nothing -> pure () @@ -336,17 +340,17 @@ deleteServiceUser pid sid bid = do "DELETE FROM service_team \ \WHERE provider = ? AND service = ? AND team = ? AND user = ?" -updateStatus :: UserId -> AccountStatus -> (AppIO r) () +updateStatus :: MonadClient m => UserId -> AccountStatus -> m () updateStatus u s = retry x5 $ write userStatusUpdate (params LocalQuorum (s, u)) -- | Whether the account has been activated by verifying -- an email address or phone number. -isActivated :: UserId -> (AppIO r) Bool +isActivated :: MonadClient m => UserId -> m Bool isActivated u = (== Just (Identity True)) <$> retry x1 (query1 activatedSelect (params LocalQuorum (Identity u))) -filterActive :: [UserId] -> (AppIO r) [UserId] +filterActive :: MonadClient m => [UserId] -> m [UserId] filterActive us = map (view _1) . filter isActiveUser <$> retry x1 (query accountStateSelectAll (params LocalQuorum (Identity us))) @@ -355,46 +359,46 @@ filterActive us = isActiveUser (_, True, Just Active) = True isActiveUser _ = False -lookupUser :: HavePendingInvitations -> UserId -> (AppIO r) (Maybe User) +lookupUser :: (MonadClient m, MonadReader Env m) => HavePendingInvitations -> UserId -> m (Maybe User) lookupUser hpi u = listToMaybe <$> lookupUsers hpi [u] -activateUser :: UserId -> UserIdentity -> (AppIO r) () +activateUser :: MonadClient m => UserId -> UserIdentity -> m () activateUser u ident = do let email = emailIdentity ident let phone = phoneIdentity ident retry x5 $ write userActivatedUpdate (params LocalQuorum (email, phone, u)) -deactivateUser :: UserId -> (AppIO r) () +deactivateUser :: MonadClient m => UserId -> m () deactivateUser u = retry x5 $ write userDeactivatedUpdate (params LocalQuorum (Identity u)) -lookupLocale :: UserId -> (AppIO r) (Maybe Locale) +lookupLocale :: (MonadClient m, MonadReader Env m) => UserId -> m (Maybe Locale) lookupLocale u = do defLoc <- setDefaultUserLocale <$> view settings fmap (toLocale defLoc) <$> retry x1 (query1 localeSelect (params LocalQuorum (Identity u))) -lookupName :: UserId -> (AppIO r) (Maybe Name) +lookupName :: MonadClient m => UserId -> m (Maybe Name) lookupName u = fmap runIdentity <$> retry x1 (query1 nameSelect (params LocalQuorum (Identity u))) -lookupPassword :: UserId -> (AppIO r) (Maybe Password) +lookupPassword :: MonadClient m => UserId -> m (Maybe Password) lookupPassword u = - join . fmap runIdentity + (runIdentity =<<) <$> retry x1 (query1 passwordSelect (params LocalQuorum (Identity u))) -lookupStatus :: UserId -> (AppIO r) (Maybe AccountStatus) +lookupStatus :: MonadClient m => UserId -> m (Maybe AccountStatus) lookupStatus u = - join . fmap runIdentity + (runIdentity =<<) <$> retry x1 (query1 statusSelect (params LocalQuorum (Identity u))) -lookupRichInfo :: UserId -> (AppIO r) (Maybe RichInfoAssocList) +lookupRichInfo :: MonadClient m => UserId -> m (Maybe RichInfoAssocList) lookupRichInfo u = fmap runIdentity <$> retry x1 (query1 richInfoSelect (params LocalQuorum (Identity u))) -- | Returned rich infos are in the same order as users -lookupRichInfoMultiUsers :: [UserId] -> (AppIO r) [(UserId, RichInfo)] +lookupRichInfoMultiUsers :: MonadClient m => [UserId] -> m [(UserId, RichInfo)] lookupRichInfoMultiUsers users = do mapMaybe (\(uid, mbRi) -> (uid,) . RichInfo <$> mbRi) <$> retry x1 (query richInfoSelectMulti (params LocalQuorum (Identity users))) @@ -402,12 +406,12 @@ lookupRichInfoMultiUsers users = do -- | Lookup user (no matter what status) and return 'TeamId'. Safe to use for authorization: -- suspended / deleted / ... users can't login, so no harm done if we authorize them *after* -- successful login. -lookupUserTeam :: UserId -> (AppIO r) (Maybe TeamId) +lookupUserTeam :: MonadClient m => UserId -> m (Maybe TeamId) lookupUserTeam u = (runIdentity =<<) <$> retry x1 (query1 teamSelect (params LocalQuorum (Identity u))) -lookupAuth :: (MonadClient m) => UserId -> m (Maybe (Maybe Password, AccountStatus)) +lookupAuth :: MonadClient m => (MonadClient m) => UserId -> m (Maybe (Maybe Password, AccountStatus)) lookupAuth u = fmap f <$> retry x1 (query1 authSelect (params LocalQuorum (Identity u))) where f (pw, st) = (pw, fromMaybe Active st) @@ -415,22 +419,22 @@ lookupAuth u = fmap f <$> retry x1 (query1 authSelect (params LocalQuorum (Ident -- | Return users with given IDs. -- -- Skips nonexistent users. /Does not/ skip users who have been deleted. -lookupUsers :: HavePendingInvitations -> [UserId] -> (AppIO r) [User] +lookupUsers :: (MonadClient m, MonadReader Env m) => HavePendingInvitations -> [UserId] -> m [User] lookupUsers hpi usrs = do loc <- setDefaultUserLocale <$> view settings domain <- viewFederationDomain toUsers domain loc hpi <$> retry x1 (query usersSelect (params LocalQuorum (Identity usrs))) -lookupAccount :: UserId -> (AppIO r) (Maybe UserAccount) +lookupAccount :: (MonadClient m, MonadReader Env m) => UserId -> m (Maybe UserAccount) lookupAccount u = listToMaybe <$> lookupAccounts [u] -lookupAccounts :: [UserId] -> (AppIO r) [UserAccount] +lookupAccounts :: (MonadClient m, MonadReader Env m) => [UserId] -> m [UserAccount] lookupAccounts usrs = do loc <- setDefaultUserLocale <$> view settings domain <- viewFederationDomain fmap (toUserAccount domain loc) <$> retry x1 (query accountsSelect (params LocalQuorum (Identity usrs))) -lookupServiceUser :: ProviderId -> ServiceId -> BotId -> (AppIO r) (Maybe (ConvId, Maybe TeamId)) +lookupServiceUser :: MonadClient m => ProviderId -> ServiceId -> BotId -> m (Maybe (ConvId, Maybe TeamId)) lookupServiceUser pid sid bid = retry x1 (query1 cql (params LocalQuorum (pid, sid, bid))) where cql :: PrepQuery R (ProviderId, ServiceId, BotId) (ConvId, Maybe TeamId) @@ -440,9 +444,10 @@ lookupServiceUser pid sid bid = retry x1 (query1 cql (params LocalQuorum (pid, s -- | NB: might return a lot of users, and therefore we do streaming here (page-by-page). lookupServiceUsers :: + MonadClient m => ProviderId -> ServiceId -> - ConduitM () [(BotId, ConvId, Maybe TeamId)] (AppIO r) () + ConduitM () [(BotId, ConvId, Maybe TeamId)] m () lookupServiceUsers pid sid = paginateC cql (paramsP LocalQuorum (pid, sid) 100) x1 where @@ -452,10 +457,11 @@ lookupServiceUsers pid sid = \WHERE provider = ? AND service = ?" lookupServiceUsersForTeam :: + MonadClient m => ProviderId -> ServiceId -> TeamId -> - ConduitM () [(BotId, ConvId)] (AppIO r) () + ConduitM () [(BotId, ConvId)] m () lookupServiceUsersForTeam pid sid tid = paginateC cql (paramsP LocalQuorum (pid, sid, tid) 100) x1 where diff --git a/services/brig/src/Brig/Data/UserKey.hs b/services/brig/src/Brig/Data/UserKey.hs index f4483c09b9b..d9afe3af532 100644 --- a/services/brig/src/Brig/Data/UserKey.hs +++ b/services/brig/src/Brig/Data/UserKey.hs @@ -35,7 +35,7 @@ module Brig.Data.UserKey ) where -import Brig.App (AppIO, digestSHA256) +import Brig.App (Env, digestSHA256) import Brig.Data.Instances () import qualified Brig.Data.User as User import Brig.Email @@ -120,11 +120,12 @@ keyTextOriginal (UserPhoneKey k) = fromPhone (phoneKeyOrig k) -- | Claim a 'UserKey' for a user. claimKey :: + (MonadClient m, MonadReader Env m) => -- | The key to claim. UserKey -> -- | The user claiming the key. UserId -> - (AppIO r) Bool + m Bool claimKey k u = do free <- keyAvailable k (Just u) when free (insertKey u k) @@ -134,11 +135,12 @@ claimKey k u = do -- A key is available if it is not already actived for another user or -- if the other user and the user looking to claim the key are the same. keyAvailable :: + MonadClient m => -- | The key to check. UserKey -> -- | The user looking to claim the key, if any. Maybe UserId -> - (AppIO r) Bool + m Bool keyAvailable k u = do o <- lookupKey k case (o, u) of @@ -146,32 +148,32 @@ keyAvailable k u = do (Just x, Just y) | x == y -> return True (Just x, _) -> not <$> User.isActivated x -lookupKey :: UserKey -> (AppIO r) (Maybe UserId) +lookupKey :: MonadClient m => UserKey -> m (Maybe UserId) lookupKey k = fmap runIdentity <$> retry x1 (query1 keySelect (params LocalQuorum (Identity $ keyText k))) -insertKey :: UserId -> UserKey -> (AppIO r) () +insertKey :: (MonadClient m, MonadReader Env m) => UserId -> UserKey -> m () insertKey u k = do hk <- hashKey k let kt = foldKey (\(_ :: Email) -> UKHashEmail) (\(_ :: Phone) -> UKHashPhone) k retry x5 $ write insertHashed (params LocalQuorum (hk, kt, u)) retry x5 $ write keyInsert (params LocalQuorum (keyText k, u)) -deleteKey :: UserKey -> (AppIO r) () +deleteKey :: (MonadClient m, MonadReader Env m) => UserKey -> m () deleteKey k = do hk <- hashKey k retry x5 $ write deleteHashed (params LocalQuorum (Identity hk)) retry x5 $ write keyDelete (params LocalQuorum (Identity $ keyText k)) -hashKey :: UserKey -> (AppIO r) UserKeyHash +hashKey :: MonadReader Env m => UserKey -> m UserKeyHash hashKey uk = do d <- view digestSHA256 let d' = digestBS d $ T.encodeUtf8 (keyText uk) return . UserKeyHash $ MH.MultihashDigest MH.SHA256 (B.length d') d' -lookupPhoneHashes :: [ByteString] -> (AppIO r) [(ByteString, UserId)] +lookupPhoneHashes :: MonadClient m => [ByteString] -> m [(ByteString, UserId)] lookupPhoneHashes hp = mapMaybe mk <$> retry x1 (query selectHashed (params One (Identity hashed))) where diff --git a/services/brig/src/Brig/Data/UserPendingActivation.hs b/services/brig/src/Brig/Data/UserPendingActivation.hs index 2f1222194d7..2388479446e 100644 --- a/services/brig/src/Brig/Data/UserPendingActivation.hs +++ b/services/brig/src/Brig/Data/UserPendingActivation.hs @@ -26,7 +26,6 @@ module Brig.Data.UserPendingActivation ) where -import Brig.App (AppIO) import Cassandra import Data.Id (UserId) import Data.Time (UTCTime) @@ -38,14 +37,14 @@ data UserPendingActivation = UserPendingActivation } deriving stock (Eq, Show, Ord) -usersPendingActivationAdd :: UserPendingActivation -> (AppIO r) () +usersPendingActivationAdd :: MonadClient m => UserPendingActivation -> m () usersPendingActivationAdd (UserPendingActivation uid expiresAt) = do retry x5 . write insertExpiration . params LocalQuorum $ (uid, expiresAt) where insertExpiration :: PrepQuery W (UserId, UTCTime) () insertExpiration = "INSERT INTO users_pending_activation (user, expires_at) VALUES (?, ?)" -usersPendingActivationList :: (AppIO r) (Page UserPendingActivation) +usersPendingActivationList :: MonadClient m => m (Page UserPendingActivation) usersPendingActivationList = do uncurry UserPendingActivation <$$> retry x1 (paginate selectExpired (params LocalQuorum ())) where @@ -53,12 +52,12 @@ usersPendingActivationList = do selectExpired = "SELECT user, expires_at FROM users_pending_activation" -usersPendingActivationRemove :: UserId -> (AppIO r) () +usersPendingActivationRemove :: MonadClient m => UserId -> m () usersPendingActivationRemove uid = usersPendingActivationRemoveMultiple [uid] -usersPendingActivationRemoveMultiple :: [UserId] -> (AppIO r) () +usersPendingActivationRemoveMultiple :: MonadClient m => [UserId] -> m () usersPendingActivationRemoveMultiple uids = - retry x5 . write deleteExpired . params LocalQuorum $ (Identity uids) + retry x5 . write deleteExpired . params LocalQuorum $ Identity uids where deleteExpired :: PrepQuery W (Identity [UserId]) () deleteExpired = diff --git a/services/brig/src/Brig/Email.hs b/services/brig/src/Brig/Email.hs index a411875b493..ff868636c2f 100644 --- a/services/brig/src/Brig/Email.hs +++ b/services/brig/src/Brig/Email.hs @@ -40,7 +40,7 @@ module Brig.Email where import qualified Brig.AWS as AWS -import Brig.App (AppIO, awsEnv, smtpEnv) +import Brig.App (Env, awsEnv, smtpEnv) import qualified Brig.SMTP as SMTP import Brig.Types import Control.Lens (view) @@ -49,7 +49,7 @@ import Imports import Network.Mail.Mime ------------------------------------------------------------------------------- -sendMail :: Mail -> (AppIO r) () +sendMail :: (MonadIO m, MonadReader Env m) => Mail -> m () sendMail m = view smtpEnv >>= \case Just smtp -> SMTP.sendMail smtp m diff --git a/services/brig/src/Brig/IO/Intra.hs b/services/brig/src/Brig/IO/Intra.hs index d2f239fdf00..d896e3970a0 100644 --- a/services/brig/src/Brig/IO/Intra.hs +++ b/services/brig/src/Brig/IO/Intra.hs @@ -57,6 +57,7 @@ module Brig.IO.Intra getTeamLegalHoldStatus, changeTeamStatus, getTeamSearchVisibility, + getVerificationCodeEnabled, -- * Legalhold guardLegalhold, @@ -80,6 +81,7 @@ import Brig.RPC import Brig.Types import Brig.Types.User.Event import qualified Brig.User.Search.Index as Search +import Cassandra (MonadClient) import Conduit (runConduit, (.|)) import Control.Error (ExceptT) import Control.Lens (view, (.~), (?~), (^.)) @@ -120,7 +122,7 @@ import System.Logger.Class as Log hiding (name, (.=)) import Wire.API.Federation.API.Brig import Wire.API.Federation.Error import Wire.API.Message (UserClients) -import Wire.API.Team.Feature (IncludeLockStatus (..), TeamFeatureName (..), TeamFeatureStatus) +import Wire.API.Team.Feature import Wire.API.Team.LegalHold (LegalholdProtectee) import qualified Wire.API.Team.Member as Member @@ -186,15 +188,15 @@ updateSearchIndex orig e = case e of -- no-ops UserCreated {} -> return () UserIdentityUpdated UserIdentityUpdatedData {..} -> do - when (isJust eiuEmail) $ Search.reindex orig + when (isJust eiuEmail) $ wrapClient $ Search.reindex orig UserIdentityRemoved {} -> return () UserLegalHoldDisabled {} -> return () UserLegalHoldEnabled {} -> return () LegalHoldClientRequested {} -> return () - UserSuspended {} -> Search.reindex orig - UserResumed {} -> Search.reindex orig - UserActivated {} -> Search.reindex orig - UserDeleted {} -> Search.reindex orig + UserSuspended {} -> wrapClient $ Search.reindex orig + UserResumed {} -> wrapClient $ Search.reindex orig + UserActivated {} -> wrapClient $ Search.reindex orig + UserDeleted {} -> wrapClient $ Search.reindex orig UserUpdated UserUpdatedData {..} -> do let interesting = or @@ -204,7 +206,7 @@ updateSearchIndex orig e = case e of isJust eupManagedBy, isJust eupSSOId || eupSSOIdRemoved ] - when interesting $ Search.reindex orig + when interesting $ wrapClient $ Search.reindex orig journalEvent :: UserId -> UserEvent -> (AppIO r) () journalEvent orig e = case e of @@ -254,14 +256,17 @@ dispatchNotifications orig conn e = case e of notifyUserDeletionLocals :: UserId -> Maybe ConnId -> List1 Event -> (AppIO r) () notifyUserDeletionLocals deleted conn event = do - recipients <- list1 deleted <$> lookupContactList deleted + recipients <- list1 deleted <$> wrapClient (lookupContactList deleted) notify event deleted Push.RouteDirect conn (pure recipients) notifyUserDeletionRemotes :: UserId -> (AppIO r) () notifyUserDeletionRemotes deleted = do - runConduit $ - Data.lookupRemoteConnectedUsersC deleted (fromInteger (natVal (Proxy @UserDeletedNotificationMaxConnections))) - .| C.mapM_ fanoutNotifications + e <- ask + fmap + wrapClient + runConduit + $ Data.lookupRemoteConnectedUsersC deleted (fromInteger (natVal (Proxy @UserDeletedNotificationMaxConnections))) + .| C.mapM_ (runAppIOLifted e . fanoutNotifications) where fanoutNotifications :: [Remote UserId] -> (AppIO r) () fanoutNotifications = mapM_ notifyBackend . bucketRemote @@ -279,7 +284,7 @@ notifyUserDeletionRemotes deleted = do whenLeft eitherFErr $ logFederationError (tDomain uids) - logFederationError :: Domain -> FederationError -> AppT r IO () + logFederationError :: Log.MonadLogger m => Domain -> FederationError -> m () logFederationError domain fErr = Log.err $ Log.msg ("Federation error while notifying remote backends of a user deletion." :: ByteString) @@ -398,9 +403,9 @@ notifyContacts events orig route conn = do env <- ask notify events orig route conn $ runAppT env $ - list1 orig <$> liftA2 (++) contacts teamContacts + list1 orig <$> liftA2 (++) (wrapClient contacts) teamContacts where - contacts :: (AppIO r) [UserId] + contacts :: MonadClient m => m [UserId] contacts = lookupContactList orig teamContacts :: (AppIO r) [UserId] teamContacts = screenMemberList =<< getTeamContacts orig @@ -916,7 +921,7 @@ memberIsTeamOwner :: TeamId -> UserId -> (AppIO r) Bool memberIsTeamOwner tid uid = do r <- galleyRequest GET $ - (paths ["i", "teams", toByteString' tid, "is-team-owner", toByteString' uid]) + paths ["i", "teams", toByteString' tid, "is-team-owner", toByteString' uid] pure $ responseStatus r /= status403 -- | Only works on 'BindingTeam's! The list of members returned is potentially truncated. @@ -988,6 +993,19 @@ getTeamSearchVisibility tid = paths ["i", "teams", toByteString' tid, "search-visibility"] . expect2xx +getVerificationCodeEnabled :: TeamId -> (AppIO r) Bool +getVerificationCodeEnabled tid = do + debug $ remote "galley" . msg (val "Get snd factor password challenge settings") + response <- galleyRequest GET req + status <- tfwoStatus <$> decodeBody "galley" response + case status of + TeamFeatureEnabled -> pure True + TeamFeatureDisabled -> pure False + where + req = + paths ["i", "teams", toByteString' tid, "features", toByteString' TeamFeatureSndFactorPasswordChallenge] + . expect2xx + -- | Calls 'Galley.API.updateTeamStatusH'. changeTeamStatus :: TeamId -> Team.TeamStatus -> Maybe Currency.Alpha -> (AppIO r) () changeTeamStatus tid s cur = do diff --git a/services/brig/src/Brig/InternalEvent/Process.hs b/services/brig/src/Brig/InternalEvent/Process.hs index 60b3b8d92f1..353a02642d6 100644 --- a/services/brig/src/Brig/InternalEvent/Process.hs +++ b/services/brig/src/Brig/InternalEvent/Process.hs @@ -42,7 +42,7 @@ onEvent n = handleTimeout $ case n of Log.info $ msg (val "Processing user delete event") ~~ field "user" (toByteString uid) - API.lookupAccount uid >>= mapM_ API.deleteAccount + wrapClient (API.lookupAccount uid) >>= mapM_ API.deleteAccount -- As user deletions are expensive resource-wise in the context of -- bulk user deletions (e.g. during team deletions), -- wait 'delay' ms before processing the next event @@ -60,7 +60,7 @@ onEvent n = handleTimeout $ case n of Just x -> pure x Nothing -> throwM (InternalEventTimeout n) -data InternalEventException +newtype InternalEventException = -- | 'onEvent' has timed out InternalEventTimeout InternalNotification deriving (Show) diff --git a/services/brig/src/Brig/Options.hs b/services/brig/src/Brig/Options.hs index a78957f2e5e..4ad889d5f94 100644 --- a/services/brig/src/Brig/Options.hs +++ b/services/brig/src/Brig/Options.hs @@ -32,6 +32,7 @@ import Data.Aeson (defaultOptions, fieldLabelModifier, genericParseJSON, withTex import qualified Data.Aeson as Aeson import Data.Aeson.Types (typeMismatch) import qualified Data.Char as Char +import qualified Data.Code as Code import Data.Domain (Domain (..)) import Data.Id import Data.LanguageCodes (ISO639_1 (EN)) @@ -459,6 +460,9 @@ data Opts = Opts data Settings = Settings { -- | Activation timeout, in seconds setActivationTimeout :: !Timeout, + -- | Default verification code timeout, in seconds + -- use `setVerificationTimeout` as the getter function which always provides a default value + setVerificationCodeTimeoutInternal :: !(Maybe Code.Timeout), -- | Team invitation timeout, in seconds setTeamInvitationTimeout :: !Timeout, -- | Check for expired users every so often, in seconds @@ -577,6 +581,12 @@ defaultUserLocale = defaultTemplateLocale setDefaultUserLocale :: Settings -> Locale setDefaultUserLocale = fromMaybe defaultUserLocale . setDefaultUserLocaleInternal +defVerificationTimeout :: Code.Timeout +defVerificationTimeout = Code.Timeout (60 * 10) -- 10 minutes + +setVerificationTimeout :: Settings -> Code.Timeout +setVerificationTimeout = fromMaybe defVerificationTimeout . setVerificationCodeTimeoutInternal + setDefaultTemplateLocale :: Settings -> Locale setDefaultTemplateLocale = fromMaybe defaultTemplateLocale . setDefaultTemplateLocaleInternal @@ -753,6 +763,7 @@ instance FromJSON Settings where { fieldLabelModifier = \case "setDefaultUserLocaleInternal" -> "setDefaultUserLocale" "setDefaultTemplateLocaleInternal" -> "setDefaultTemplateLocale" + "setVerificationCodeTimeoutInternal" -> "setVerificationTimeout" other -> other } diff --git a/services/brig/src/Brig/Phone.hs b/services/brig/src/Brig/Phone.hs index 81df86b45e1..c8c54f048a7 100644 --- a/services/brig/src/Brig/Phone.hs +++ b/services/brig/src/Brig/Phone.hs @@ -41,6 +41,7 @@ import Bilge.Retry (httpHandlers) import Brig.App import Brig.Budget import Brig.Types +import Cassandra (MonadClient) import Control.Lens (view) import Control.Monad.Catch import Control.Retry @@ -73,7 +74,10 @@ data PhoneException instance Exception PhoneException -sendCall :: forall r. Nexmo.Call -> AppIO r () +sendCall :: + (MonadClient m, MonadReader Env m, Log.MonadLogger m) => + Nexmo.Call -> + m () sendCall call = unless (isTestPhone $ Nexmo.callTo call) $ do m <- view httpManager cred <- view nexmoCreds @@ -99,9 +103,7 @@ sendCall call = unless (isTestPhone $ Nexmo.callTo call) $ do Nexmo.CallInternal -> True _ -> False ] - unreachable :: Nexmo.CallErrorResponse -> AppT r IO () unreachable ex = warn (toException ex) >> throwM PhoneNumberUnreachable - barred :: Nexmo.CallErrorResponse -> AppT r IO () barred ex = warn (toException ex) >> throwM PhoneNumberBarred warn ex = Log.warn $ @@ -109,12 +111,20 @@ sendCall call = unless (isTestPhone $ Nexmo.callTo call) $ do ~~ field "error" (show ex) ~~ field "phone" (Nexmo.callTo call) -sendSms :: forall r. Locale -> SMSMessage -> (AppIO r) () +sendSms :: + ( MonadClient m, + MonadCatch m, + Log.MonadLogger m, + MonadReader Env m + ) => + Locale -> + SMSMessage -> + m () sendSms loc SMSMessage {..} = unless (isTestPhone smsTo) $ do m <- view httpManager withSmsBudget smsTo $ do -- We try Nexmo first (cheaper and specialised to SMS) - f <- (sendNexmoSms m *> pure Nothing) `catches` nexmoFailed + f <- (sendNexmoSms m $> Nothing) `catches` nexmoFailed for_ f $ \ex -> do warn ex r <- try @_ @Twilio.ErrorResponse $ sendTwilioSms m @@ -132,7 +142,7 @@ sendSms loc SMSMessage {..} = unless (isTestPhone smsTo) $ do _ -> throwM ex' Right () -> return () where - sendNexmoSms :: Manager -> (AppIO r) () + sendNexmoSms :: (MonadIO f, MonadReader Env f) => Manager -> f () sendNexmoSms mgr = do crd <- view nexmoCreds void . liftIO . recovering x3 nexmoHandlers $ @@ -149,7 +159,7 @@ sendSms loc SMSMessage {..} = unless (isTestPhone smsTo) $ do ES -> Nexmo.UCS2 ZH -> Nexmo.UCS2 _ -> Nexmo.GSM7 - sendTwilioSms :: Manager -> (AppIO r) () + sendTwilioSms :: (MonadIO f, MonadReader Env f) => Manager -> f () sendTwilioSms mgr = do crd <- view twilioCreds void . liftIO . recovering x3 twilioHandlers $ @@ -179,9 +189,7 @@ sendSms loc SMSMessage {..} = unless (isTestPhone smsTo) $ do 20503 -> True -- Temporarily Unavailable _ -> False ] - unreachable :: Twilio.ErrorResponse -> AppT r IO () unreachable ex = warn (toException ex) >> throwM PhoneNumberUnreachable - barred :: Twilio.ErrorResponse -> AppT r IO () barred ex = warn (toException ex) >> throwM PhoneNumberBarred warn ex = Log.warn $ @@ -194,7 +202,7 @@ sendSms loc SMSMessage {..} = unless (isTestPhone smsTo) $ do -- | Validate a phone number. Returns the canonical -- E.164 format of the given phone number on success. -validatePhone :: Phone -> (AppIO r) (Maybe Phone) +validatePhone :: (MonadClient m, MonadReader Env m) => Phone -> m (Maybe Phone) validatePhone (Phone p) | isTestPhone p = return (Just (Phone p)) | otherwise = do @@ -223,7 +231,14 @@ smsBudget = budgetValue = 5 -- # of SMS within timeout } -withSmsBudget :: Text -> (AppIO r) a -> (AppIO r) a +withSmsBudget :: + ( MonadClient m, + Log.MonadLogger m, + MonadReader Env m + ) => + Text -> + m a -> + m a withSmsBudget phone go = do let k = BudgetKey ("sms#" <> phone) r <- withBudget k smsBudget go @@ -251,7 +266,14 @@ callBudget = budgetValue = 2 -- # of voice calls within timeout } -withCallBudget :: Text -> (AppIO r) a -> (AppIO r) a +withCallBudget :: + ( MonadClient m, + Log.MonadLogger m, + MonadReader Env m + ) => + Text -> + m a -> + m a withCallBudget phone go = do let k = BudgetKey ("call#" <> phone) r <- withBudget k callBudget go diff --git a/services/brig/src/Brig/Provider/API.hs b/services/brig/src/Brig/Provider/API.hs index f6028eafba6..5bc64f98f49 100644 --- a/services/brig/src/Brig/Provider/API.hs +++ b/services/brig/src/Brig/Provider/API.hs @@ -29,7 +29,7 @@ import qualified Brig.API.Client as Client import Brig.API.Error import Brig.API.Handler import Brig.API.Types (PasswordResetError (..)) -import Brig.App (AppIO, internalEvents, settings, viewFederationDomain) +import Brig.App import qualified Brig.Code as Code import qualified Brig.Data.Client as User import qualified Brig.Data.User as User @@ -54,6 +54,7 @@ import qualified Brig.ZAuth as ZAuth import Control.Error (throwE) import Control.Exception.Enclosed (handleAny) import Control.Lens (view, (^.)) +import Control.Monad.Except import Data.Aeson hiding (json) import Data.ByteString.Conversion import qualified Data.ByteString.Lazy.Char8 as LC8 @@ -330,14 +331,14 @@ newAccount new = do let descr = fromRange (Public.newProviderDescr new) let url = Public.newProviderUrl new let emailKey = mkEmailKey email - DB.lookupKey emailKey >>= mapM_ (const $ throwStd emailExists) + wrapClientE (DB.lookupKey emailKey) >>= mapM_ (const $ throwStd emailExists) (safePass, newPass) <- case pass of Just newPass -> (,Nothing) <$> mkSafePassword newPass Nothing -> do newPass <- genPassword safePass <- mkSafePassword newPass return (safePass, Just newPass) - pid <- DB.insertAccount name safePass url descr + pid <- wrapClientE $ DB.insertAccount name safePass url descr gen <- Code.mkGen (Code.ForEmail email) code <- Code.generate @@ -346,7 +347,7 @@ newAccount new = do (Code.Retries 3) (Code.Timeout (3600 * 24)) -- 24h (Just (toUUID pid)) - Code.insert code + wrapClientE $ Code.insert code let key = Code.codeKey code let val = Code.codeValue code lift $ sendActivationMail name email key val False @@ -358,17 +359,17 @@ activateAccountKeyH (key ::: val) = do activateAccountKey :: Code.Key -> Code.Value -> (Handler r) (Maybe Public.ProviderActivationResponse) activateAccountKey key val = do - c <- Code.verify key Code.IdentityVerification val >>= maybeInvalidCode + c <- wrapClientE (Code.verify key Code.IdentityVerification val) >>= maybeInvalidCode (pid, email) <- case (Code.codeAccount c, Code.codeForEmail c) of (Just p, Just e) -> return (Id p, e) _ -> throwErrorDescriptionType @InvalidCode - (name, memail, _url, _descr) <- DB.lookupAccountData pid >>= maybeInvalidCode + (name, memail, _url, _descr) <- wrapClientE (DB.lookupAccountData pid) >>= maybeInvalidCode case memail of Just email' | email == email' -> return Nothing Just email' -> do -- Ensure we remove any pending password reset gen <- Code.mkGen (Code.ForEmail email') - lift $ Code.delete (Code.genKey gen) Code.PasswordReset + lift $ wrapClient $ Code.delete (Code.genKey gen) Code.PasswordReset -- Activate the new and remove the old key activate pid (Just email') email return . Just $ Public.ProviderActivationResponse email @@ -388,10 +389,10 @@ getActivationCode e = do Right em -> return em Left _ -> throwStd (errorDescriptionTypeToWai @InvalidEmail) gen <- Code.mkGen (Code.ForEmail email) - code <- Code.lookup (Code.genKey gen) Code.IdentityVerification + code <- wrapClientE $ Code.lookup (Code.genKey gen) Code.IdentityVerification maybe (throwStd activationKeyNotFound) (return . FoundActivationCode) code -data FoundActivationCode = FoundActivationCode Code.Code +newtype FoundActivationCode = FoundActivationCode Code.Code instance ToJSON FoundActivationCode where toJSON (FoundActivationCode vcode) = @@ -404,10 +405,10 @@ approveAccountKeyH (key ::: val) = do approveAccountKey :: Code.Key -> Code.Value -> (Handler r) () approveAccountKey key val = do - c <- Code.verify key Code.AccountApproval val >>= maybeInvalidCode + c <- wrapClientE (Code.verify key Code.AccountApproval val) >>= maybeInvalidCode case (Code.codeAccount c, Code.codeForEmail c) of (Just pid, Just email) -> do - (name, _, _, _) <- DB.lookupAccountData (Id pid) >>= maybeInvalidCode + (name, _, _, _) <- wrapClientE (DB.lookupAccountData (Id pid)) >>= maybeInvalidCode activate (Id pid) Nothing email lift $ sendApprovalConfirmMail name email _ -> throwErrorDescriptionType @InvalidCode @@ -419,8 +420,8 @@ loginH req = do login :: Public.ProviderLogin -> (Handler r) ZAuth.ProviderToken login l = do - pid <- DB.lookupKey (mkEmailKey (providerLoginEmail l)) >>= maybeBadCredentials - pass <- DB.lookupPassword pid >>= maybeBadCredentials + pid <- wrapClientE (DB.lookupKey (mkEmailKey (providerLoginEmail l))) >>= maybeBadCredentials + pass <- wrapClientE (DB.lookupPassword pid) >>= maybeBadCredentials unless (verifyPassword (providerLoginPassword l) pass) $ throwErrorDescriptionType @BadCredentials ZAuth.newProviderToken pid @@ -431,9 +432,9 @@ beginPasswordResetH req = do beginPasswordReset :: Public.PasswordReset -> (Handler r) () beginPasswordReset (Public.PasswordReset target) = do - pid <- DB.lookupKey (mkEmailKey target) >>= maybeBadCredentials + pid <- wrapClientE (DB.lookupKey (mkEmailKey target)) >>= maybeBadCredentials gen <- Code.mkGen (Code.ForEmail target) - pending <- lift $ Code.lookup (Code.genKey gen) Code.PasswordReset + pending <- lift . wrapClient $ Code.lookup (Code.genKey gen) Code.PasswordReset code <- case pending of Just p -> throwE $ pwResetError (PasswordResetInProgress . Just $ Code.codeTTL p) Nothing -> @@ -443,7 +444,7 @@ beginPasswordReset (Public.PasswordReset target) = do (Code.Retries 3) (Code.Timeout 3600) -- 1h (Just (toUUID pid)) - Code.insert code + wrapClientE $ Code.insert code lift $ sendPasswordResetMail target (Code.codeKey code) (Code.codeValue code) completePasswordResetH :: JsonRequest Public.CompletePasswordReset -> (Handler r) Response @@ -452,15 +453,16 @@ completePasswordResetH req = do completePasswordReset :: Public.CompletePasswordReset -> (Handler r) () completePasswordReset (Public.CompletePasswordReset key val newpwd) = do - code <- Code.verify key Code.PasswordReset val >>= maybeInvalidCode + code <- wrapClientE (Code.verify key Code.PasswordReset val) >>= maybeInvalidCode case Id <$> Code.codeAccount code of Nothing -> throwE $ pwResetError InvalidPasswordResetCode Just pid -> do - oldpass <- DB.lookupPassword pid >>= maybeBadCredentials + oldpass <- wrapClientE (DB.lookupPassword pid) >>= maybeBadCredentials when (verifyPassword newpwd oldpass) $ do throwStd newPasswordMustDiffer - DB.updateAccountPassword pid newpwd - Code.delete key Code.PasswordReset + wrapClientE $ do + DB.updateAccountPassword pid newpwd + Code.delete key Code.PasswordReset -------------------------------------------------------------------------------- -- Provider API @@ -472,8 +474,7 @@ getAccountH pid = do Nothing -> setStatus status404 empty getAccount :: ProviderId -> (Handler r) (Maybe Public.Provider) -getAccount pid = do - DB.lookupAccount pid +getAccount = wrapClientE . DB.lookupAccount updateAccountProfileH :: ProviderId ::: JsonRequest Public.UpdateProvider -> (Handler r) Response updateAccountProfileH (pid ::: req) = do @@ -481,12 +482,13 @@ updateAccountProfileH (pid ::: req) = do updateAccountProfile :: ProviderId -> Public.UpdateProvider -> (Handler r) () updateAccountProfile pid upd = do - _ <- DB.lookupAccount pid >>= maybeInvalidProvider - DB.updateAccountProfile - pid - (updateProviderName upd) - (updateProviderUrl upd) - (updateProviderDescr upd) + _ <- wrapClientE (DB.lookupAccount pid) >>= maybeInvalidProvider + wrapClientE $ + DB.updateAccountProfile + pid + (updateProviderName upd) + (updateProviderUrl upd) + (updateProviderDescr upd) updateAccountEmailH :: ProviderId ::: JsonRequest Public.EmailUpdate -> (Handler r) Response updateAccountEmailH (pid ::: req) = do @@ -498,7 +500,7 @@ updateAccountEmail pid (Public.EmailUpdate new) = do Right em -> return em Left _ -> throwStd (errorDescriptionTypeToWai @InvalidEmail) let emailKey = mkEmailKey email - DB.lookupKey emailKey >>= mapM_ (const $ throwStd emailExists) + wrapClientE (DB.lookupKey emailKey) >>= mapM_ (const $ throwStd emailExists) gen <- Code.mkGen (Code.ForEmail email) code <- Code.generate @@ -507,7 +509,7 @@ updateAccountEmail pid (Public.EmailUpdate new) = do (Code.Retries 3) (Code.Timeout (3600 * 24)) -- 24h (Just (toUUID pid)) - Code.insert code + wrapClientE $ Code.insert code lift $ sendActivationMail (Name "name") email (Code.codeKey code) (Code.codeValue code) True updateAccountPasswordH :: ProviderId ::: JsonRequest Public.PasswordChange -> (Handler r) Response @@ -516,12 +518,12 @@ updateAccountPasswordH (pid ::: req) = do updateAccountPassword :: ProviderId -> Public.PasswordChange -> (Handler r) () updateAccountPassword pid upd = do - pass <- DB.lookupPassword pid >>= maybeBadCredentials + pass <- wrapClientE (DB.lookupPassword pid) >>= maybeBadCredentials unless (verifyPassword (cpOldPassword upd) pass) $ throwErrorDescriptionType @BadCredentials when (verifyPassword (cpNewPassword upd) pass) $ throwStd newPasswordMustDiffer - DB.updateAccountPassword pid (cpNewPassword upd) + wrapClientE $ DB.updateAccountPassword pid (cpNewPassword upd) addServiceH :: ProviderId ::: JsonRequest Public.NewService -> (Handler r) Response addServiceH (pid ::: req) = do @@ -529,7 +531,7 @@ addServiceH (pid ::: req) = do addService :: ProviderId -> Public.NewService -> (Handler r) Public.NewServiceResponse addService pid new = do - _ <- DB.lookupAccount pid >>= maybeInvalidProvider + _ <- wrapClientE (DB.lookupAccount pid) >>= maybeInvalidProvider let name = newServiceName new let summary = fromRange (newServiceSummary new) let descr = fromRange (newServiceDescr new) @@ -539,7 +541,7 @@ addService pid new = do let tags = fromRange (newServiceTags new) (pk, fp) <- validateServiceKey pubkey >>= maybeInvalidServiceKey token <- maybe randServiceToken return (newServiceToken new) - sid <- DB.insertService pid name summary descr baseUrl token pk fp assets tags + sid <- wrapClientE $ DB.insertService pid name summary descr baseUrl token pk fp assets tags let rstoken = maybe (Just token) (const Nothing) (newServiceToken new) return $ Public.NewServiceResponse sid rstoken @@ -547,15 +549,15 @@ listServicesH :: ProviderId -> (Handler r) Response listServicesH pid = json <$> listServices pid listServices :: ProviderId -> (Handler r) [Public.Service] -listServices = DB.listServices +listServices = wrapClientE . DB.listServices getServiceH :: ProviderId ::: ServiceId -> (Handler r) Response getServiceH (pid ::: sid) = do json <$> getService pid sid getService :: ProviderId -> ServiceId -> (Handler r) Public.Service -getService pid sid = do - DB.lookupService pid sid >>= maybeServiceNotFound +getService pid sid = + wrapClientE (DB.lookupService pid sid) >>= maybeServiceNotFound updateServiceH :: ProviderId ::: ServiceId ::: JsonRequest Public.UpdateService -> (Handler r) Response updateServiceH (pid ::: sid ::: req) = do @@ -563,20 +565,31 @@ updateServiceH (pid ::: sid ::: req) = do updateService :: ProviderId -> ServiceId -> Public.UpdateService -> (Handler r) () updateService pid sid upd = do - _ <- DB.lookupAccount pid >>= maybeInvalidProvider + _ <- wrapClientE (DB.lookupAccount pid) >>= maybeInvalidProvider -- Update service profile - svc <- DB.lookupService pid sid >>= maybeServiceNotFound + svc <- wrapClientE (DB.lookupService pid sid) >>= maybeServiceNotFound let name = serviceName svc let newName = updateServiceName upd - let nameChange = liftM2 (,) (pure name) newName + let nameChange = fmap (name,) newName let tags = unsafeRange (serviceTags svc) let newTags = updateServiceTags upd - let tagsChange = liftM2 (,) (pure tags) (rcast <$> newTags) + let tagsChange = fmap (tags,) (rcast <$> newTags) let newSummary = fromRange <$> updateServiceSummary upd let newDescr = fromRange <$> updateServiceDescr upd let newAssets = updateServiceAssets upd -- Update service, tags/prefix index if the service is enabled - DB.updateService pid sid name tags nameChange newSummary newDescr newAssets tagsChange (serviceEnabled svc) + wrapClientE $ + DB.updateService + pid + sid + name + tags + nameChange + newSummary + newDescr + newAssets + tagsChange + (serviceEnabled svc) updateServiceConnH :: ProviderId ::: ServiceId ::: JsonRequest Public.UpdateServiceConn -> (Handler r) Response updateServiceConnH (pid ::: sid ::: req) = do @@ -584,11 +597,11 @@ updateServiceConnH (pid ::: sid ::: req) = do updateServiceConn :: ProviderId -> ServiceId -> Public.UpdateServiceConn -> (Handler r) () updateServiceConn pid sid upd = do - pass <- DB.lookupPassword pid >>= maybeBadCredentials + pass <- wrapClientE (DB.lookupPassword pid) >>= maybeBadCredentials unless (verifyPassword (updateServiceConnPassword upd) pass) $ throwErrorDescriptionType @BadCredentials - scon <- DB.lookupServiceConn pid sid >>= maybeServiceNotFound - svc <- DB.lookupServiceProfile pid sid >>= maybeServiceNotFound + scon <- wrapClientE (DB.lookupServiceConn pid sid) >>= maybeServiceNotFound + svc <- wrapClientE (DB.lookupServiceProfile pid sid) >>= maybeServiceNotFound let newBaseUrl = updateServiceConnUrl upd let newTokens = maybeList1 . fromRange =<< updateServiceConnTokens upd let newEnabled = updateServiceConnEnabled upd @@ -596,7 +609,7 @@ updateServiceConn pid sid upd = do keys <- forM newKeyPems (mapM (validateServiceKey >=> maybeInvalidServiceKey)) let newKeys = keys >>= maybeList1 let newFps = fmap snd <$> newKeys - DB.updateServiceConn pid sid newBaseUrl newTokens newKeys newEnabled + wrapClientE $ DB.updateServiceConn pid sid newBaseUrl newTokens newKeys newEnabled let scon' = scon { sconBaseUrl = fromMaybe (sconBaseUrl scon) newBaseUrl, @@ -611,9 +624,10 @@ updateServiceConn pid sid upd = do let name = serviceProfileName svc let tags = unsafeRange (serviceProfileTags svc) -- Update index, make it visible over search - if sconEnabled scon - then DB.deleteServiceIndexes pid sid name tags - else DB.insertServiceIndexes pid sid name tags + wrapClientE $ + if sconEnabled scon + then DB.deleteServiceIndexes pid sid name tags + else DB.insertServiceIndexes pid sid name tags -- TODO: Send informational email to provider. @@ -633,27 +647,30 @@ deleteServiceH (pid ::: sid ::: req) = do -- delete the service. See 'finishDeleteService'. deleteService :: ProviderId -> ServiceId -> Public.DeleteService -> (Handler r) () deleteService pid sid del = do - pass <- DB.lookupPassword pid >>= maybeBadCredentials + pass <- wrapClientE (DB.lookupPassword pid) >>= maybeBadCredentials unless (verifyPassword (deleteServicePassword del) pass) $ throwErrorDescriptionType @BadCredentials - _ <- DB.lookupService pid sid >>= maybeServiceNotFound + _ <- wrapClientE (DB.lookupService pid sid) >>= maybeServiceNotFound -- Disable the service - DB.updateServiceConn pid sid Nothing Nothing Nothing (Just False) + wrapClientE $ DB.updateServiceConn pid sid Nothing Nothing Nothing (Just False) -- Create an event queue <- view internalEvents lift $ Queue.enqueue queue (Internal.DeleteService pid sid) finishDeleteService :: ProviderId -> ServiceId -> (AppIO r) () finishDeleteService pid sid = do - mbSvc <- DB.lookupService pid sid + e <- ask + mbSvc <- wrapClient $ DB.lookupService pid sid for_ mbSvc $ \svc -> do let tags = unsafeRange (serviceTags svc) name = serviceName svc - runConduit $ - User.lookupServiceUsers pid sid - .| C.mapM_ (pooledMapConcurrentlyN_ 16 kick) + fmap + wrapClient + runConduit + $ User.lookupServiceUsers pid sid + .| C.mapM_ (runAppIOLifted e . pooledMapConcurrentlyN_ 16 kick) RPC.removeServiceConn pid sid - DB.deleteService pid sid name tags + wrapClient $ DB.deleteService pid sid name tags where kick (bid, cid, _) = deleteBot (botUserId bid) Nothing bid cid @@ -663,19 +680,20 @@ deleteAccountH (pid ::: req) = do deleteAccount :: ProviderId -> Public.DeleteProvider -> (Handler r) () deleteAccount pid del = do - prov <- DB.lookupAccount pid >>= maybeInvalidProvider - pass <- DB.lookupPassword pid >>= maybeBadCredentials + prov <- wrapClientE (DB.lookupAccount pid) >>= maybeInvalidProvider + pass <- wrapClientE (DB.lookupPassword pid) >>= maybeBadCredentials unless (verifyPassword (deleteProviderPassword del) pass) $ throwErrorDescriptionType @BadCredentials - svcs <- DB.listServices pid + svcs <- wrapClientE $ DB.listServices pid forM_ svcs $ \svc -> do let sid = serviceId svc let tags = unsafeRange (serviceTags svc) name = serviceName svc lift $ RPC.removeServiceConn pid sid - DB.deleteService pid sid name tags - DB.deleteKey (mkEmailKey (providerEmail prov)) - DB.deleteAccount pid + wrapClientE $ DB.deleteService pid sid name tags + wrapClientE $ do + DB.deleteKey (mkEmailKey (providerEmail prov)) + DB.deleteAccount pid -------------------------------------------------------------------------------- -- User API @@ -685,24 +703,23 @@ getProviderProfileH pid = do json <$> getProviderProfile pid getProviderProfile :: ProviderId -> (Handler r) Public.ProviderProfile -getProviderProfile pid = do - DB.lookupAccountProfile pid >>= maybeProviderNotFound +getProviderProfile pid = + wrapClientE (DB.lookupAccountProfile pid) >>= maybeProviderNotFound listServiceProfilesH :: ProviderId -> (Handler r) Response listServiceProfilesH pid = do json <$> listServiceProfiles pid listServiceProfiles :: ProviderId -> (Handler r) [Public.ServiceProfile] -listServiceProfiles pid = do - DB.listServiceProfiles pid +listServiceProfiles = wrapClientE . DB.listServiceProfiles getServiceProfileH :: ProviderId ::: ServiceId -> (Handler r) Response getServiceProfileH (pid ::: sid) = do json <$> getServiceProfile pid sid getServiceProfile :: ProviderId -> ServiceId -> (Handler r) Public.ServiceProfile -getServiceProfile pid sid = do - DB.lookupServiceProfile pid sid >>= maybeServiceNotFound +getServiceProfile pid sid = + wrapClientE (DB.lookupServiceProfile pid sid) >>= maybeServiceNotFound searchServiceProfilesH :: Maybe (Public.QueryAnyTags 1 3) ::: Maybe Text ::: Range 10 100 Int32 -> (Handler r) Response searchServiceProfilesH (qt ::: start ::: size) = do @@ -715,9 +732,9 @@ searchServiceProfilesH (qt ::: start ::: size) = do searchServiceProfiles :: Maybe (Public.QueryAnyTags 1 3) -> Maybe Text -> Range 10 100 Int32 -> (Handler r) Public.ServiceProfilePage searchServiceProfiles Nothing (Just start) size = do prefix :: Range 1 128 Text <- rangeChecked start - DB.paginateServiceNames (Just prefix) (fromRange size) =<< setProviderSearchFilter <$> view settings + wrapClientE . DB.paginateServiceNames (Just prefix) (fromRange size) . setProviderSearchFilter =<< view settings searchServiceProfiles (Just tags) start size = do - DB.paginateServiceTags tags start (fromRange size) =<< setProviderSearchFilter <$> view settings + (wrapClientE . DB.paginateServiceTags tags start (fromRange size)) . setProviderSearchFilter =<< view settings searchServiceProfiles Nothing Nothing _ = do throwStd $ badRequest "At least `tags` or `start` must be provided." @@ -739,11 +756,11 @@ searchTeamServiceProfiles uid tid prefix filterDisabled size = do -- Check that the user actually belong to the team they claim they -- belong to. (Note: the 'tid' team might not even exist but we'll throw -- 'insufficientTeamPermissions' anyway) - teamId <- lift $ User.lookupUserTeam uid + teamId <- lift $ wrapClient $ User.lookupUserTeam uid unless (Just tid == teamId) $ throwStd insufficientTeamPermissions -- Get search results - DB.paginateServiceWhitelist tid prefix filterDisabled (fromRange size) + wrapClientE $ DB.paginateServiceWhitelist tid prefix filterDisabled (fromRange size) getServiceTagListH :: () -> (Handler r) Response getServiceTagListH () = json <$> getServiceTagList () @@ -767,34 +784,37 @@ data UpdateServiceWhitelistResp updateServiceWhitelist :: UserId -> ConnId -> TeamId -> Public.UpdateServiceWhitelist -> (Handler r) UpdateServiceWhitelistResp updateServiceWhitelist uid con tid upd = do + e <- ask let pid = updateServiceWhitelistProvider upd sid = updateServiceWhitelistService upd newWhitelisted = updateServiceWhitelistStatus upd -- Preconditions ensurePermissions uid tid (Set.toList Teams.serviceWhitelistPermissions) - _ <- DB.lookupService pid sid >>= maybeServiceNotFound + _ <- wrapClientE (DB.lookupService pid sid) >>= maybeServiceNotFound -- Add to various tables - whitelisted <- DB.getServiceWhitelistStatus tid pid sid + whitelisted <- wrapClientE $ DB.getServiceWhitelistStatus tid pid sid case (whitelisted, newWhitelisted) of (False, False) -> return UpdateServiceWhitelistRespUnchanged (True, True) -> return UpdateServiceWhitelistRespUnchanged (False, True) -> do - DB.insertServiceWhitelist tid pid sid + wrapClientE $ DB.insertServiceWhitelist tid pid sid return UpdateServiceWhitelistRespChanged (True, False) -> do -- When the service is de-whitelisted, remove its bots from team -- conversations lift $ - runConduit $ - User.lookupServiceUsersForTeam pid sid tid + fmap + wrapClient + runConduit + $ User.lookupServiceUsersForTeam pid sid tid .| C.mapM_ - ( pooledMapConcurrentlyN_ - 16 - ( \(bid, cid) -> - deleteBot uid (Just con) bid cid - ) + ( runAppIOLifted e + . pooledMapConcurrentlyN_ + 16 + ( uncurry (deleteBot uid (Just con)) + ) ) - DB.deleteServiceWhitelist (Just tid) pid sid + wrapClientE $ DB.deleteServiceWhitelist (Just tid) pid sid return UpdateServiceWhitelistRespChanged addBotH :: UserId ::: ConnId ::: ConvId ::: JsonRequest Public.AddBot -> (Handler r) Response @@ -803,7 +823,7 @@ addBotH (zuid ::: zcon ::: cid ::: req) = do addBot :: UserId -> ConnId -> ConvId -> Public.AddBot -> (Handler r) Public.AddBotResponse addBot zuid zcon cid add = do - zusr <- lift (User.lookupUser NoPendingInvitations zuid) >>= maybeInvalidUser + zusr <- lift (wrapClient $ User.lookupUser NoPendingInvitations zuid) >>= maybeInvalidUser let pid = addBotProvider add let sid = addBotService add -- Get the conversation and check preconditions @@ -819,12 +839,12 @@ addBot zuid zcon cid add = do unless (Set.member ServiceAccessRole (cnvAccessRoles cnv)) $ throwStd invalidConv -- Lookup the relevant service data - scon <- DB.lookupServiceConn pid sid >>= maybeServiceNotFound + scon <- wrapClientE (DB.lookupServiceConn pid sid) >>= maybeServiceNotFound unless (sconEnabled scon) $ throwStd serviceDisabled - svp <- DB.lookupServiceProfile pid sid >>= maybeServiceNotFound + svp <- wrapClientE (DB.lookupServiceProfile pid sid) >>= maybeServiceNotFound for_ (cnvTeam cnv) $ \tid -> do - whitelisted <- DB.getServiceWhitelistStatus tid pid sid + whitelisted <- wrapClientE $ DB.getServiceWhitelistStatus tid pid sid unless whitelisted $ throwStd serviceNotWhitelisted -- Prepare a user ID, client ID and token for the bot. @@ -853,14 +873,14 @@ addBot zuid zcon cid add = do (newClient PermanentClientType (Ext.rsNewBotLastPrekey rs)) { newClientPrekeys = Ext.rsNewBotPrekeys rs } - lift $ User.insertAccount (UserAccount usr Active) (Just (cid, cnvTeam cnv)) Nothing True + lift $ wrapClient $ User.insertAccount (UserAccount usr Active) (Just (cid, cnvTeam cnv)) Nothing True maxPermClients <- fromMaybe Opt.defUserMaxPermClients . Opt.setUserMaxPermClients <$> view settings (clt, _, _) <- do _ <- do -- if we want to protect bots against lh, 'addClient' cannot just send lh capability -- implicitly in the next line. pure $ FutureWork @'UnprotectedBot undefined - User.addClient (botUserId bid) bcl newClt maxPermClients Nothing (Just $ Set.singleton Public.ClientSupportsLegalholdImplicitConsent) + wrapClientE (User.addClient (botUserId bid) bcl newClt maxPermClients Nothing (Just $ Set.singleton Public.ClientSupportsLegalholdImplicitConsent)) !>> const (StdError badGateway) -- MalformedPrekeys -- Add the bot to the conversation @@ -902,7 +922,7 @@ botGetSelfH bot = json <$> botGetSelf bot botGetSelf :: BotId -> (Handler r) Public.UserProfile botGetSelf bot = do - p <- lift $ User.lookupUser NoPendingInvitations (botUserId bot) + p <- lift $ wrapClient $ User.lookupUser NoPendingInvitations (botUserId bot) maybe (throwErrorDescriptionType @UserNotFound) (return . (`Public.publicProfile` UserLegalHoldNoConsent)) p botGetClientH :: BotId -> (Handler r) Response @@ -910,8 +930,8 @@ botGetClientH bot = do maybe (throwErrorDescriptionType @ClientNotFound) (pure . json) =<< lift (botGetClient bot) botGetClient :: BotId -> (AppIO r) (Maybe Public.Client) -botGetClient bot = do - listToMaybe <$> User.lookupClients (botUserId bot) +botGetClient bot = + listToMaybe <$> wrapClient (User.lookupClients (botUserId bot)) botListPrekeysH :: BotId -> (Handler r) Response botListPrekeysH bot = do @@ -919,10 +939,10 @@ botListPrekeysH bot = do botListPrekeys :: BotId -> (Handler r) [Public.PrekeyId] botListPrekeys bot = do - clt <- lift $ listToMaybe <$> User.lookupClients (botUserId bot) + clt <- lift $ listToMaybe <$> wrapClient (User.lookupClients (botUserId bot)) case clientId <$> clt of Nothing -> return [] - Just ci -> lift (User.lookupPrekeyIds (botUserId bot) ci) + Just ci -> lift (wrapClient $ User.lookupPrekeyIds (botUserId bot) ci) botUpdatePrekeysH :: BotId ::: JsonRequest Public.UpdateBotPrekeys -> (Handler r) Response botUpdatePrekeysH (bot ::: req) = do @@ -930,12 +950,12 @@ botUpdatePrekeysH (bot ::: req) = do botUpdatePrekeys :: BotId -> Public.UpdateBotPrekeys -> (Handler r) () botUpdatePrekeys bot upd = do - clt <- lift $ listToMaybe <$> User.lookupClients (botUserId bot) + clt <- lift $ listToMaybe <$> wrapClient (User.lookupClients (botUserId bot)) case clt of Nothing -> throwErrorDescriptionType @ClientNotFound Just c -> do let pks = updateBotPrekeyList upd - User.updatePrekeys (botUserId bot) (clientId c) pks !>> clientDataError + wrapClientE (User.updatePrekeys (botUserId bot) (clientId c) pks) !>> clientDataError botClaimUsersPrekeysH :: JsonRequest Public.UserClients -> (Handler r) Response botClaimUsersPrekeysH req = do @@ -954,7 +974,7 @@ botListUserProfilesH uids = do botListUserProfiles :: List UserId -> (Handler r) [Public.BotUserView] botListUserProfiles uids = do - us <- lift $ User.lookupUsers NoPendingInvitations (fromList uids) + us <- lift . wrapClient $ User.lookupUsers NoPendingInvitations (fromList uids) return (map mkBotUserView us) botGetUserClientsH :: UserId -> (Handler r) Response @@ -962,8 +982,8 @@ botGetUserClientsH uid = do json <$> lift (botGetUserClients uid) botGetUserClients :: UserId -> (AppIO r) [Public.PubClient] -botGetUserClients uid = do - pubClient <$$> User.lookupClients uid +botGetUserClients uid = + pubClient <$$> wrapClient (User.lookupClients uid) where pubClient c = Public.PubClient (clientId c) (clientClass c) @@ -973,7 +993,7 @@ botDeleteSelfH (bid ::: cid) = do botDeleteSelf :: BotId -> ConvId -> (Handler r) () botDeleteSelf bid cid = do - bot <- lift $ User.lookupUser NoPendingInvitations (botUserId bid) + bot <- lift . wrapClient $ User.lookupUser NoPendingInvitations (botUserId bid) _ <- maybeInvalidBot (userService =<< bot) _ <- lift $ deleteBot (botUserId bid) Nothing bid cid return () @@ -987,10 +1007,10 @@ minRsaKeySize = 256 -- Bytes (= 2048 bits) activate :: ProviderId -> Maybe Public.Email -> Public.Email -> (Handler r) () activate pid old new = do let emailKey = mkEmailKey new - taken <- maybe False (/= pid) <$> DB.lookupKey emailKey + taken <- maybe False (/= pid) <$> wrapClientE (DB.lookupKey emailKey) when taken $ throwStd emailExists - DB.insertKey pid (mkEmailKey <$> old) emailKey + wrapClientE $ DB.insertKey pid (mkEmailKey <$> old) emailKey deleteBot :: UserId -> Maybe ConnId -> BotId -> ConvId -> (AppIO r) (Maybe Public.Event) deleteBot zusr zcon bid cid = do @@ -998,22 +1018,22 @@ deleteBot zusr zcon bid cid = do ev <- RPC.removeBotMember zusr zcon cid bid -- Delete the bot user and client let buid = botUserId bid - mbUser <- User.lookupUser NoPendingInvitations buid - User.lookupClients buid >>= mapM_ (User.rmClient buid . clientId) + mbUser <- wrapClient $ User.lookupUser NoPendingInvitations buid + wrapClient (User.lookupClients buid) >>= mapM_ (wrapClient . User.rmClient buid . clientId) for_ (userService =<< mbUser) $ \sref -> do let pid = sref ^. serviceRefProvider sid = sref ^. serviceRefId - User.deleteServiceUser pid sid bid + wrapClient $ User.deleteServiceUser pid sid bid -- TODO: Consider if we can actually delete the bot user entirely, -- i.e. not just marking the account as deleted. - User.updateStatus buid Deleted + wrapClient $ User.updateStatus buid Deleted return ev validateServiceKey :: MonadIO m => Public.ServiceKeyPEM -> m (Maybe (Public.ServiceKey, Fingerprint Rsa)) validateServiceKey pem = liftIO $ readPublicKey >>= \pk -> - case join (SSL.toPublicKey <$> pk) of + case SSL.toPublicKey =<< pk of Nothing -> return Nothing Just pk' -> do Just sha <- SSL.getDigestByName "SHA256" @@ -1029,7 +1049,7 @@ validateServiceKey pem = readPublicKey = handleAny (const $ return Nothing) - (SSL.readPublicKey (LC8.unpack (toByteString pem)) >>= return . Just) + (SSL.readPublicKey (LC8.unpack (toByteString pem)) <&> Just) mkBotUserView :: User -> Public.BotUserView mkBotUserView u = diff --git a/services/brig/src/Brig/Run.hs b/services/brig/src/Brig/Run.hs index cd046c4aaaf..e3b43ad9e7a 100644 --- a/services/brig/src/Brig/Run.hs +++ b/services/brig/src/Brig/Run.hs @@ -40,7 +40,7 @@ import Brig.Options hiding (internalEvents, sesQueue) import qualified Brig.Queue as Queue import Brig.Types.Intra (AccountStatus (PendingInvitation)) import Brig.Version -import Cassandra (Page (Page), liftClient) +import Cassandra (Page (Page)) import qualified Control.Concurrent.Async as Async import Control.Exception.Safe (catchAny) import Control.Lens (view, (.~), (^.)) @@ -181,20 +181,19 @@ bodyParserErrorFormatter _ _ errMsg = Servant.errHeaders = [(HTTP.hContentType, HTTPMedia.renderHeader (Servant.contentType (Proxy @Servant.JSON)))] } -pendingActivationCleanup :: forall r. (AppIO r) () +pendingActivationCleanup :: forall r. AppIO r () pendingActivationCleanup = do safeForever "pendingActivationCleanup" $ do now <- liftIO =<< view currentTime forExpirationsPaged $ \exps -> do uids <- - ( for exps $ \(UserPendingActivation uid expiresAt) -> do - isPendingInvitation <- (Just PendingInvitation ==) <$> API.lookupStatus uid - pure $ - ( expiresAt < now, - isPendingInvitation, - uid - ) - ) + for exps $ \(UserPendingActivation uid expiresAt) -> do + isPendingInvitation <- (Just PendingInvitation ==) <$> wrapClient (API.lookupStatus uid) + pure + ( expiresAt < now, + isPendingInvitation, + uid + ) API.deleteUsersNoVerify $ catMaybes @@ -202,7 +201,7 @@ pendingActivationCleanup = do if isExpired && isPendingInvitation then Just uid else Nothing ) - usersPendingActivationRemoveMultiple $ + wrapClient . usersPendingActivationRemoveMultiple $ catMaybes ( uids <&> \(isExpired, _isPendingInvitation, uid) -> if isExpired then Just uid else Nothing @@ -220,13 +219,13 @@ pendingActivationCleanup = do forExpirationsPaged :: ([UserPendingActivation] -> (AppIO r) ()) -> (AppIO r) () forExpirationsPaged f = do - go =<< usersPendingActivationList + go =<< wrapClient usersPendingActivationList where - go :: (Page UserPendingActivation) -> (AppIO r) () + go :: Page UserPendingActivation -> (AppIO r) () go (Page hasMore result nextPage) = do f result when hasMore $ - go =<< liftClient nextPage + go =<< wrapClient (lift nextPage) threadDelayRandom :: (AppIO r) () threadDelayRandom = do diff --git a/services/brig/src/Brig/Team/API.hs b/services/brig/src/Brig/Team/API.hs index 5b18709bf0b..c2c17016130 100644 --- a/services/brig/src/Brig/Team/API.hs +++ b/services/brig/src/Brig/Team/API.hs @@ -26,7 +26,7 @@ import Brig.API.Handler import Brig.API.User (createUserInviteViaScim, fetchUserIdentity) import qualified Brig.API.User as API import Brig.API.Util (logEmail, logInvitationCode) -import Brig.App (currentTime, emailSender, settings) +import Brig.App (currentTime, emailSender, settings, wrapClient) import qualified Brig.Data.Blacklist as Blacklist import Brig.Data.UserKey import qualified Brig.Data.UserKey as Data @@ -227,10 +227,10 @@ getInvitationCodeH (_ ::: t ::: r) = do getInvitationCode :: TeamId -> InvitationId -> (Handler r) FoundInvitationCode getInvitationCode t r = do - code <- lift $ DB.lookupInvitationCode t r + code <- lift . wrapClient $ DB.lookupInvitationCode t r maybe (throwStd $ errorDescriptionTypeToWai @InvalidInvitationCode) (return . FoundInvitationCode) code -data FoundInvitationCode = FoundInvitationCode InvitationCode +newtype FoundInvitationCode = FoundInvitationCode InvitationCode deriving (Eq, Show, Generic) instance ToJSON FoundInvitationCode where @@ -323,26 +323,26 @@ createInvitation' tid inviteeRole mbInviterUid fromEmail body = do -- Validate e-mail inviteeEmail <- either (const $ throwStd (errorDescriptionTypeToWai @InvalidEmail)) return (Email.validateEmail (irInviteeEmail body)) let uke = userEmailKey inviteeEmail - blacklistedEm <- lift $ Blacklist.exists uke + blacklistedEm <- lift $ wrapClient $ Blacklist.exists uke when blacklistedEm $ throwStd blacklistedEmail - emailTaken <- lift $ isJust <$> Data.lookupKey uke + emailTaken <- lift $ isJust <$> wrapClient (Data.lookupKey uke) when emailTaken $ throwStd emailExists -- Validate phone inviteePhone <- for (irInviteePhone body) $ \p -> do - validatedPhone <- maybe (throwStd (errorDescriptionTypeToWai @InvalidPhone)) return =<< lift (Phone.validatePhone p) + validatedPhone <- maybe (throwStd (errorDescriptionTypeToWai @InvalidPhone)) return =<< lift (wrapClient $ Phone.validatePhone p) let ukp = userPhoneKey validatedPhone - blacklistedPh <- lift $ Blacklist.exists ukp + blacklistedPh <- lift $ wrapClient $ Blacklist.exists ukp when blacklistedPh $ throwStd (errorDescriptionTypeToWai @BlacklistedPhone) - phoneTaken <- lift $ isJust <$> Data.lookupKey ukp + phoneTaken <- lift $ isJust <$> wrapClient (Data.lookupKey ukp) when phoneTaken $ throwStd phoneExists return validatedPhone maxSize <- setMaxTeamSize <$> view settings - pending <- lift $ DB.countInvitations tid + pending <- lift $ wrapClient $ DB.countInvitations tid when (fromIntegral pending >= maxSize) $ throwStd tooManyTeamInvitations @@ -354,16 +354,17 @@ createInvitation' tid inviteeRole mbInviterUid fromEmail body = do now <- liftIO =<< view currentTime timeout <- setTeamInvitationTimeout <$> view settings (newInv, code) <- - DB.insertInvitation - iid - tid - inviteeRole - now - mbInviterUid - inviteeEmail - inviteeName - inviteePhone - timeout + wrapClient $ + DB.insertInvitation + iid + tid + inviteeRole + now + mbInviterUid + inviteeEmail + inviteeName + inviteePhone + timeout (newInv, code) <$ sendInvitationMail inviteeEmail tid fromEmail code locale deleteInvitationH :: JSON ::: UserId ::: TeamId ::: InvitationId -> (Handler r) Response @@ -373,7 +374,7 @@ deleteInvitationH (_ ::: uid ::: tid ::: iid) = do deleteInvitation :: UserId -> TeamId -> InvitationId -> (Handler r) () deleteInvitation uid tid iid = do ensurePermissions uid tid [Team.AddTeamMember] - DB.deleteInvitation tid iid + lift $ wrapClient $ DB.deleteInvitation tid iid listInvitationsH :: JSON ::: UserId ::: TeamId ::: Maybe InvitationId ::: Range 1 500 Int32 -> (Handler r) Response listInvitationsH (_ ::: uid ::: tid ::: start ::: size) = do @@ -382,7 +383,7 @@ listInvitationsH (_ ::: uid ::: tid ::: start ::: size) = do listInvitations :: UserId -> TeamId -> Maybe InvitationId -> Range 1 500 Int32 -> (Handler r) Public.InvitationList listInvitations uid tid start size = do ensurePermissions uid tid [Team.AddTeamMember] - rs <- lift $ DB.lookupInvitations tid start size + rs <- lift $ wrapClient $ DB.lookupInvitations tid start size return $! Public.InvitationList (DB.resultList rs) (DB.resultHasMore rs) getInvitationH :: JSON ::: UserId ::: TeamId ::: InvitationId -> (Handler r) Response @@ -395,7 +396,7 @@ getInvitationH (_ ::: uid ::: tid ::: iid) = do getInvitation :: UserId -> TeamId -> InvitationId -> (Handler r) (Maybe Public.Invitation) getInvitation uid tid iid = do ensurePermissions uid tid [Team.AddTeamMember] - lift $ DB.lookupInvitation tid iid + lift $ wrapClient $ DB.lookupInvitation tid iid getInvitationByCodeH :: JSON ::: Public.InvitationCode -> (Handler r) Response getInvitationByCodeH (_ ::: c) = do @@ -403,12 +404,12 @@ getInvitationByCodeH (_ ::: c) = do getInvitationByCode :: Public.InvitationCode -> (Handler r) Public.Invitation getInvitationByCode c = do - inv <- lift $ DB.lookupInvitationByCode c + inv <- lift . wrapClient $ DB.lookupInvitationByCode c maybe (throwStd $ errorDescriptionTypeToWai @InvalidInvitationCode) return inv headInvitationByEmailH :: JSON ::: Email -> (Handler r) Response headInvitationByEmailH (_ ::: e) = do - inv <- lift $ DB.lookupInvitationInfoByEmail e + inv <- lift $ wrapClient $ DB.lookupInvitationInfoByEmail e return $ case inv of DB.InvitationByEmail _ -> setStatus status200 empty DB.InvitationByEmailNotFound -> setStatus status404 empty @@ -423,7 +424,7 @@ getInvitationByEmailH (_ ::: email) = getInvitationByEmail :: Email -> (Handler r) Public.Invitation getInvitationByEmail email = do - inv <- lift $ DB.lookupInvitationByEmail email + inv <- lift $ wrapClient $ DB.lookupInvitationByEmail email maybe (throwStd (notFound "Invitation not found")) return inv suspendTeamH :: JSON ::: TeamId -> (Handler r) Response @@ -433,7 +434,7 @@ suspendTeamH (_ ::: tid) = do suspendTeam :: TeamId -> (Handler r) () suspendTeam tid = do changeTeamAccountStatuses tid Suspended - lift $ DB.deleteInvitations tid + lift $ wrapClient $ DB.deleteInvitations tid lift $ Intra.changeTeamStatus tid Team.Suspended Nothing unsuspendTeamH :: JSON ::: TeamId -> (Handler r) Response @@ -450,7 +451,7 @@ unsuspendTeam tid = do changeTeamAccountStatuses :: TeamId -> AccountStatus -> (Handler r) () changeTeamAccountStatuses tid s = do - team <- Team.tdTeam <$> (lift $ Intra.getTeam tid) + team <- Team.tdTeam <$> lift (Intra.getTeam tid) unless (team ^. Team.teamBinding == Team.Binding) $ throwStd noBindingTeam uids <- toList1 =<< lift (fmap (view Team.userId) . view Team.teamMembers <$> Intra.getTeamMembers tid) diff --git a/services/brig/src/Brig/User/API/Auth.hs b/services/brig/src/Brig/User/API/Auth.hs index 15f227e010e..4dfdd203bda 100644 --- a/services/brig/src/Brig/User/API/Auth.hs +++ b/services/brig/src/Brig/User/API/Auth.hs @@ -25,13 +25,16 @@ import Brig.API.Error import Brig.API.Handler import Brig.API.Types import qualified Brig.API.User as User -import Brig.App (AppIO) +import Brig.App import Brig.Phone -import Brig.Types.Intra (ReAuthUser, reAuthPassword) +import Brig.Types.Intra (ReAuthUser, reAuthCode, reAuthCodeAction, reAuthPassword) import Brig.Types.User.Auth import qualified Brig.User.Auth as Auth import qualified Brig.User.Auth.Cookie as Auth import qualified Brig.ZAuth as ZAuth +import Control.Error (catchE) +import Control.Monad.Except +import Control.Monad.Trans.Except (throwE) import qualified Data.ByteString as BS import Data.ByteString.Conversion import Data.Either.Combinators (leftToMaybe, rightToMaybe) @@ -119,7 +122,8 @@ routesPublic = do Doc.errorResponse (errorDescriptionTypeToWai @BadCredentials) Doc.errorResponse accountSuspended Doc.errorResponse accountPending - Doc.errorResponse loginsTooFrequent + Doc.errorResponse loginCodeAuthenticationFailed + Doc.errorResponse loginCodeAuthenticationRequired post "/access/logout" (continue logoutH) $ accept "application" "json" .&. tokenRequest @@ -221,7 +225,7 @@ getLoginCodeH (_ ::: phone) = json <$> getLoginCode phone getLoginCode :: Phone -> (Handler r) Public.PendingLoginCode getLoginCode phone = do - code <- lift $ Auth.lookupLoginCode phone + code <- lift $ wrapClient $ Auth.lookupLoginCode phone maybe (throwStd loginCodeNotFound) return code reAuthUserH :: UserId ::: JsonRequest ReAuthUser -> (Handler r) Response @@ -231,7 +235,15 @@ reAuthUserH (uid ::: req) = do reAuthUser :: UserId -> ReAuthUser -> (Handler r) () reAuthUser uid body = do - User.reauthenticate uid (reAuthPassword body) !>> reauthError + wrapClientE (User.reauthenticate uid (reAuthPassword body)) !>> reauthError + case reAuthCodeAction body of + Just action -> + Auth.verifyCode (reAuthCode body) action uid + `catchE` \case + VerificationCodeRequired -> throwE $ reauthError ReAuthCodeVerificationRequired + VerificationCodeNoPendingCode -> throwE $ reauthError ReAuthCodeVerificationNoPendingCode + VerificationCodeNoEmail -> throwE $ reauthError ReAuthCodeVerificationNoEmail + Nothing -> pure () loginH :: JsonRequest Public.Login ::: Bool ::: JSON -> (Handler r) Response loginH (req ::: persist ::: _) = do @@ -305,21 +317,21 @@ changeSelfEmailH (_ ::: req ::: ckies ::: toks) = do Just (Right _legalholdAccessToken) -> throwStd invalidAccessToken Just (Left userTokens) -> - fst <$> (Auth.validateTokens userCookies (Just userTokens) !>> zauthError) + fst <$> Auth.validateTokens userCookies (Just userTokens) !>> zauthError listCookiesH :: UserId ::: Maybe (List Public.CookieLabel) ::: JSON -> (Handler r) Response listCookiesH (u ::: ll ::: _) = json <$> lift (listCookies u ll) listCookies :: UserId -> Maybe (List Public.CookieLabel) -> (AppIO r) Public.CookieList listCookies u ll = do - Public.CookieList <$> Auth.listCookies u (maybe [] fromList ll) + Public.CookieList <$> wrapClient (Auth.listCookies u (maybe [] fromList ll)) rmCookiesH :: UserId ::: JsonRequest Public.RemoveCookies -> (Handler r) Response rmCookiesH (uid ::: req) = do empty <$ (rmCookies uid =<< parseJsonBody req) rmCookies :: UserId -> Public.RemoveCookies -> (Handler r) () -rmCookies uid (Public.RemoveCookies pw lls ids) = do +rmCookies uid (Public.RemoveCookies pw lls ids) = Auth.revokeAccess uid pw ids lls !>> authError renewH :: JSON ::: Maybe (Either (List1 ZAuth.UserToken) (List1 ZAuth.LegalHoldUserToken)) ::: Maybe (Either ZAuth.AccessToken ZAuth.LegalHoldAccessToken) -> (Handler r) Response @@ -424,7 +436,7 @@ cookies k r = [] -> Fail . addLabel "cookie" $ notAvailable k cc -> case mapMaybe fromByteString cc of - [] -> (Fail . addLabel "cookie" . typeError k $ "Failed to get zuid cookies") + [] -> Fail . addLabel "cookie" . typeError k $ "Failed to get zuid cookies" (x : xs) -> return $ List1.list1 x xs notAvailable :: ByteString -> P.Error diff --git a/services/brig/src/Brig/User/API/Handle.hs b/services/brig/src/Brig/User/API/Handle.hs index 82dc56f4c33..12d82b99ab2 100644 --- a/services/brig/src/Brig/User/API/Handle.hs +++ b/services/brig/src/Brig/User/API/Handle.hs @@ -61,7 +61,7 @@ getRemoteHandleInfo handle = do getLocalHandleInfo :: Local UserId -> Handle -> (Handler r) (Maybe Public.UserProfile) getLocalHandleInfo self handle = do Log.info $ Log.msg $ Log.val "getHandleInfo - local lookup" - maybeOwnerId <- lift $ API.lookupHandle handle + maybeOwnerId <- lift . wrapClient $ API.lookupHandle handle case maybeOwnerId of Nothing -> return Nothing Just ownerId -> do @@ -76,7 +76,7 @@ filterHandleResults searchingUser us = do sameTeamSearchOnly <- fromMaybe False <$> view (settings . searchSameTeamOnly) if sameTeamSearchOnly then do - fromTeam <- lift $ Data.lookupUserTeam (tUnqualified searchingUser) + fromTeam <- lift . wrapClient $ Data.lookupUserTeam (tUnqualified searchingUser) return $ case fromTeam of Just team -> filter (\x -> Public.profileTeam x == Just team) us Nothing -> us diff --git a/services/brig/src/Brig/User/API/Search.hs b/services/brig/src/Brig/User/API/Search.hs index 4f01f330997..4c8d3b1daca 100644 --- a/services/brig/src/Brig/User/API/Search.hs +++ b/services/brig/src/Brig/User/API/Search.hs @@ -101,14 +101,14 @@ routesInternal = do -- make index updates visible (e.g. for integration testing) post "/i/index/refresh" - (continue (const $ lift refreshIndex *> pure empty)) + (continue (const $ lift refreshIndex $> empty)) true -- reindex from Cassandra (e.g. integration testing -- prefer the -- `brig-index` executable for actual operations!) post "/i/index/reindex" - (continue . const $ lift reindexAll *> pure empty) + (continue . const $ lift (wrapClient reindexAll) $> empty) true -- forcefully reindex from Cassandra, even if nothing has changed @@ -116,7 +116,7 @@ routesInternal = do -- for actual operations!) post "/i/index/reindex-if-same-or-newer" - (continue . const $ lift reindexAllIfSameOrNewer *> pure empty) + (continue . const $ lift (wrapClient reindexAllIfSameOrNewer) $> empty) true -- Handlers @@ -178,7 +178,7 @@ searchLocally searcherId searchTerm maybeMaxResults = do mkTeamSearchInfo :: (Handler r) TeamSearchInfo mkTeamSearchInfo = lift $ do - searcherTeamId <- DB.lookupUserTeam searcherId + searcherTeamId <- wrapClient $ DB.lookupUserTeam searcherId sameTeamSearchOnly <- fromMaybe False <$> view (settings . Opts.searchSameTeamOnly) case searcherTeamId of Nothing -> return Search.NoTeam diff --git a/services/brig/src/Brig/User/Auth.hs b/services/brig/src/Brig/User/Auth.hs index f0977ce66e4..449885a97aa 100644 --- a/services/brig/src/Brig/User/Auth.hs +++ b/services/brig/src/Brig/User/Auth.hs @@ -24,6 +24,7 @@ module Brig.User.Auth renewAccess, validateTokens, revokeAccess, + verifyCode, -- * Internal lookupLoginCode, @@ -39,6 +40,7 @@ import Brig.API.Types import Brig.API.User (suspendAccount) import Brig.App import Brig.Budget +import qualified Brig.Code as Code import qualified Brig.Data.Activation as Data import qualified Brig.Data.LoginCode as Data import qualified Brig.Data.User as Data @@ -56,6 +58,7 @@ import Brig.User.Auth.Cookie import Brig.User.Handle import Brig.User.Phone import qualified Brig.ZAuth as ZAuth +import Cassandra import Control.Error hiding (bool) import Control.Lens (to, view) import Data.ByteString.Conversion (toByteString) @@ -71,38 +74,43 @@ import Network.Wai.Utilities.Error ((!>>)) import System.Logger (field, msg, val, (~~)) import qualified System.Logger.Class as Log import Wire.API.Team.Feature (TeamFeatureStatusNoConfig (..), TeamFeatureStatusValue (..)) -import Wire.API.User (TeamFeatureSndFPasswordChallengeNotImplemented (..)) +import qualified Wire.API.Team.Feature as Public +import Wire.API.User (VerificationAction (..)) data Access u = Access { accessToken :: !AccessToken, accessCookie :: !(Maybe (Cookie (ZAuth.Token u))) } -sendLoginCode :: Phone -> Bool -> Bool -> ExceptT SendLoginCodeError (AppIO r) PendingLoginCode +sendLoginCode :: + Phone -> + Bool -> + Bool -> + ExceptT SendLoginCodeError (AppIO r) PendingLoginCode sendLoginCode phone call force = do pk <- maybe (throwE $ SendLoginInvalidPhone phone) (return . userPhoneKey) - =<< lift (validatePhone phone) - user <- lift $ Data.lookupKey pk + =<< lift (wrapClient $ validatePhone phone) + user <- lift . wrapClient $ Data.lookupKey pk case user of Nothing -> throwE $ SendLoginInvalidPhone phone Just u -> do Log.debug $ field "user" (toByteString u) . field "action" (Log.val "User.sendLoginCode") - pw <- lift $ Data.lookupPassword u + pw <- lift . wrapClient $ Data.lookupPassword u unless (isNothing pw || force) $ throwE SendLoginPasswordExists lift $ do - l <- Data.lookupLocale u - c <- Data.createLoginCode u + l <- wrapClient $ Data.lookupLocale u + c <- wrapClient $ Data.createLoginCode u void . forPhoneKey pk $ \ph -> if call - then sendLoginCall ph (pendingLoginCode c) l - else sendLoginSms ph (pendingLoginCode c) l + then wrapClient $ sendLoginCall ph (pendingLoginCode c) l + else wrapClient $ sendLoginSms ph (pendingLoginCode c) l return c -lookupLoginCode :: Phone -> (AppIO r) (Maybe PendingLoginCode) +lookupLoginCode :: (MonadClient m, Log.MonadLogger m, MonadReader Env m) => Phone -> m (Maybe PendingLoginCode) lookupLoginCode phone = Data.lookupKey (userPhoneKey phone) >>= \case Nothing -> return Nothing @@ -110,44 +118,76 @@ lookupLoginCode phone = Log.debug $ field "user" (toByteString u) . field "action" (Log.val "User.lookupLoginCode") Data.lookupLoginCode u -login :: Login -> CookieType -> ExceptT LoginError (AppIO r) (Access ZAuth.User) -login (PasswordLogin li pw label _) typ = do - case TeamFeatureSndFPasswordChallengeNotImplemented of - -- mark this place to implement handling verification codes later - -- (for now just ignore them unconditionally.) - _ -> pure () - uid <- resolveLoginId li +login :: + Login -> + CookieType -> + ExceptT LoginError (AppIO r) (Access ZAuth.User) +login (PasswordLogin li pw label code) typ = do + uid <- wrapClientE $ resolveLoginId li Log.debug $ field "user" (toByteString uid) . field "action" (Log.val "User.login") - checkRetryLimit uid - Data.authenticate uid pw `catchE` \case - AuthInvalidUser -> loginFailed uid - AuthInvalidCredentials -> loginFailed uid + wrapClientE $ checkRetryLimit uid + wrapClientE (Data.authenticate uid pw) `catchE` \case + AuthInvalidUser -> wrapClientE $ loginFailed uid + AuthInvalidCredentials -> wrapClientE $ loginFailed uid AuthSuspended -> throwE LoginSuspended AuthEphemeral -> throwE LoginEphemeral AuthPendingInvitation -> throwE LoginPendingActivation + verifyLoginCode code uid newAccess @ZAuth.User @ZAuth.Access uid typ label + where + verifyLoginCode :: Maybe Code.Value -> UserId -> ExceptT LoginError (AppIO r) () + verifyLoginCode mbCode uid = do + verifyCode mbCode Login uid + `catchE` \case + VerificationCodeNoPendingCode -> wrapClientE $ loginFailedWith LoginCodeInvalid uid + VerificationCodeRequired -> wrapClientE $ loginFailedWith LoginCodeRequired uid + VerificationCodeNoEmail -> wrapClientE $ loginFailed uid login (SmsLogin phone code label) typ = do - uid <- resolveLoginId (LoginByPhone phone) + uid <- wrapClientE $ resolveLoginId (LoginByPhone phone) Log.debug $ field "user" (toByteString uid) . field "action" (Log.val "User.login") - checkRetryLimit uid - ok <- lift $ Data.verifyLoginCode uid code + wrapClientE $ checkRetryLimit uid + ok <- lift . wrapClient $ Data.verifyLoginCode uid code unless ok $ - loginFailed uid + wrapClientE $ loginFailed uid newAccess @ZAuth.User @ZAuth.Access uid typ label -loginFailed :: UserId -> ExceptT LoginError (AppIO r) () -loginFailed uid = decrRetryLimit uid >> throwE LoginFailed +verifyCode :: Maybe Code.Value -> VerificationAction -> UserId -> ExceptT VerificationCodeError (AppIO r) () +verifyCode mbCode action uid = do + (mbEmail, mbTeamId) <- getEmailAndTeamId uid + featureEnabled <- lift $ do + mbFeatureEnabled <- Intra.getVerificationCodeEnabled `traverse` mbTeamId + pure $ fromMaybe (Public.tfwoapsStatus Public.defaultTeamFeatureSndFactorPasswordChallengeStatus == Public.TeamFeatureEnabled) mbFeatureEnabled + when featureEnabled $ do + case (mbCode, mbEmail) of + (Just code, Just email) -> do + key <- Code.mkKey $ Code.ForEmail email + codeValid <- wrapClientE $ isJust <$> Code.verify key (Code.scopeFromAction action) code + unless codeValid $ throwE VerificationCodeNoPendingCode + (Nothing, _) -> throwE VerificationCodeRequired + (_, Nothing) -> throwE VerificationCodeNoEmail + where + getEmailAndTeamId :: UserId -> ExceptT e (AppIO r) (Maybe Email, Maybe TeamId) + getEmailAndTeamId u = do + mbAccount <- wrapClientE $ Data.lookupAccount u + pure (userEmail <$> accountUser =<< mbAccount, userTeam <$> accountUser =<< mbAccount) + +loginFailedWith :: (MonadClient m, MonadReader Env m) => LoginError -> UserId -> ExceptT LoginError m () +loginFailedWith e uid = decrRetryLimit uid >> throwE e -decrRetryLimit :: UserId -> ExceptT LoginError (AppIO r) () +loginFailed :: (MonadClient m, MonadReader Env m) => UserId -> ExceptT LoginError m () +loginFailed = loginFailedWith LoginFailed + +decrRetryLimit :: (MonadClient m, MonadReader Env m) => UserId -> ExceptT LoginError m () decrRetryLimit = withRetryLimit (\k b -> withBudget k b $ pure ()) -checkRetryLimit :: UserId -> ExceptT LoginError (AppIO r) () +checkRetryLimit :: (MonadClient m, MonadReader Env m) => UserId -> ExceptT LoginError m () checkRetryLimit = withRetryLimit checkBudget withRetryLimit :: - (BudgetKey -> Budget -> ExceptT LoginError (AppIO r) (Budgeted ())) -> + (MonadClient m, MonadReader Env m) => + (BudgetKey -> Budget -> ExceptT LoginError m (Budgeted ())) -> UserId -> - ExceptT LoginError (AppIO r) () + ExceptT LoginError m () withRetryLimit action uid = do mLimitFailedLogins <- view (settings . to Opt.setLimitFailedLogins) forM_ mLimitFailedLogins $ \opts -> do @@ -164,7 +204,7 @@ withRetryLimit action uid = do logout :: ZAuth.TokenPair u a => List1 (ZAuth.Token u) -> ZAuth.Token a -> ExceptT ZAuth.Failure (AppIO r) () logout uts at = do (u, ck) <- validateTokens uts (Just at) - lift $ revokeCookies u [cookieId ck] [] + lift . wrapClient $ revokeCookies u [cookieId ck] [] renewAccess :: ZAuth.TokenPair u a => @@ -186,16 +226,16 @@ revokeAccess :: [CookieLabel] -> ExceptT AuthError (AppIO r) () revokeAccess u pw cc ll = do - Log.debug $ field "user" (toByteString u) . field "action" (Log.val "User.revokeAccess") - Data.authenticate u pw - lift $ revokeCookies u cc ll + lift $ Log.debug $ field "user" (toByteString u) . field "action" (Log.val "User.revokeAccess") + wrapClientE $ Data.authenticate u pw + lift . wrapClient $ revokeCookies u cc ll -------------------------------------------------------------------------------- -- Internal catchSuspendInactiveUser :: UserId -> e -> ExceptT e (AppIO r) () catchSuspendInactiveUser uid errval = do - mustsuspend <- lift $ mustSuspendInactiveUser uid + mustsuspend <- lift . wrapClient $ mustSuspendInactiveUser uid when mustsuspend $ do Log.warn $ msg (val "Suspending user due to inactivity") @@ -204,7 +244,13 @@ catchSuspendInactiveUser uid errval = do lift $ suspendAccount (List1.singleton uid) throwE errval -newAccess :: forall u a r. ZAuth.TokenPair u a => UserId -> CookieType -> Maybe CookieLabel -> ExceptT LoginError (AppIO r) (Access u) +newAccess :: + forall u a r. + ZAuth.TokenPair u a => + UserId -> + CookieType -> + Maybe CookieLabel -> + ExceptT LoginError (AppIO r) (Access u) newAccess uid ct cl = do catchSuspendInactiveUser uid LoginSuspended r <- lift $ newCookieLimited uid ct cl @@ -214,7 +260,7 @@ newAccess uid ct cl = do t <- lift $ newAccessToken @u @a ck Nothing return $ Access t (Just ck) -resolveLoginId :: LoginId -> ExceptT LoginError (AppIO r) UserId +resolveLoginId :: (MonadClient m, MonadReader Env m) => LoginId -> ExceptT LoginError m UserId resolveLoginId li = do usr <- validateLoginId li >>= lift . either lookupKey lookupHandle case usr of @@ -226,7 +272,7 @@ resolveLoginId li = do else LoginFailed Just uid -> return uid -validateLoginId :: LoginId -> ExceptT LoginError (AppIO r) (Either UserKey Handle) +validateLoginId :: (MonadClient m, MonadReader Env m) => LoginId -> ExceptT LoginError m (Either UserKey Handle) validateLoginId (LoginByEmail email) = either (const $ throwE LoginFailed) @@ -240,7 +286,7 @@ validateLoginId (LoginByPhone phone) = validateLoginId (LoginByHandle h) = return (Right h) -isPendingActivation :: LoginId -> (AppIO r) Bool +isPendingActivation :: (MonadClient m, MonadReader Env m) => LoginId -> m Bool isPendingActivation ident = case ident of (LoginByHandle _) -> return False (LoginByEmail e) -> checkKey (userEmailKey e) @@ -280,10 +326,13 @@ validateTokens uts at = do getFirstSuccessOrFirstFail tokens where -- FUTUREWORK: There is surely a better way to do this - getFirstSuccessOrFirstFail :: List1 (Either ZAuth.Failure (UserId, Cookie (ZAuth.Token u))) -> ExceptT ZAuth.Failure (AppIO r) (UserId, Cookie (ZAuth.Token u)) + getFirstSuccessOrFirstFail :: + Monad m => + List1 (Either ZAuth.Failure (UserId, Cookie (ZAuth.Token u))) -> + ExceptT ZAuth.Failure m (UserId, Cookie (ZAuth.Token u)) getFirstSuccessOrFirstFail tks = case (lefts $ NE.toList $ List1.toNonEmpty tks, rights $ NE.toList $ List1.toNonEmpty tks) of - (_, (suc : _)) -> return suc - ((e : _), _) -> throwE e + (_, suc : _) -> return suc + (e : _, _) -> throwE e _ -> throwE ZAuth.Invalid -- Impossible validateToken :: @@ -299,14 +348,17 @@ validateToken ut at = do ExceptT (ZAuth.validateToken token) `catchE` \e -> unless (e == ZAuth.Expired) (throwE e) - ck <- lift (lookupCookie ut) >>= maybe (throwE ZAuth.Invalid) return + ck <- lift (wrapClient $ lookupCookie ut) >>= maybe (throwE ZAuth.Invalid) return return (ZAuth.userTokenOf ut, ck) -- | Allow to login as any user without having the credentials. ssoLogin :: SsoLogin -> CookieType -> ExceptT LoginError (AppIO r) (Access ZAuth.User) ssoLogin (SsoLogin uid label) typ = do - Data.reauthenticate uid Nothing `catchE` \case + wrapClientE (Data.reauthenticate uid Nothing) `catchE` \case ReAuthMissingPassword -> pure () + ReAuthCodeVerificationRequired -> pure () + ReAuthCodeVerificationNoPendingCode -> pure () + ReAuthCodeVerificationNoEmail -> pure () ReAuthError e -> case e of AuthInvalidUser -> throwE LoginFailed AuthInvalidCredentials -> pure () @@ -318,7 +370,7 @@ ssoLogin (SsoLogin uid label) typ = do -- | Log in as a LegalHold service, getting LegalHoldUser/Access Tokens. legalHoldLogin :: LegalHoldLogin -> CookieType -> ExceptT LegalHoldLoginError (AppIO r) (Access ZAuth.LegalHoldUser) legalHoldLogin (LegalHoldLogin uid plainTextPassword label) typ = do - Data.reauthenticate uid plainTextPassword !>> LegalHoldReAuthError + wrapClientE (Data.reauthenticate uid plainTextPassword) !>> LegalHoldReAuthError -- legalhold login is only possible if -- the user is a team user -- and the team has legalhold enabled diff --git a/services/brig/src/Brig/User/Auth/Cookie.hs b/services/brig/src/Brig/User/Auth/Cookie.hs index 2cd821f9af2..f3721ca02b3 100644 --- a/services/brig/src/Brig/User/Auth/Cookie.hs +++ b/services/brig/src/Brig/User/Auth/Cookie.hs @@ -47,6 +47,7 @@ import Brig.Types.User.Auth hiding (user) import Brig.User.Auth.Cookie.Limit import qualified Brig.User.Auth.DB.Cookie as DB import qualified Brig.ZAuth as ZAuth +import Cassandra import Control.Lens (to, view) import Data.ByteString.Conversion import Data.Id @@ -69,7 +70,7 @@ newCookie :: UserId -> CookieType -> Maybe CookieLabel -> - (AppIO r) (Cookie (ZAuth.Token u)) + AppIO r (Cookie (ZAuth.Token u)) newCookie uid typ label = do now <- liftIO =<< view currentTime tok <- @@ -86,12 +87,15 @@ newCookie uid typ label = do cookieSucc = Nothing, cookieValue = tok } - DB.insertCookie uid c Nothing + wrapClient $ DB.insertCookie uid c Nothing return c -- | Renew the given cookie with a fresh token, if its age -- exceeds the configured minimum threshold. -nextCookie :: ZAuth.UserTokenLike u => Cookie (ZAuth.Token u) -> (AppIO r) (Maybe (Cookie (ZAuth.Token u))) +nextCookie :: + ZAuth.UserTokenLike u => + Cookie (ZAuth.Token u) -> + AppIO r (Maybe (Cookie (ZAuth.Token u))) nextCookie c = do s <- view settings now <- liftIO =<< view currentTime @@ -109,7 +113,7 @@ nextCookie c = do Just ck -> do let uid = ZAuth.userTokenOf (cookieValue c) trackSuperseded uid (cookieId c) - cs <- DB.listCookies uid + cs <- wrapClient . DB.listCookies $ uid case List.find (\x -> cookieId x == ck && persist x) cs of Nothing -> renewCookie c Just c' -> do @@ -117,7 +121,10 @@ nextCookie c = do return c' {cookieValue = t} -- | Renew the given cookie with a fresh token. -renewCookie :: ZAuth.UserTokenLike u => Cookie (ZAuth.Token u) -> (AppIO r) (Cookie (ZAuth.Token u)) +renewCookie :: + ZAuth.UserTokenLike u => + Cookie (ZAuth.Token u) -> + AppIO r (Cookie (ZAuth.Token u)) renewCookie old = do let t = cookieValue old let uid = ZAuth.userTokenOf t @@ -128,14 +135,14 @@ renewCookie old = do -- an ever growing chain of superseded cookies. let old' = old {cookieSucc = Just (cookieId new)} ttl <- setUserCookieRenewAge <$> view settings - DB.insertCookie uid old' (Just (DB.TTL (fromIntegral ttl))) + wrapClient $ DB.insertCookie uid old' (Just (DB.TTL (fromIntegral ttl))) return new -- | Whether a user has not renewed any of her cookies for longer than -- 'suspendCookiesOlderThanSecs'. Call this always before 'newCookie', 'nextCookie', -- 'newCookieLimited' if there is a chance that the user should be suspended (we don't do it -- implicitly because of cyclical dependencies). -mustSuspendInactiveUser :: UserId -> (AppIO r) Bool +mustSuspendInactiveUser :: (MonadReader Env m, MonadIO m, MonadClient m) => UserId -> m Bool mustSuspendInactiveUser uid = view (settings . to setSuspendInactiveUsers) >>= \case Nothing -> pure False @@ -152,7 +159,12 @@ mustSuspendInactiveUser uid = | otherwise = True pure mustSuspend -newAccessToken :: forall u a r. ZAuth.TokenPair u a => Cookie (ZAuth.Token u) -> Maybe (ZAuth.Token a) -> (AppIO r) AccessToken +newAccessToken :: + forall u a m. + (ZAuth.TokenPair u a, MonadReader Env m, ZAuth.MonadZAuth m) => + Cookie (ZAuth.Token u) -> + Maybe (ZAuth.Token a) -> + m AccessToken newAccessToken c mt = do t' <- case mt of Nothing -> ZAuth.newAccessToken (cookieValue c) @@ -167,7 +179,7 @@ newAccessToken c mt = do -- | Lookup the stored cookie associated with a user token, -- if one exists. -lookupCookie :: ZAuth.UserTokenLike u => ZAuth.Token u -> (AppIO r) (Maybe (Cookie (ZAuth.Token u))) +lookupCookie :: (ZAuth.UserTokenLike u, MonadClient m) => ZAuth.Token u -> m (Maybe (Cookie (ZAuth.Token u))) lookupCookie t = do let user = ZAuth.userTokenOf t let rand = ZAuth.userTokenRand t @@ -176,16 +188,16 @@ lookupCookie t = do where setToken c = c {cookieValue = t} -listCookies :: UserId -> [CookieLabel] -> (AppIO r) [Cookie ()] +listCookies :: MonadClient m => UserId -> [CookieLabel] -> m [Cookie ()] listCookies u [] = DB.listCookies u listCookies u ll = filter byLabel <$> DB.listCookies u where byLabel c = maybe False (`elem` ll) (cookieLabel c) -revokeAllCookies :: UserId -> (AppIO r) () +revokeAllCookies :: MonadClient m => UserId -> m () revokeAllCookies u = revokeCookies u [] [] -revokeCookies :: UserId -> [CookieId] -> [CookieLabel] -> (AppIO r) () +revokeCookies :: MonadClient m => UserId -> [CookieId] -> [CookieLabel] -> m () revokeCookies u [] [] = DB.deleteAllCookies u revokeCookies u ids labels = do cc <- filter matching <$> DB.listCookies u @@ -203,9 +215,9 @@ newCookieLimited :: UserId -> CookieType -> Maybe CookieLabel -> - (AppIO r) (Either RetryAfter (Cookie (ZAuth.Token t))) + AppIO r (Either RetryAfter (Cookie (ZAuth.Token t))) newCookieLimited u typ label = do - cs <- filter ((typ ==) . cookieType) <$> DB.listCookies u + cs <- filter ((typ ==) . cookieType) <$> wrapClient (DB.listCookies u) now <- liftIO =<< view currentTime lim <- CookieLimit . setUserCookieLimit <$> view settings thr <- setUserCookieThrottle <$> view settings @@ -215,7 +227,7 @@ newCookieLimited u typ label = do else case throttleCookies now thr cs of Just wait -> return (Left wait) Nothing -> do - revokeCookies u evict [] + wrapClient $ revokeCookies u evict [] Right <$> newCookie u typ label -------------------------------------------------------------------------------- @@ -249,7 +261,7 @@ toWebCookie c = do -------------------------------------------------------------------------------- -- Tracking -trackSuperseded :: UserId -> CookieId -> (AppIO r) () +trackSuperseded :: (MonadReader Env m, MonadIO m, Log.MonadLogger m) => UserId -> CookieId -> m () trackSuperseded u c = do m <- view metrics Metrics.counterIncr (Metrics.path "user.auth.cookie.superseded") m diff --git a/services/brig/src/Brig/User/EJPD.hs b/services/brig/src/Brig/User/EJPD.hs index b1c4d49c730..e8a4938417b 100644 --- a/services/brig/src/Brig/User/EJPD.hs +++ b/services/brig/src/Brig/User/EJPD.hs @@ -22,7 +22,7 @@ module Brig.User.EJPD (ejpdRequest) where import Brig.API.Handler import Brig.API.User (lookupHandle) -import Brig.App (AppIO) +import Brig.App (AppIO, wrapClient) import qualified Brig.Data.Connection as Conn import Brig.Data.User (lookupUser) import qualified Brig.IO.Intra as Intra @@ -47,8 +47,8 @@ ejpdRequest includeContacts (EJPDRequestBody handles) = do -- find uid given handle go1 :: Bool -> Handle -> (AppIO r) (Maybe EJPDResponseItem) go1 includeContacts' handle = do - mbUid <- lookupHandle handle - mbUsr <- maybe (pure Nothing) (lookupUser NoPendingInvitations) mbUid + mbUid <- wrapClient $ lookupHandle handle + mbUsr <- maybe (pure Nothing) (wrapClient . lookupUser NoPendingInvitations) mbUid maybe (pure Nothing) (fmap Just . go2 includeContacts') mbUsr -- construct response item given uid @@ -63,12 +63,12 @@ ejpdRequest includeContacts (EJPDRequestBody handles) = do if includeContacts' then do contacts :: [(UserId, RelationWithHistory)] <- - Conn.lookupContactListWithRelation uid + wrapClient $ Conn.lookupContactListWithRelation uid contactsFull :: [Maybe (Relation, EJPDResponseItem)] <- forM contacts $ \(uid', relationDropHistory -> rel) -> do - mbUsr <- lookupUser NoPendingInvitations uid' - maybe (pure Nothing) (\usr -> Just . (rel,) <$> go2 False usr) mbUsr + mbUsr <- wrapClient $ lookupUser NoPendingInvitations uid' + maybe (pure Nothing) (fmap (Just . (rel,)) . go2 False) mbUsr pure . Just . Set.fromList . catMaybes $ contactsFull else do @@ -82,7 +82,7 @@ ejpdRequest includeContacts (EJPDRequestBody handles) = do contactsFull :: [Maybe EJPDResponseItem] <- forM members $ \uid' -> do - mbUsr <- lookupUser NoPendingInvitations uid' + mbUsr <- wrapClient $ lookupUser NoPendingInvitations uid' maybe (pure Nothing) (fmap Just . go2 False) mbUsr pure . Just . (,Team.toNewListType (memberList ^. Team.teamMemberListType)) . Set.fromList . catMaybes $ contactsFull diff --git a/services/brig/src/Brig/User/Email.hs b/services/brig/src/Brig/User/Email.hs index b1c3e11c6a0..3f2cc39dbe6 100644 --- a/services/brig/src/Brig/User/Email.hs +++ b/services/brig/src/Brig/User/Email.hs @@ -24,6 +24,9 @@ module Brig.User.Email sendPasswordResetMail, sendDeletionEmail, sendNewClientEmail, + sendLoginVerificationMail, + sendCreateScimTokenVerificationMail, + sendTeamDeletionVerificationMail, -- * Re-exports validateEmail, @@ -45,14 +48,69 @@ import qualified Data.Text.Ascii as Ascii import Data.Text.Lazy (toStrict) import Imports -sendVerificationMail :: Email -> ActivationPair -> Maybe Locale -> (AppIO r) () +sendVerificationMail :: + ( MonadIO m, + MonadReader Env m + ) => + Email -> + ActivationPair -> + Maybe Locale -> + m () sendVerificationMail to pair loc = do tpl <- verificationEmail . snd <$> userTemplates loc branding <- view templateBranding let mail = VerificationEmail to pair Email.sendMail $ renderVerificationMail mail tpl branding -sendActivationMail :: Email -> Name -> ActivationPair -> Maybe Locale -> Maybe UserIdentity -> (AppIO r) () +sendLoginVerificationMail :: + ( MonadReader Env m, + MonadIO m + ) => + Email -> + Code.Value -> + Maybe Locale -> + m () +sendLoginVerificationMail email code mbLocale = do + tpl <- verificationLoginEmail . snd <$> userTemplates mbLocale + branding <- view templateBranding + Email.sendMail $ renderSecondFactorVerificationEmail tpl email code branding + +sendCreateScimTokenVerificationMail :: + ( MonadIO m, + MonadReader Env m + ) => + Email -> + Code.Value -> + Maybe Locale -> + m () +sendCreateScimTokenVerificationMail email code mbLocale = do + tpl <- verificationScimTokenEmail . snd <$> userTemplates mbLocale + branding <- view templateBranding + Email.sendMail $ renderSecondFactorVerificationEmail tpl email code branding + +sendTeamDeletionVerificationMail :: + ( MonadIO m, + MonadReader Env m + ) => + Email -> + Code.Value -> + Maybe Locale -> + m () +sendTeamDeletionVerificationMail email code mbLocale = do + tpl <- verificationTeamDeletionEmail . snd <$> userTemplates mbLocale + branding <- view templateBranding + Email.sendMail $ renderSecondFactorVerificationEmail tpl email code branding + +sendActivationMail :: + ( MonadIO m, + MonadReader Env m + ) => + Email -> + Name -> + ActivationPair -> + Maybe Locale -> + Maybe UserIdentity -> + m () sendActivationMail to name pair loc ident = do tpl <- selectTemplate . snd <$> userTemplates loc branding <- view templateBranding @@ -64,26 +122,59 @@ sendActivationMail to name pair loc ident = do then activationEmail else activationEmailUpdate -sendPasswordResetMail :: Email -> PasswordResetPair -> Maybe Locale -> (AppIO r) () +sendPasswordResetMail :: + ( MonadIO m, + MonadReader Env m + ) => + Email -> + PasswordResetPair -> + Maybe Locale -> + m () sendPasswordResetMail to pair loc = do tpl <- passwordResetEmail . snd <$> userTemplates loc branding <- view templateBranding let mail = PasswordResetEmail to pair Email.sendMail $ renderPwResetMail mail tpl branding -sendDeletionEmail :: Name -> Email -> Code.Key -> Code.Value -> Locale -> (AppIO r) () +sendDeletionEmail :: + ( MonadIO m, + MonadReader Env m + ) => + Name -> + Email -> + Code.Key -> + Code.Value -> + Locale -> + m () sendDeletionEmail name email key code locale = do tpl <- deletionEmail . snd <$> userTemplates (Just locale) branding <- view templateBranding Email.sendMail $ renderDeletionEmail tpl (DeletionEmail email name key code) branding -sendNewClientEmail :: Name -> Email -> Client -> Locale -> (AppIO r) () +sendNewClientEmail :: + ( MonadIO m, + MonadReader Env m + ) => + Name -> + Email -> + Client -> + Locale -> + m () sendNewClientEmail name email client locale = do tpl <- newClientEmail . snd <$> userTemplates (Just locale) branding <- view templateBranding Email.sendMail $ renderNewClientEmail tpl (NewClientEmail locale email name client) branding -sendTeamActivationMail :: Email -> Name -> ActivationPair -> Maybe Locale -> Text -> (AppIO r) () +sendTeamActivationMail :: + ( MonadIO m, + MonadReader Env m + ) => + Email -> + Name -> + ActivationPair -> + Maybe Locale -> + Text -> + m () sendTeamActivationMail to name pair loc team = do tpl <- teamActivationEmail . snd <$> userTemplates loc let mail = TeamActivationEmail to name team pair @@ -311,3 +402,33 @@ renderPwResetUrl t (PasswordResetKey k, PasswordResetCode c) branding = replace "key" = Ascii.toText k replace "code" = Ascii.toText c replace x = x + +------------------------------------------------------------------------------- +-- Second Factor Verification Code Email + +renderSecondFactorVerificationEmail :: + SecondFactorVerificationEmailTemplate -> + Email -> + Code.Value -> + TemplateBranding -> + Mail +renderSecondFactorVerificationEmail SecondFactorVerificationEmailTemplate {..} email codeValue branding = + (emptyMail from) + { mailTo = [to], + mailHeaders = + [ ("Subject", toStrict subj), + ("X-Zeta-Purpose", "SecondFactorVerification"), + ("X-Zeta-Code", code) + ], + mailParts = [[plainPart txt, htmlPart html]] + } + where + from = Address (Just sndFactorVerificationEmailSenderName) (fromEmail sndFactorVerificationEmailSender) + to = Address Nothing (fromEmail email) + txt = renderTextWithBranding sndFactorVerificationEmailBodyText replace branding + html = renderHtmlWithBranding sndFactorVerificationEmailBodyHtml replace branding + subj = renderTextWithBranding sndFactorVerificationEmailSubject replace branding + code = Ascii.toText (fromRange (Code.asciiValue codeValue)) + replace "email" = fromEmail email + replace "code" = code + replace x = x diff --git a/services/brig/src/Brig/User/Handle.hs b/services/brig/src/Brig/User/Handle.hs index 5616c5efb85..1cee4911a8e 100644 --- a/services/brig/src/Brig/User/Handle.hs +++ b/services/brig/src/Brig/User/Handle.hs @@ -34,7 +34,7 @@ import Data.Id import Imports -- | Claim a new handle for an existing 'User'. -claimHandle :: UserId -> Maybe Handle -> Handle -> (AppIO r) Bool +claimHandle :: (MonadClient m, MonadReader Env m) => UserId -> Maybe Handle -> Handle -> m Bool claimHandle uid oldHandle newHandle = isJust <$> do owner <- lookupHandle newHandle @@ -47,28 +47,28 @@ claimHandle uid oldHandle newHandle = runAppT env $ do -- Record ownership - retry x5 $ write handleInsert (params LocalQuorum (newHandle, uid)) + wrapClient $ retry x5 $ write handleInsert (params LocalQuorum (newHandle, uid)) -- Update profile - result <- User.updateHandle uid newHandle + result <- wrapClient $ User.updateHandle uid newHandle -- Free old handle (if it changed) for_ (mfilter (/= newHandle) oldHandle) $ - freeHandle uid + wrapClient . freeHandle uid return result -- | Free a 'Handle', making it available to be claimed again. -freeHandle :: UserId -> Handle -> (AppIO r) () +freeHandle :: MonadClient m => UserId -> Handle -> m () freeHandle uid h = do retry x5 $ write handleDelete (params LocalQuorum (Identity h)) let key = "@" <> fromHandle h deleteClaim uid key (30 # Minute) -- | Lookup the current owner of a 'Handle'. -lookupHandle :: Handle -> (AppIO r) (Maybe UserId) +lookupHandle :: MonadClient m => Handle -> m (Maybe UserId) lookupHandle = lookupHandleWithPolicy LocalQuorum -- | A weaker version of 'lookupHandle' that trades availability -- (and potentially speed) for the possibility of returning stale data. -glimpseHandle :: Handle -> (AppIO r) (Maybe UserId) +glimpseHandle :: MonadClient m => Handle -> m (Maybe UserId) glimpseHandle = lookupHandleWithPolicy One {-# INLINE lookupHandleWithPolicy #-} @@ -78,9 +78,9 @@ glimpseHandle = lookupHandleWithPolicy One -- -- FUTUREWORK: This should ideally be tackled by hiding constructor for 'Handle' -- and only allowing it to be parsed. -lookupHandleWithPolicy :: Consistency -> Handle -> (AppIO r) (Maybe UserId) +lookupHandleWithPolicy :: MonadClient m => Consistency -> Handle -> m (Maybe UserId) lookupHandleWithPolicy policy h = do - join . fmap runIdentity + (runIdentity =<<) <$> retry x1 (query1 handleSelect (params policy (Identity h))) -------------------------------------------------------------------------------- diff --git a/services/brig/src/Brig/User/Phone.hs b/services/brig/src/Brig/User/Phone.hs index a172780d083..d1fb8c9a8c4 100644 --- a/services/brig/src/Brig/User/Phone.hs +++ b/services/brig/src/Brig/User/Phone.hs @@ -44,45 +44,101 @@ import qualified Brig.Types.Code as Code import Brig.Types.User import Brig.Types.User.Auth (LoginCode (..)) import Brig.User.Template +import Cassandra (MonadClient) import Control.Lens (view) +import Control.Monad.Catch import Data.Range import qualified Data.Text as Text import qualified Data.Text.Ascii as Ascii import Data.Text.Lazy (toStrict) import Imports import qualified Ropes.Nexmo as Nexmo - -sendActivationSms :: Phone -> ActivationPair -> Maybe Locale -> (AppIO r) () +import qualified System.Logger.Class as Log + +sendActivationSms :: + ( MonadClient m, + MonadReader Env m, + MonadCatch m, + Log.MonadLogger m + ) => + Phone -> + ActivationPair -> + Maybe Locale -> + m () sendActivationSms to (_, c) loc = do branding <- view templateBranding (loc', tpl) <- userTemplates loc sendSms loc' $ renderActivationSms (ActivationSms to c) (activationSms tpl) branding -sendPasswordResetSms :: Phone -> PasswordResetPair -> Maybe Locale -> (AppIO r) () +sendPasswordResetSms :: + ( MonadClient m, + MonadReader Env m, + MonadCatch m, + Log.MonadLogger m + ) => + Phone -> + PasswordResetPair -> + Maybe Locale -> + m () sendPasswordResetSms to (_, c) loc = do branding <- view templateBranding (loc', tpl) <- userTemplates loc sendSms loc' $ renderPasswordResetSms (PasswordResetSms to c) (passwordResetSms tpl) branding -sendLoginSms :: Phone -> LoginCode -> Maybe Locale -> (AppIO r) () +sendLoginSms :: + ( MonadClient m, + MonadReader Env m, + MonadCatch m, + Log.MonadLogger m + ) => + Phone -> + LoginCode -> + Maybe Locale -> + m () sendLoginSms to code loc = do branding <- view templateBranding (loc', tpl) <- userTemplates loc sendSms loc' $ renderLoginSms (LoginSms to code) (loginSms tpl) branding -sendDeletionSms :: Phone -> Code.Key -> Code.Value -> Locale -> (AppIO r) () +sendDeletionSms :: + ( MonadClient m, + MonadReader Env m, + MonadCatch m, + Log.MonadLogger m + ) => + Phone -> + Code.Key -> + Code.Value -> + Locale -> + m () sendDeletionSms to key code loc = do branding <- view templateBranding (loc', tpl) <- userTemplates (Just loc) sendSms loc' $ renderDeletionSms (DeletionSms to key code) (deletionSms tpl) branding -sendActivationCall :: Phone -> ActivationPair -> Maybe Locale -> (AppIO r) () +sendActivationCall :: + ( MonadClient m, + MonadReader Env m, + Log.MonadLogger m + ) => + Phone -> + ActivationPair -> + Maybe Locale -> + m () sendActivationCall to (_, c) loc = do branding <- view templateBranding (loc', tpl) <- userTemplates loc sendCall $ renderActivationCall (ActivationCall to c) (activationCall tpl) loc' branding -sendLoginCall :: Phone -> LoginCode -> Maybe Locale -> (AppIO r) () +sendLoginCall :: + ( MonadClient m, + MonadReader Env m, + Log.MonadLogger m + ) => + Phone -> + LoginCode -> + Maybe Locale -> + m () sendLoginCall to c loc = do branding <- view templateBranding (loc', tpl) <- userTemplates loc diff --git a/services/brig/src/Brig/User/Template.hs b/services/brig/src/Brig/User/Template.hs index 544e0bbf366..d9197acf09e 100644 --- a/services/brig/src/Brig/User/Template.hs +++ b/services/brig/src/Brig/User/Template.hs @@ -29,6 +29,7 @@ module Brig.User.Template DeletionSmsTemplate (..), DeletionEmailTemplate (..), NewClientEmailTemplate (..), + SecondFactorVerificationEmailTemplate (..), loadUserTemplates, -- * Re-exports @@ -56,7 +57,10 @@ data UserTemplates = UserTemplates loginCall :: !LoginCallTemplate, deletionSms :: !DeletionSmsTemplate, deletionEmail :: !DeletionEmailTemplate, - newClientEmail :: !NewClientEmailTemplate + newClientEmail :: !NewClientEmailTemplate, + verificationLoginEmail :: !SecondFactorVerificationEmailTemplate, + verificationScimTokenEmail :: !SecondFactorVerificationEmailTemplate, + verificationTeamDeletionEmail :: !SecondFactorVerificationEmailTemplate } data ActivationSmsTemplate = ActivationSmsTemplate @@ -143,6 +147,14 @@ data NewClientEmailTemplate = NewClientEmailTemplate newClientEmailSenderName :: !Text } +data SecondFactorVerificationEmailTemplate = SecondFactorVerificationEmailTemplate + { sndFactorVerificationEmailSubject :: !Template, + sndFactorVerificationEmailBodyText :: !Template, + sndFactorVerificationEmailBodyHtml :: !Template, + sndFactorVerificationEmailSender :: !Email, + sndFactorVerificationEmailSenderName :: !Text + } + loadUserTemplates :: Opt.Opts -> IO (Localised UserTemplates) loadUserTemplates o = readLocalesDir defLocale templateDir "user" $ \fp -> UserTemplates @@ -217,6 +229,27 @@ loadUserTemplates o = readLocalesDir defLocale templateDir "user" $ \fp -> <*> pure emailSender <*> readText fp "email/sender.txt" ) + <*> ( SecondFactorVerificationEmailTemplate + <$> readTemplate fp "email/verification-login-subject.txt" + <*> readTemplate fp "email/verification-login.txt" + <*> readTemplate fp "email/verification-login.html" + <*> pure emailSender + <*> readText fp "email/sender.txt" + ) + <*> ( SecondFactorVerificationEmailTemplate + <$> readTemplate fp "email/verification-scim-token-subject.txt" + <*> readTemplate fp "email/verification-scim-token.txt" + <*> readTemplate fp "email/verification-scim-token.html" + <*> pure emailSender + <*> readText fp "email/sender.txt" + ) + <*> ( SecondFactorVerificationEmailTemplate + <$> readTemplate fp "email/verification-delete-team-subject.txt" + <*> readTemplate fp "email/verification-delete-team.txt" + <*> readTemplate fp "email/verification-delete-team.html" + <*> pure emailSender + <*> readText fp "email/sender.txt" + ) where gOptions = Opt.general $ Opt.emailSMS o uOptions = Opt.user $ Opt.emailSMS o diff --git a/services/brig/test/integration/API/MLS.hs b/services/brig/test/integration/API/MLS.hs index b85678be23b..f816bbfceb0 100644 --- a/services/brig/test/integration/API/MLS.hs +++ b/services/brig/test/integration/API/MLS.hs @@ -7,6 +7,7 @@ import Data.ByteString.Conversion import qualified Data.ByteString.Lazy as LBS import Data.Domain import Data.Id +import qualified Data.Map as Map import Data.Qualified import qualified Data.Set as Set import qualified Data.Text as T @@ -16,6 +17,8 @@ import Test.Tasty import Test.Tasty.HUnit import UnliftIO.Temporary import Util +import Web.HttpApiData +import Wire.API.MLS.Credential import Wire.API.MLS.KeyPackage import Wire.API.User import Wire.API.User.Client @@ -25,6 +28,7 @@ tests m b _opts = testGroup "MLS" [ test m "POST /mls/key-packages/self/:client" (testKeyPackageUpload b), + test m "POST /mls/key-packages/self/:client (no public keys)" (testKeyPackageUploadNoKey b), test m "GET /mls/key-packages/self/:client/count" (testKeyPackageZeroCount b), test m "GET /mls/key-packages/claim/:domain/:user" (testKeyPackageClaim b) ] @@ -34,28 +38,26 @@ testKeyPackageUpload brig = do u <- userQualifiedId <$> randomUser brig c <- createClient brig u 0 withSystemTempFile "store.db" $ \store _ -> - uploadKeyPackages brig store u c 5 + uploadKeyPackages brig store SetKey u c 5 - count :: KeyPackageCount <- - responseJsonError - =<< get - ( brig . paths ["mls", "key-packages", "self", toByteString' c, "count"] - . zUser (qUnqualified u) - ) - Http () +testKeyPackageUploadNoKey brig = do + u <- userQualifiedId <$> randomUser brig + c <- createClient brig u 0 + withSystemTempFile "store.db" $ \store _ -> + uploadKeyPackages brig store DontSetKey u c 5 + + count <- getKeyPackageCount brig u c + liftIO $ count @?= 0 + testKeyPackageZeroCount :: Brig -> Http () testKeyPackageZeroCount brig = do u <- userQualifiedId <$> randomUser brig c <- randomClient - count :: KeyPackageCount <- - responseJsonError - =<< get - ( brig . paths ["mls", "key-packages", "self", toByteString' c, "count"] - . zUser (qUnqualified u) - ) - Http () @@ -64,9 +66,9 @@ testKeyPackageClaim brig = do u <- userQualifiedId <$> randomUser brig [c1, c2] <- for [0, 1] $ \i -> do c <- createClient brig u i - -- upload 5 key packages for each client + -- upload 3 key packages for each client withSystemTempFile "store.db" $ \store _ -> - uploadKeyPackages brig store u c 5 + uploadKeyPackages brig store SetKey u c 3 pure c -- claim packages for both clients of u @@ -90,10 +92,33 @@ testKeyPackageClaim brig = do . zUser (qUnqualified u) ) do + cid <- + responseJsonError + =<< get (brig . paths ["i", "mls", "key-packages", toHeader (kpbeRef e)]) + Qualified UserId -> ClientId -> Http KeyPackageCount +getKeyPackageCount brig u c = + responseJsonError + =<< get + ( brig . paths ["mls", "key-packages", "self", toByteString' c, "count"] + . zUser (qUnqualified u) + ) + Qualified UserId -> Int -> Http ClientId createClient brig u i = fmap clientId $ @@ -104,8 +129,8 @@ createClient brig u i = (defNewClient PermanentClientType [somePrekeys !! i] (someLastPrekeys !! i)) FilePath -> Qualified UserId -> ClientId -> Int -> Http () -uploadKeyPackages brig store u c n = do +uploadKeyPackages :: Brig -> FilePath -> SetKey -> Qualified UserId -> ClientId -> Int -> Http () +uploadKeyPackages brig store sk u c n = do let cmd0 = ["crypto-cli", "--store", store, "--enc-key", "test"] clientId = show (qUnqualified u) @@ -116,6 +141,18 @@ uploadKeyPackages brig store u c n = do kps <- replicateM n . liftIO . fmap (KeyPackageData . LBS.fromStrict) . spawn . shell . unwords $ cmd0 <> ["key-package", clientId] + when (sk == SetKey) $ + do + pk <- + liftIO . spawn . shell . unwords $ + cmd0 <> ["public-key", clientId] + put + ( brig + . paths ["clients", toByteString' c] + . zUser (qUnqualified u) + . json defUpdateClient {updateClientMLSPublicKeys = Map.fromList [(Ed25519, pk)]} + ) + !!! const 200 === statusCode let upload = KeyPackageUpload kps post ( brig @@ -123,4 +160,4 @@ uploadKeyPackages brig store u c n = do . zUser (qUnqualified u) . json upload ) - !!! const 201 === statusCode + !!! const (case sk of SetKey -> 201; DontSetKey -> 400) === statusCode diff --git a/services/brig/test/integration/API/Provider.hs b/services/brig/test/integration/API/Provider.hs index 742fe3e30c3..7e5ec14f760 100644 --- a/services/brig/test/integration/API/Provider.hs +++ b/services/brig/test/integration/API/Provider.hs @@ -87,6 +87,7 @@ import qualified Test.Tasty.Cannon as WS import Test.Tasty.HUnit import Util import Web.Cookie (SetCookie (..), parseSetCookie) +import Wire.API.Asset hiding (Asset) import Wire.API.Event.Conversation tests :: Domain -> Config -> Manager -> DB.ClientState -> Brig -> Cannon -> Galley -> IO TestTree @@ -1294,7 +1295,7 @@ createConvWithAccessRoles ars g u us = . contentJson . body (RequestBodyLBS (encode conv)) where - conv = NewConv us [] Nothing Set.empty ars Nothing Nothing Nothing roleNameWireAdmin + conv = NewConv us [] Nothing Set.empty ars Nothing Nothing Nothing roleNameWireAdmin ProtocolProteus postMessage :: Galley -> @@ -1561,7 +1562,11 @@ defServiceTags :: Range 1 3 (Set ServiceTag) defServiceTags = unsafeRange (Set.singleton SocialTag) defServiceAssets :: [Asset] -defServiceAssets = [ImageAsset "key" (Just AssetComplete)] +defServiceAssets = + [ ImageAsset + (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) + (Just AssetComplete) + ] -- TODO: defServiceToken :: ServiceToken diff --git a/services/brig/test/integration/API/Team.hs b/services/brig/test/integration/API/Team.hs index 654f274fc8c..5f59e081150 100644 --- a/services/brig/test/integration/API/Team.hs +++ b/services/brig/test/integration/API/Team.hs @@ -42,6 +42,7 @@ import Data.Id hiding (client) import Data.Json.Util (UTCTimeMillis, toUTCTimeMillis) import qualified Data.Text.Ascii as Ascii import Data.Time (addUTCTime, getCurrentTime) +import qualified Data.UUID as UUID (fromString) import qualified Data.UUID.V4 as UUID import qualified Galley.Types.Teams as Team import qualified Galley.Types.Teams.Intra as Team @@ -56,6 +57,7 @@ import UnliftIO.Async (mapConcurrently_, pooledForConcurrentlyN_, replicateConcu import Util import Util.AWS as Util import Web.Cookie (parseSetCookie, setCookieName) +import Wire.API.Asset import Wire.API.User.Identity (mkSimpleSampleUref) newtype TeamSizeLimit = TeamSizeLimit Word32 @@ -149,7 +151,7 @@ testUpdateEvents brig cannon = do void $ getConnection brig alice bob Galley -> TeamId -> UserId -> [UserId] -> Mayb createTeamConv g tid u us mtimer = do let tinfo = Just $ ConvTeamInfo tid let conv = - NewConv us [] Nothing (Set.fromList []) Nothing tinfo mtimer Nothing roleNameWireAdmin + NewConv us [] Nothing (Set.fromList []) Nothing tinfo mtimer Nothing roleNameWireAdmin ProtocolProteus r <- post ( g @@ -265,7 +266,7 @@ getTeams u galley = ) newTeam :: Team.BindingNewTeam -newTeam = Team.BindingNewTeam $ Team.newNewTeam (unsafeRange "teamName") (unsafeRange "defaultIcon") +newTeam = Team.BindingNewTeam $ Team.newNewTeam (unsafeRange "teamName") DefaultIcon putLegalHoldEnabled :: HasCallStack => TeamId -> TeamFeatureStatusValue -> Galley -> Http () putLegalHoldEnabled tid enabled g = do diff --git a/services/brig/test/integration/API/User.hs b/services/brig/test/integration/API/User.hs index 296ca1157c7..96abe851db3 100644 --- a/services/brig/test/integration/API/User.hs +++ b/services/brig/test/integration/API/User.hs @@ -62,9 +62,9 @@ tests conf fbc fgc p b c ch g n aws db = do return $ testGroup "user" - [ API.User.Client.tests cl at conf p b c g, + [ API.User.Client.tests cl at conf p db b c g, API.User.Account.tests cl at conf p b c ch g aws, - API.User.Auth.tests conf p z b g n, + API.User.Auth.tests conf p z db b g n, API.User.Connection.tests cl at conf p b c g fbc fgc db, API.User.Handles.tests cl at conf p b c g, API.User.PasswordReset.tests cl at conf p b c g, diff --git a/services/brig/test/integration/API/User/Account.hs b/services/brig/test/integration/API/User/Account.hs index 5483a764036..04aebbd3b6c 100644 --- a/services/brig/test/integration/API/User/Account.hs +++ b/services/brig/test/integration/API/User/Account.hs @@ -82,6 +82,7 @@ import UnliftIO (mapConcurrently_) import Util import Util.AWS as Util import Web.Cookie (parseSetCookie) +import Wire.API.Asset hiding (Asset) import qualified Wire.API.Asset as Asset import Wire.API.Federation.API.Brig (UserDeletedConnectionsNotification (..)) import qualified Wire.API.Federation.API.Brig as FedBrig @@ -816,7 +817,12 @@ testUserUpdate brig cannon aws = do aliceNewName <- randomName connectUsers brig alice (singleton bob) let newColId = Just 5 - newAssets = Just [ImageAsset "abc" (Just AssetComplete)] + newAssets = + Just + [ ImageAsset + (AssetKeyV3 (Id (fromJust (UUID.fromString "5cd81cc4-c643-4e9c-849c-c596a88c27fd"))) AssetExpiring) + (Just AssetComplete) + ] mNewName = Just $ aliceNewName newPic = Nothing -- Legacy userUpdate = UserUpdate mNewName newPic newAssets newColId @@ -1336,7 +1342,7 @@ testDeleteWithProfilePic brig cargohold = do let newAssets = Just [ ImageAsset - (T.decodeLatin1 $ toByteString' (qUnqualified (ast ^. Asset.assetKey))) + (qUnqualified $ ast ^. Asset.assetKey) (Just AssetComplete) ] userUpdate = UserUpdate Nothing Nothing newAssets Nothing diff --git a/services/brig/test/integration/API/User/Auth.hs b/services/brig/test/integration/API/User/Auth.hs index 73e0375fd4f..fc6a922a716 100644 --- a/services/brig/test/integration/API/User/Auth.hs +++ b/services/brig/test/integration/API/User/Auth.hs @@ -25,17 +25,19 @@ module API.User.Auth where import API.Team.Util +import qualified API.User.Util as Util import Bilge hiding (body) import qualified Bilge as Http import Bilge.Assert hiding (assert) +import qualified Brig.Code as Code import qualified Brig.Options as Opts -import qualified Brig.Types.Code as Code +import Brig.Types import Brig.Types.Intra -import Brig.Types.User import Brig.Types.User.Auth import qualified Brig.Types.User.Auth as Auth import Brig.ZAuth (ZAuth, runZAuth) import qualified Brig.ZAuth as ZAuth +import qualified Cassandra as DB import Control.Lens (set, (^.)) import Control.Retry import Data.Aeson as Aeson @@ -46,7 +48,9 @@ import Data.Handle (Handle (Handle)) import Data.Id import Data.Misc (PlainTextPassword (..)) import Data.Proxy +import Data.Range (unsafeRange) import qualified Data.Text as Text +import Data.Text.Ascii (AsciiChars (validate)) import Data.Text.IO (hPutStrLn) import qualified Data.Text.Lazy as Lazy import Data.Time.Clock @@ -60,6 +64,8 @@ import Test.Tasty.HUnit import qualified Test.Tasty.HUnit as HUnit import UnliftIO.Async hiding (wait) import Util +import qualified Wire.API.Team.Feature as Public +import qualified Wire.API.User as Public -- | FUTUREWORK: Implement this function. This wrapper should make sure that -- wrapped tests run only when the feature flag 'legalhold' is set to @@ -80,8 +86,8 @@ onlyIfLhWhitelisted action = do \(the 'withLHWhitelist' trick does not work because it does not allow \ \brig to talk to the dynamically spawned galley)." -tests :: Opts.Opts -> Manager -> ZAuth.Env -> Brig -> Galley -> Nginz -> TestTree -tests conf m z b g n = +tests :: Opts.Opts -> Manager -> ZAuth.Env -> DB.ClientState -> Brig -> Galley -> Nginz -> TestTree +tests conf m z db b g n = testGroup "auth" [ testGroup @@ -115,6 +121,14 @@ tests conf m z b g n = [ test m "nginz-login" (testNginz b n), test m "nginz-legalhold-login" (onlyIfLhWhitelisted (testNginzLegalHold b g n)), test m "nginz-login-multiple-cookies" (testNginzMultipleCookies conf b n) + ], + testGroup + "snd-factor-password-challenge" + [ test m "test-login-verify6-digit-email-code-success" $ testLoginVerify6DigitEmailCodeSuccess b g db, + test m "test-login-verify6-digit-wrong-code-fails" $ testLoginVerify6DigitWrongCodeFails b g, + test m "test-login-verify6-digit-missing-code-fails" $ testLoginVerify6DigitMissingCodeFails b g, + test m "test-login-verify6-digit-expired-code-fails" $ testLoginVerify6DigitExpiredCodeFails b g db, + test m "test-login-verify6-digit-resend-code-success" $ testLoginVerify6DigitResendCodeSuccess b g db ] ], testGroup @@ -361,6 +375,92 @@ testSendLoginCode brig = do let _timeout = fromLoginCodeTimeout <$> responseJsonMaybe rsp2 liftIO $ assertEqual "timeout" (Just (Code.Timeout 600)) _timeout +testLoginVerify6DigitEmailCodeSuccess :: Brig -> Galley -> DB.ClientState -> Http () +testLoginVerify6DigitEmailCodeSuccess brig galley db = do + (u, tid) <- createUserWithTeam' brig + let Just email = userEmail u + let checkLoginSucceeds body = login brig body PersistentCookie !!! const 200 === statusCode + Util.setTeamFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge galley tid Public.Unlocked + Util.setTeamSndFactorPasswordChallenge galley tid Public.TeamFeatureEnabled + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + key <- Code.mkKey (Code.ForEmail email) + Just vcode <- Util.lookupCode db key Code.AccountLogin + checkLoginSucceeds $ PasswordLogin (LoginByEmail email) defPassword (Just defCookieLabel) (Just $ Code.codeValue vcode) + +testLoginVerify6DigitResendCodeSuccess :: Brig -> Galley -> DB.ClientState -> Http () +testLoginVerify6DigitResendCodeSuccess brig galley db = do + (u, tid) <- createUserWithTeam' brig + let Just email = userEmail u + let checkLoginSucceeds body = login brig body PersistentCookie !!! const 200 === statusCode + let checkLoginFails body = + login brig body PersistentCookie !!! do + const 403 === statusCode + const (Just "code-authentication-failed") === errorLabel + let getCodeFromDb = do + key <- Code.mkKey (Code.ForEmail email) + Just c <- Util.lookupCode db key Code.AccountLogin + pure c + + Util.setTeamFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge galley tid Public.Unlocked + Util.setTeamSndFactorPasswordChallenge galley tid Public.TeamFeatureEnabled + + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + fstCode <- getCodeFromDb + + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + mostRecentCode <- getCodeFromDb + + checkLoginFails $ PasswordLogin (LoginByEmail email) defPassword (Just defCookieLabel) (Just $ Code.codeValue fstCode) + checkLoginSucceeds $ PasswordLogin (LoginByEmail email) defPassword (Just defCookieLabel) (Just $ Code.codeValue mostRecentCode) + +testLoginVerify6DigitWrongCodeFails :: Brig -> Galley -> Http () +testLoginVerify6DigitWrongCodeFails brig galley = do + (u, tid) <- createUserWithTeam' brig + let Just email = userEmail u + let checkLoginFails body = + login brig body PersistentCookie !!! do + const 403 === statusCode + const (Just "code-authentication-failed") === errorLabel + + Util.setTeamFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge galley tid Public.Unlocked + Util.setTeamSndFactorPasswordChallenge galley tid Public.TeamFeatureEnabled + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + let wrongCode = Code.Value $ unsafeRange (fromRight undefined (validate "123456")) + checkLoginFails $ PasswordLogin (LoginByEmail email) defPassword (Just defCookieLabel) (Just wrongCode) + +testLoginVerify6DigitMissingCodeFails :: Brig -> Galley -> Http () +testLoginVerify6DigitMissingCodeFails brig galley = do + (u, tid) <- createUserWithTeam' brig + let Just email = userEmail u + let checkLoginFails body = + login brig body PersistentCookie !!! do + const 403 === statusCode + const (Just "code-authentication-required") === errorLabel + + Util.setTeamFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge galley tid Public.Unlocked + Util.setTeamSndFactorPasswordChallenge galley tid Public.TeamFeatureEnabled + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + checkLoginFails $ PasswordLogin (LoginByEmail email) defPassword (Just defCookieLabel) Nothing + +testLoginVerify6DigitExpiredCodeFails :: Brig -> Galley -> DB.ClientState -> Http () +testLoginVerify6DigitExpiredCodeFails brig galley db = do + (u, tid) <- createUserWithTeam' brig + let Just email = userEmail u + let checkLoginFails body = + login brig body PersistentCookie !!! do + const 403 === statusCode + const (Just "code-authentication-failed") === errorLabel + + Util.setTeamFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge galley tid Public.Unlocked + Util.setTeamSndFactorPasswordChallenge galley tid Public.TeamFeatureEnabled + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + key <- Code.mkKey (Code.ForEmail email) + Just vcode <- Util.lookupCode db key Code.AccountLogin + -- wait > 5 sec for the code to expire (assumption: setVerificationTimeout in brig.integration.yaml is set to <= 5 sec) + threadDelay $ (5 * 1000000) + 600000 + checkLoginFails $ PasswordLogin (LoginByEmail email) defPassword (Just defCookieLabel) (Just $ Code.codeValue vcode) + -- The testLoginFailure test conforms to the following testing standards: -- @SF.Provisioning @TSFI.RESTfulAPI @S2 -- @@ -1006,7 +1106,7 @@ testReauthentication b = do const 403 === statusCode const (Just "suspended") === errorLabel where - payload = Http.body . RequestBodyLBS . encode . ReAuthUser + payload pw = Http.body $ RequestBodyLBS $ encode $ ReAuthUser pw Nothing Nothing ----------------------------------------------------------------------------- -- Helpers diff --git a/services/brig/test/integration/API/User/Client.hs b/services/brig/test/integration/API/User/Client.hs index bf14f69df98..6fd0274f1f6 100644 --- a/services/brig/test/integration/API/User/Client.hs +++ b/services/brig/test/integration/API/User/Client.hs @@ -22,22 +22,28 @@ module API.User.Client ) where +import qualified API.Team.Util as Util import API.User.Util +import qualified API.User.Util as Util import Bilge hiding (accept, head, timeout) import Bilge.Assert +import qualified Brig.Code as Code import qualified Brig.Options as Opt import Brig.Types import Brig.Types.User.Auth hiding (user) +import qualified Cassandra as DB import Control.Lens (at, preview, (.~), (^.), (^?)) -import Data.Aeson +import Data.Aeson hiding (json) import Data.Aeson.Lens import Data.ByteString.Conversion +import Data.Default import Data.Id hiding (client) import qualified Data.List1 as List1 import qualified Data.Map as Map import Data.Qualified (Qualified (..)) import Data.Range (unsafeRange) import qualified Data.Set as Set +import Data.Text.Ascii (AsciiChars (validate)) import qualified Data.Vector as Vec import Gundeck.Types.Notification import Imports @@ -49,20 +55,16 @@ import qualified Test.Tasty.Cannon as WS import Test.Tasty.HUnit import UnliftIO (mapConcurrently) import Util +import Wire.API.MLS.Credential +import qualified Wire.API.Team.Feature as Public import Wire.API.User (LimitedQualifiedUserIdList (LimitedQualifiedUserIdList)) +import qualified Wire.API.User as Public import Wire.API.User.Client - ( ClientCapability (ClientSupportsLegalholdImplicitConsent), - ClientCapabilityList (ClientCapabilityList), - QualifiedUserClients (..), - UserClients (..), - mkQualifiedUserClientPrekeyMap, - mkUserClientPrekeyMap, - ) import Wire.API.UserMap (QualifiedUserMap (..), UserMap (..), WrappedQualifiedUserMap) import Wire.API.Wrapped (Wrapped (..)) -tests :: ConnectionLimit -> Opt.Timeout -> Opt.Opts -> Manager -> Brig -> Cannon -> Galley -> TestTree -tests _cl _at opts p b c g = +tests :: ConnectionLimit -> Opt.Timeout -> Opt.Opts -> Manager -> DB.ClientState -> Brig -> Cannon -> Galley -> TestTree +tests _cl _at opts p db b c g = testGroup "client" [ test p "delete /clients/:client 403 - can't delete legalhold clients" $ @@ -80,8 +82,16 @@ tests _cl _at opts p b c g = test p "post /users/list-prekeys" $ testMultiUserGetPrekeysQualified b opts, test p "post /users/list-clients - 200" $ testListClientsBulk opts b, test p "post /users/list-clients/v2 - 200" $ testListClientsBulkV2 opts b, - test p "post /clients - 201 (pwd)" $ testAddGetClient True b c, - test p "post /clients - 201 (no pwd)" $ testAddGetClient False b c, + test p "post /clients - 201 (pwd)" $ testAddGetClient def {addWithPassword = True} b c, + test p "post /clients - 201 (no pwd)" $ testAddGetClient def {addWithPassword = False} b c, + testGroup + "post /clients - verification code" + [ test p "success" $ testAddGetClientVerificationCode db b g, + test p "missing code" $ testAddGetClientMissingCode b g, + test p "wrong code" $ testAddGetClientWrongCode b g, + test p "expired code" $ testAddGetClientCodeExpired db b g + ], + test p "post /clients - 201 (with mls keys)" $ testAddGetClient def {addWithMLSKeys = True} b c, test p "post /clients - 403" $ testClientReauthentication b, test p "get /clients - 200" $ testListClients b, test p "get /clients/:client/prekeys - 200" $ testListPrekeyIds b, @@ -91,16 +101,101 @@ tests _cl _at opts p b c g = test p "delete /clients/:client - 400 (short pwd)" $ testRemoveClientShortPwd b, test p "delete /clients/:client - 403 (incorrect pwd)" $ testRemoveClientIncorrectPwd b, test p "put /clients/:client - 200" $ testUpdateClient opts b, + test p "put /clients/:client - 200 (mls keys)" $ testMLSPublicKeyUpdate b, test p "get /clients/:client - 404" $ testMissingClient b, test p "post /clients - 200 multiple temporary" $ testAddMultipleTemporary b g, test p "client/prekeys/race" $ testPreKeyRace b ] -testAddGetClient :: Bool -> Brig -> Cannon -> Http () -testAddGetClient hasPwd brig cannon = do - uid <- userId <$> randomUser' hasPwd brig +testAddGetClientVerificationCode :: DB.ClientState -> Brig -> Galley -> Http () +testAddGetClientVerificationCode db brig galley = do + (u, tid) <- Util.createUserWithTeam' brig + let uid = userId u + let Just email = userEmail u + let checkLoginSucceeds b = login brig b PersistentCookie !!! const 200 === statusCode + let addClient' :: Maybe Code.Value -> Http Client + addClient' codeValue = responseJsonError =<< addClient brig uid (defNewClientWithVerificationCode codeValue PermanentClientType [head somePrekeys] (head someLastPrekeys)) + + Util.setTeamFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge galley tid Public.Unlocked + Util.setTeamSndFactorPasswordChallenge galley tid Public.TeamFeatureEnabled + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + k <- Code.mkKey (Code.ForEmail email) + codeValue <- Code.codeValue <$$> lookupCode db k Code.AccountLogin + checkLoginSucceeds $ PasswordLogin (LoginByEmail email) defPassword (Just defCookieLabel) codeValue + c <- addClient' codeValue + getClient brig uid (clientId c) !!! do + const 200 === statusCode + const (Just c) === responseJsonMaybe + +testAddGetClientMissingCode :: Brig -> Galley -> Http () +testAddGetClientMissingCode brig galley = do + (u, tid) <- Util.createUserWithTeam' brig + let uid = userId u + let Just email = userEmail u + let addClient' codeValue = addClient brig uid (defNewClientWithVerificationCode codeValue PermanentClientType [head somePrekeys] (head someLastPrekeys)) + + Util.setTeamFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge galley tid Public.Unlocked + Util.setTeamSndFactorPasswordChallenge galley tid Public.TeamFeatureEnabled + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + addClient' Nothing !!! do + const 403 === statusCode + const (Just "code-authentication-required") === fmap Error.label . responseJsonMaybe + +testAddGetClientWrongCode :: Brig -> Galley -> Http () +testAddGetClientWrongCode brig galley = do + (u, tid) <- Util.createUserWithTeam' brig + let uid = userId u + let Just email = userEmail u + let addClient' codeValue = addClient brig uid (defNewClientWithVerificationCode codeValue PermanentClientType [head somePrekeys] (head someLastPrekeys)) + + Util.setTeamFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge galley tid Public.Unlocked + Util.setTeamSndFactorPasswordChallenge galley tid Public.TeamFeatureEnabled + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + let wrongCode = Code.Value $ unsafeRange (fromRight undefined (validate "123456")) + addClient' (Just wrongCode) !!! do + const 403 === statusCode + const (Just "code-authentication-failed") === fmap Error.label . responseJsonMaybe + +testAddGetClientCodeExpired :: DB.ClientState -> Brig -> Galley -> Http () +testAddGetClientCodeExpired db brig galley = do + (u, tid) <- Util.createUserWithTeam' brig + let uid = userId u + let Just email = userEmail u + let checkLoginSucceeds b = login brig b PersistentCookie !!! const 200 === statusCode + let addClient' codeValue = addClient brig uid (defNewClientWithVerificationCode codeValue PermanentClientType [head somePrekeys] (head someLastPrekeys)) + + Util.setTeamFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge galley tid Public.Unlocked + Util.setTeamSndFactorPasswordChallenge galley tid Public.TeamFeatureEnabled + Util.generateVerificationCode brig (Public.SendVerificationCode Public.Login email) + k <- Code.mkKey (Code.ForEmail email) + codeValue <- Code.codeValue <$$> lookupCode db k Code.AccountLogin + checkLoginSucceeds $ PasswordLogin (LoginByEmail email) defPassword (Just defCookieLabel) codeValue + -- wait > 5 sec for the code to expire (assumption: setVerificationTimeout in brig.integration.yaml is set to <= 5 sec) + threadDelay $ (5 * 1000000) + 600000 + addClient' codeValue !!! do + const 403 === statusCode + const (Just "code-authentication-failed") === fmap Error.label . responseJsonMaybe + +data AddGetClient = AddGetClient + { addWithPassword :: Bool, + addWithMLSKeys :: Bool + } + +instance Default AddGetClient where + def = AddGetClient True False + +testAddGetClient :: AddGetClient -> Brig -> Cannon -> Http () +testAddGetClient params brig cannon = do + uid <- userId <$> randomUser' (addWithPassword params) brig + let new = + (defNewClient TemporaryClientType [somePrekeys !! 0] (someLastPrekeys !! 0)) + { newClientMLSPublicKeys = keys + } + keys + | addWithMLSKeys params = Map.fromList [(Ed25519, "aGVsbG8gd29ybGQ=")] + | otherwise = mempty let rq = - addClientReq brig uid (defNewClient TemporaryClientType [somePrekeys !! 0] (someLastPrekeys !! 0)) + addClientReq brig uid new . header "X-Forwarded-For" "127.0.0.1" -- Fake IP to test IpAddr parsing. c <- WS.bracketR cannon uid $ \ws -> do c <- @@ -116,6 +211,7 @@ testAddGetClient hasPwd brig cannon = do etype @?= Just "user.client-add" fmap fromJSON eclient @?= Just (Success c) return c + liftIO $ clientMLSPublicKeys c @?= keys getClient brig uid (clientId c) !!! do const 200 === statusCode const (Just c) === responseJsonMaybe @@ -579,7 +675,11 @@ testUpdateClient opts brig = do const (Just PhoneClient) === (clientClass <=< responseJsonMaybe) const (Just "featurephone") === (clientModel <=< responseJsonMaybe) let newPrekey = somePrekeys !! 2 - let update = UpdateClient [newPrekey] Nothing (Just "label") Nothing + let update = + defUpdateClient + { updateClientPrekeys = [newPrekey], + updateClientLabel = Just "label" + } put ( brig . paths ["clients", toByteString' (clientId c)] @@ -604,6 +704,7 @@ testUpdateClient opts brig = do const (Just $ clientId c) === (fmap pubClientId . responseJsonMaybe) const (Just PhoneClient) === (pubClientClass <=< responseJsonMaybe) const Nothing === (preview (key "label") <=< responseJsonMaybe @Value) + const Nothing === (preview (key "mls_public_keys") <=< responseJsonMaybe @Value) -- via `/users/:domain/:uid/clients/:client`, only `id` and `class` are visible: let localdomain = opts ^. Opt.optionSettings & Opt.setFederationDomain @@ -612,8 +713,9 @@ testUpdateClient opts brig = do const (Just $ clientId c) === (fmap pubClientId . responseJsonMaybe) const (Just PhoneClient) === (pubClientClass <=< responseJsonMaybe) const Nothing === (preview (key "label") <=< responseJsonMaybe @Value) + const Nothing === (preview (key "mls_public_keys") <=< responseJsonMaybe @Value) - let update' = UpdateClient [] Nothing Nothing Nothing + let update' = defUpdateClient -- empty update should be a no-op put @@ -634,7 +736,7 @@ testUpdateClient opts brig = do -- update supported client capabilities work let checkUpdate :: HasCallStack => Maybe [ClientCapability] -> Bool -> [ClientCapability] -> Http () checkUpdate capsIn respStatusOk capsOut = do - let update'' = UpdateClient [] Nothing Nothing (Set.fromList <$> capsIn) + let update'' = defUpdateClient {updateClientCapabilities = Set.fromList <$> capsIn} put ( brig . paths ["clients", toByteString' (clientId c)] @@ -693,8 +795,12 @@ testUpdateClient opts brig = do ( brig . paths ["clients", toByteString' (clientId c)] . zUser uid - . contentJson - . (body . RequestBodyLBS . encode $ UpdateClient [prekey] (Just lastprekey) (Just label) Nothing) + . json + defUpdateClient + { updateClientPrekeys = [prekey], + updateClientLastKey = Just lastprekey, + updateClientLabel = Just label + } ) !!! const 200 === statusCode checkClientLabel @@ -702,14 +808,43 @@ testUpdateClient opts brig = do ( brig . paths ["clients", toByteString' (clientId c)] . zUser uid - . contentJson - . (body . RequestBodyLBS . encode $ UpdateClient [] Nothing Nothing caps) + . json defUpdateClient {updateClientCapabilities = caps} ) !!! const 200 === statusCode checkClientLabel checkClientPrekeys prekey checkClientPrekeys (unpackLastPrekey lastprekey) +testMLSPublicKeyUpdate :: Brig -> Http () +testMLSPublicKeyUpdate brig = do + uid <- userId <$> randomUser brig + let clt = + (defNewClient TemporaryClientType [somePrekeys !! 0] (someLastPrekeys !! 0)) + { newClientClass = Just PhoneClient, + newClientModel = Just "featurephone" + } + c <- responseJsonError =<< addClient brig uid clt + let keys = Map.fromList [(Ed25519, "aGVsbG8gd29ybGQ=")] + put + ( brig + . paths ["clients", toByteString' (clientId c)] + . zUser uid + . contentJson + . json (UpdateClient [] Nothing Nothing Nothing keys) + ) + !!! const 200 === statusCode + c' <- responseJsonError =<< getClient brig uid (clientId c) Http () testMissingClient brig = do uid <- userId <$> randomUser brig diff --git a/services/brig/test/integration/API/User/Util.hs b/services/brig/test/integration/API/User/Util.hs index 0ef1900fd0a..cdc1fe96b84 100644 --- a/services/brig/test/integration/API/User/Util.hs +++ b/services/brig/test/integration/API/User/Util.hs @@ -21,12 +21,14 @@ module API.User.Util where import Bilge hiding (accept, timeout) import Bilge.Assert +import qualified Brig.Code as Code import Brig.Data.PasswordReset import Brig.Options (Opts) import Brig.Types import Brig.Types.Team.LegalHold (LegalHoldClientRequest (..)) import Brig.Types.User.Auth hiding (user) import qualified Brig.ZAuth +import qualified Cassandra as DB import qualified Codec.MIME.Type as MIME import Control.Lens (preview, (^?)) import Control.Monad.Catch (MonadCatch) @@ -58,6 +60,8 @@ import qualified Wire.API.Federation.API.Brig as F import Wire.API.Federation.Component import Wire.API.Routes.Internal.Brig.Connection import Wire.API.Routes.MultiTablePaging (LocalOrRemoteTable, MultiTablePagingState) +import qualified Wire.API.Team.Feature as Public +import qualified Wire.API.User as Public newtype ConnectionLimit = ConnectionLimit Int64 @@ -478,3 +482,31 @@ matchConvLeaveNotification conv remover removeds n = do where sorted (Conv.EdMembersLeave (Conv.QualifiedUserIdList m)) = Conv.EdMembersLeave (Conv.QualifiedUserIdList (sort m)) sorted x = x + +generateVerificationCode :: (MonadCatch m, MonadIO m, MonadHttp m, HasCallStack) => Brig -> Public.SendVerificationCode -> m () +generateVerificationCode brig req = do + let js = RequestBodyLBS $ encode req + post (brig . paths ["verification-code", "send"] . contentJson . body js) !!! const 200 === statusCode + +setTeamSndFactorPasswordChallenge :: (MonadCatch m, MonadIO m, MonadHttp m, HasCallStack) => Galley -> TeamId -> Public.TeamFeatureStatusValue -> m () +setTeamSndFactorPasswordChallenge galley tid status = do + let js = RequestBodyLBS $ encode $ Public.TeamFeatureStatusNoConfig status + put (galley . paths ["i", "teams", toByteString' tid, "features", toByteString' Public.TeamFeatureSndFactorPasswordChallenge] . contentJson . body js) !!! const 200 === statusCode + +setTeamFeatureLockStatus :: + forall (a :: Public.TeamFeatureName) m. + ( MonadCatch m, + MonadIO m, + MonadHttp m, + HasCallStack, + Public.KnownTeamFeatureName a + ) => + Galley -> + TeamId -> + Public.LockStatusValue -> + m () +setTeamFeatureLockStatus galley tid status = + put (galley . paths ["i", "teams", toByteString' tid, "features", toByteString' (Public.knownTeamFeatureName @a), toByteString' status]) !!! const 200 === statusCode + +lookupCode :: MonadIO m => DB.ClientState -> Code.Key -> Code.Scope -> m (Maybe Code.Code) +lookupCode db k = liftIO . DB.runClient db . Code.lookup k diff --git a/services/brig/test/integration/API/UserPendingActivation.hs b/services/brig/test/integration/API/UserPendingActivation.hs index 3c7565d8b1d..4e3e02b60be 100644 --- a/services/brig/test/integration/API/UserPendingActivation.hs +++ b/services/brig/test/integration/API/UserPendingActivation.hs @@ -64,6 +64,7 @@ import qualified Web.Scim.Schema.Meta as Scim import qualified Web.Scim.Schema.User as Scim.User import qualified Web.Scim.Schema.User.Email as Email import qualified Web.Scim.Schema.User.Phone as Phone +import Wire.API.Team (Icon (..)) import Wire.API.User.RichInfo (RichInfo) import Wire.API.User.Scim (CreateScimToken (..), ScimToken, ScimUserExtra (ScimUserExtra)) @@ -151,7 +152,7 @@ getInvitationByEmail brig email = ) newTeam :: Galley.BindingNewTeam -newTeam = Galley.BindingNewTeam $ Galley.newNewTeam (unsafeRange "teamName") (unsafeRange "defaultIcon") +newTeam = Galley.BindingNewTeam $ Galley.newNewTeam (unsafeRange "teamName") DefaultIcon createUserWithTeamDisableSSO :: (HasCallStack, MonadCatch m, MonadHttp m, MonadIO m, MonadFail m) => Brig -> Galley -> m (UserId, TeamId) createUserWithTeamDisableSSO brg gly = do diff --git a/services/brig/test/integration/Federation/End2end.hs b/services/brig/test/integration/Federation/End2end.hs index a78439bb2d9..e5ed7391e4f 100644 --- a/services/brig/test/integration/Federation/End2end.hs +++ b/services/brig/test/integration/Federation/End2end.hs @@ -247,7 +247,18 @@ testAddRemoteUsersToLocalConv brig1 galley1 brig2 galley2 = do alice <- randomUser brig1 bob <- randomUser brig2 - let newConv = NewConv [] [] (Just "gossip") mempty Nothing Nothing Nothing Nothing roleNameWireAdmin + let newConv = + NewConv + [] + [] + (Just "gossip") + mempty + Nothing + Nothing + Nothing + Nothing + roleNameWireAdmin + ProtocolProteus convId <- fmap cnvQualifiedId . responseJsonError =<< post diff --git a/services/brig/test/integration/Util.hs b/services/brig/test/integration/Util.hs index c453caed3c8..8f6b318ceb1 100644 --- a/services/brig/test/integration/Util.hs +++ b/services/brig/test/integration/Util.hs @@ -44,6 +44,7 @@ import qualified Brig.AWS as AWS import Brig.AWS.Types import Brig.App (applog, sftEnv) import Brig.Calling as Calling +import qualified Brig.Code as Code import qualified Brig.Options as Opt import qualified Brig.Options as Opts import qualified Brig.Run as Run @@ -644,12 +645,16 @@ addClientReq brig uid new = . body (RequestBodyLBS $ encode new) defNewClient :: ClientType -> [Prekey] -> LastPrekey -> NewClient -defNewClient ty pks lpk = +defNewClient = defNewClientWithVerificationCode Nothing + +defNewClientWithVerificationCode :: Maybe Code.Value -> ClientType -> [Prekey] -> LastPrekey -> NewClient +defNewClientWithVerificationCode mbCode ty pks lpk = (newClient ty lpk) { newClientPassword = Just defPassword, newClientPrekeys = pks, newClientLabel = Just "Test Device", - newClientModel = Just "Test Model" + newClientModel = Just "Test Model", + newClientVerificationCode = mbCode } getPreKey :: Brig -> UserId -> UserId -> ClientId -> Http ResponseLBS @@ -690,7 +695,18 @@ getConversationQualified galley usr cnv = createConversation :: (MonadIO m, MonadHttp m) => Galley -> UserId -> [Qualified UserId] -> m ResponseLBS createConversation galley zusr usersToAdd = do - let conv = NewConv [] usersToAdd (Just "gossip") mempty Nothing Nothing Nothing Nothing roleNameWireAdmin + let conv = + NewConv + [] + usersToAdd + (Just "gossip") + mempty + Nothing + Nothing + Nothing + Nothing + roleNameWireAdmin + ProtocolProteus post $ galley . path "/conversations" diff --git a/services/federator/test/integration/Test/Federator/InwardSpec.hs b/services/federator/test/integration/Test/Federator/InwardSpec.hs index 4b2b8efc3f3..ed960962660 100644 --- a/services/federator/test/integration/Test/Federator/InwardSpec.hs +++ b/services/federator/test/integration/Test/Federator/InwardSpec.hs @@ -79,8 +79,8 @@ spec env = -- @SF.Federation @TSFI.RESTfulAPI @S2 @S3 @S7 -- - -- (This is tested in unit tests; search for - -- 'validateDomainCertInvalid' and 'testDiscoveryFailure'.) + -- This test is covered by the unit tests 'validateDomainCertWrongDomain' because + -- the domain matching is checked on certificate validation. it "shouldRejectMissmatchingOriginDomainInward" $ runTestFederator env $ pure () -- @END diff --git a/services/federator/test/unit/Test/Federator/Validation.hs b/services/federator/test/unit/Test/Federator/Validation.hs index 498a4079e8f..a01c399149e 100644 --- a/services/federator/test/unit/Test/Federator/Validation.hs +++ b/services/federator/test/unit/Test/Federator/Validation.hs @@ -163,6 +163,7 @@ validateDomainCertMissing = res @?= Left NoClientCertificate -- @SF.Federation @TSFI.Federate @TSFI.DNS @S2 @S3 @S7 +-- Reject request if the client certificate for federator is invalid validateDomainCertInvalid :: TestTree validateDomainCertInvalid = testCase "should fail if the client certificate is invalid" $ do diff --git a/services/galley/galley.cabal b/services/galley/galley.cabal index f2d8c758882..0e2d5b06cc0 100644 --- a/services/galley/galley.cabal +++ b/services/galley/galley.cabal @@ -178,6 +178,7 @@ library , base64-bytestring >=1.0 , bilge >=0.21.1 , binary + , blake2 , brig-types >=0.73.1 , bytestring >=0.9 , bytestring-conversion >=0.2 @@ -213,6 +214,7 @@ library , polysemy-wire-zoo , proto-lens >=0.2 , protobuf >=0.2 + , random , raw-strings-qq >=1.0 , resourcet >=1.1 , retry >=0.5 @@ -225,6 +227,7 @@ library , servant-server , servant-swagger , servant-swagger-ui + , singletons , sop-core , split >=0.2 , ssl-util >=0.1 @@ -445,6 +448,7 @@ executable galley-integration , servant-client-core , servant-server , servant-swagger + , singletons , sop-core , ssl-util , string-conversions @@ -600,6 +604,7 @@ executable galley-schema V58_ConversationAccessRoleV2 V59_FileSharingLockStatus V60_TeamFeatureSndFactorPasswordChallenge + V61_MLSConversation Paths_galley hs-source-dirs: schema/src diff --git a/services/galley/package.yaml b/services/galley/package.yaml index 211826d64d6..52e4242b946 100644 --- a/services/galley/package.yaml +++ b/services/galley/package.yaml @@ -35,6 +35,7 @@ library: - base64-bytestring >=1.0 - bilge >=0.21.1 - binary + - blake2 - brig-types >=0.73.1 - bytestring >=0.9 - bytestring-conversion >=0.2 @@ -69,6 +70,7 @@ library: - protobuf >=0.2 - proto-lens >=0.2 - QuickCheck >=2.14 + - random - resourcet >=1.1 - retry >=0.5 - safe-exceptions >=0.1 @@ -77,6 +79,7 @@ library: - servant-server - servant-swagger - servant-swagger-ui + - singletons - sop-core - split >=0.2 - ssl-util >=0.1 @@ -197,6 +200,7 @@ executables: - random - retry - schema-profunctor + - singletons - servant - servant-server - servant-swagger diff --git a/services/galley/schema/src/Main.hs b/services/galley/schema/src/Main.hs index ae17ad977e3..98db7b6c3cb 100644 --- a/services/galley/schema/src/Main.hs +++ b/services/galley/schema/src/Main.hs @@ -63,6 +63,7 @@ import qualified V57_GuestLinksLockStatus import qualified V58_ConversationAccessRoleV2 import qualified V59_FileSharingLockStatus import qualified V60_TeamFeatureSndFactorPasswordChallenge +import qualified V61_MLSConversation main :: IO () main = do @@ -111,7 +112,8 @@ main = do V57_GuestLinksLockStatus.migration, V58_ConversationAccessRoleV2.migration, V59_FileSharingLockStatus.migration, - V60_TeamFeatureSndFactorPasswordChallenge.migration + V60_TeamFeatureSndFactorPasswordChallenge.migration, + V61_MLSConversation.migration -- When adding migrations here, don't forget to update -- 'schemaVersion' in Galley.Cassandra -- (see also docs/developer/cassandra-interaction.md) diff --git a/services/galley/schema/src/V61_MLSConversation.hs b/services/galley/schema/src/V61_MLSConversation.hs new file mode 100644 index 00000000000..7d7c06af66a --- /dev/null +++ b/services/galley/schema/src/V61_MLSConversation.hs @@ -0,0 +1,42 @@ +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 Wire Swiss GmbH +-- +-- This program is free software: you can redistribute it and/or modify it under +-- the terms of the GNU Affero General Public License as published by the Free +-- Software Foundation, either version 3 of the License, or (at your option) any +-- later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Affero General Public License along +-- with this program. If not, see . + +module V61_MLSConversation + ( migration, + ) +where + +import Cassandra.Schema +import Imports +import Text.RawString.QQ + +migration :: Migration +migration = + Migration 61 "Add MLS fields to conversation and create a group ID to conversation ID mapping table" $ do + schema' + [r| ALTER TABLE conversation ADD ( + protocol int, + group_id blob + ) + |] + schema' + [r| CREATE TABLE group_id_conv_id ( + group_id blob PRIMARY KEY, + conv_id uuid, + domain text + ) + |] diff --git a/services/galley/src/Galley/API/Action.hs b/services/galley/src/Galley/API/Action.hs index 49945f3d77e..7e775867432 100644 --- a/services/galley/src/Galley/API/Action.hs +++ b/services/galley/src/Galley/API/Action.hs @@ -16,23 +16,22 @@ -- with this program. If not, see . module Galley.API.Action - ( -- * Conversation action class - IsConversationAction (..), - - -- * Conversation action types - ConversationDelete (..), + ( -- * Conversation action types + ConversationActionTag (..), ConversationJoin (..), - ConversationLeave (..), ConversationMemberUpdate (..), + HasConversationActionEffects, -- * Performing actions - updateLocalConversation, - NoChanges, + updateLocalConversationWithLocalUser, + updateLocalConversationWithRemoteUser, + NoChanges (..), -- * Utilities ensureConversationActionAllowed, addMembersToLocalConversation, notifyConversationAction, + ConversationUpdate, ) where @@ -41,11 +40,13 @@ import Control.Arrow import Control.Lens import Data.Id import Data.Kind -import Data.List.NonEmpty (NonEmpty, nonEmpty) +import Data.List.NonEmpty (nonEmpty) import qualified Data.Map as Map import Data.Misc import Data.Qualified +import qualified Data.Set as S import qualified Data.Set as Set +import Data.Singletons (Sing, SingI, demote, sing) import Data.Time.Clock import Galley.API.Error import Galley.API.Util @@ -74,7 +75,7 @@ import Wire.API.Conversation.Action import Wire.API.Conversation.Role import Wire.API.ErrorDescription import Wire.API.Event.Conversation hiding (Conversation) -import Wire.API.Federation.API +import Wire.API.Federation.API (Component (Galley), fedClient) import Wire.API.Federation.API.Galley import Wire.API.Federation.Error import Wire.API.Team.LegalHold @@ -82,374 +83,349 @@ import Wire.API.Team.Member data NoChanges = NoChanges +type family HasConversationActionEffects (tag :: ConversationActionTag) r :: Constraint where + HasConversationActionEffects 'ConversationJoinTag r = + Members + '[ BrigAccess, + Error ActionError, + Error ConversationError, + Error FederationError, + Error InvalidInput, + Error LegalHoldError, + Error NotATeamMember, + ExternalAccess, + FederatorAccess, + GundeckAccess, + Input Opts, + Input UTCTime, + LegalHoldStore, + MemberStore, + TeamStore, + ConversationStore, + Error NoChanges + ] + r + HasConversationActionEffects 'ConversationLeaveTag r = + (Members '[MemberStore, Error NoChanges] r) + HasConversationActionEffects 'ConversationRemoveMembersTag r = + (Members '[MemberStore, Error NoChanges] r) + HasConversationActionEffects 'ConversationMemberUpdateTag r = + (Members '[MemberStore, Error ConversationError] r) + HasConversationActionEffects 'ConversationDeleteTag r = + Members '[Error FederationError, Error NotATeamMember, CodeStore, TeamStore, ConversationStore] r + HasConversationActionEffects 'ConversationRenameTag r = + Members '[Error ActionError, Error InvalidInput, ConversationStore] r + HasConversationActionEffects 'ConversationAccessDataTag r = + Members + '[ BotAccess, + BrigAccess, + CodeStore, + Error ActionError, + Error InvalidInput, + Error InvalidInput, + Error NoChanges, + ExternalAccess, + FederatorAccess, + FireAndForget, + GundeckAccess, + MemberStore, + TeamStore, + Input UTCTime, + ConversationStore + ] + r + HasConversationActionEffects 'ConversationMessageTimerUpdateTag r = + Members '[ConversationStore, Error NoChanges] r + HasConversationActionEffects 'ConversationReceiptModeUpdateTag r = + Members '[ConversationStore, Error NoChanges] r + noChanges :: Member (Error NoChanges) r => Sem r a noChanges = throw NoChanges --- | An update to a conversation, including addition and removal of members. --- Used to send notifications to users and to remote backends. -class IsConversationAction a where - type HasConversationActionEffects a (r :: EffectRow) :: Constraint - - conversationAction :: a -> ConversationAction - ensureAllowed :: - (IsConvMember mem, HasConversationActionEffects a r) => - Local x -> - a -> - Conversation -> - mem -> - Sem r () - ensureAllowed _ _ _ _ = pure () - conversationActionTag' :: Qualified UserId -> a -> Action - performAction :: - ( HasConversationActionEffects a r, - Members '[ConversationStore, Error NoChanges] r - ) => - Qualified UserId -> - Local ConvId -> - Conversation -> - a -> - Sem r (BotsAndMembers, a) - --- | The action of some users joining a conversation. -data ConversationJoin = ConversationJoin - { cjUsers :: NonEmpty (Qualified UserId), - cjRole :: RoleName - } - --- | The action of some users leaving a conversation. -newtype ConversationLeave = ConversationLeave - {clUsers :: NonEmpty (Qualified UserId)} - --- | The action of promoting/demoting a member of a conversation. -data ConversationMemberUpdate = ConversationMemberUpdate - { cmuTarget :: Qualified UserId, - cmuUpdate :: OtherMemberUpdate - } - --- | The action of deleting a conversation. -data ConversationDelete = ConversationDelete - -instance IsConversationAction ConversationJoin where - type - HasConversationActionEffects ConversationJoin r = +ensureAllowed :: + forall tag mem r x. + (IsConvMember mem, SingI tag, HasConversationActionEffects tag r) => + Local x -> + ConversationAction tag -> + Conversation -> + mem -> + Sem r () +ensureAllowed loc action conv origUser = do + case (sing @tag) of + SConversationJoinTag -> ensureConvRoleNotElevated origUser (cjRole action) + SConversationDeleteTag -> + for_ (convTeam conv) $ \tid -> do + lusr <- ensureLocal loc (convMemberId loc origUser) + void $ E.getTeamMember tid (tUnqualified lusr) >>= noteED @NotATeamMember + SConversationAccessDataTag -> do + -- 'PrivateAccessRole' is for self-conversations, 1:1 conversations and + -- so on; users not supposed to be able to make other conversations + -- have 'PrivateAccessRole' + when (PrivateAccess `elem` cupAccess action || Set.null (cupAccessRoles action)) $ + throw InvalidTargetAccess + -- Team conversations incur another round of checks + case convTeam conv of + Just _ -> do + -- Access mode change might result in members being removed from the + -- conversation, so the user must have the necessary permission flag + ensureActionAllowed RemoveConversationMember origUser + Nothing -> + -- not a team conv, so one of the other access roles has to allow this. + when (Set.null $ cupAccessRoles action Set.\\ Set.fromList [TeamMemberAccessRole]) $ + throw InvalidTargetAccess + _ -> pure () + +-- | Returns additional members that resulted from the action (e.g. ConversationJoin) +-- and also returns the (possible modified) action that was performed +performAction :: + forall tag r. + ( HasConversationActionEffects tag r, + SingI tag + ) => + Qualified UserId -> + Local ConvId -> + Conversation -> + ConversationAction tag -> + Sem r (BotsAndMembers, ConversationAction tag) +performAction origUser lcnv cnv action = + case (sing @tag) of + SConversationJoinTag -> do + performConversationJoin origUser lcnv cnv action + SConversationLeaveTag -> do + let presentVictims = filter (isConvMember lcnv cnv) (toList action) + when (null presentVictims) noChanges + E.deleteMembers (convId cnv) (toUserList lcnv presentVictims) + pure (mempty, action) -- FUTUREWORK: should we return the filtered action here? + SConversationRemoveMembersTag -> do + let presentVictims = filter (isConvMember lcnv cnv) (toList action) + when (null presentVictims) noChanges + E.deleteMembers (convId cnv) (toUserList lcnv presentVictims) + pure (mempty, action) -- FUTUREWORK: should we return the filtered action here? + SConversationMemberUpdateTag -> do + void $ ensureOtherMember lcnv (cmuTarget action) cnv + E.setOtherMember lcnv (cmuTarget action) (cmuUpdate action) + pure (mempty, action) + SConversationDeleteTag -> do + key <- E.makeKey (tUnqualified lcnv) + E.deleteCode key ReusableCode + case convTeam cnv of + Nothing -> E.deleteConversation (tUnqualified lcnv) + Just tid -> E.deleteTeamConversation tid (tUnqualified lcnv) + pure (mempty, action) + SConversationRenameTag -> do + cn <- rangeChecked (cupName action) + E.setConversationName (tUnqualified lcnv) cn + pure (mempty, action) + SConversationMessageTimerUpdateTag -> do + when (convMessageTimer cnv == cupMessageTimer action) noChanges + E.setConversationMessageTimer (tUnqualified lcnv) (cupMessageTimer action) + pure (mempty, action) + SConversationReceiptModeUpdateTag -> do + when (convReceiptMode cnv == Just (cruReceiptMode action)) noChanges + E.setConversationReceiptMode (tUnqualified lcnv) (cruReceiptMode action) + pure (mempty, action) + SConversationAccessDataTag -> do + (bm, act) <- performConversationAccessData origUser lcnv cnv action + pure (bm, act) + +performConversationJoin :: + (HasConversationActionEffects 'ConversationJoinTag r) => + Qualified UserId -> + Local ConvId -> + Conversation -> + ConversationJoin -> + Sem r (BotsAndMembers, ConversationJoin) +performConversationJoin qusr lcnv conv (ConversationJoin invited role) = do + let newMembers = ulNewMembers lcnv conv . toUserList lcnv $ invited + + lusr <- ensureLocal lcnv qusr + ensureMemberLimit (toList (convLocalMembers conv)) newMembers + ensureAccess conv InviteAccess + checkLocals lusr (convTeam conv) (ulLocals newMembers) + checkRemotes lusr (ulRemotes newMembers) + checkLHPolicyConflictsLocal (ulLocals newMembers) + checkLHPolicyConflictsRemote (FutureWork (ulRemotes newMembers)) + + addMembersToLocalConversation lcnv newMembers role + where + checkLocals :: Members '[ BrigAccess, Error ActionError, Error ConversationError, - Error FederationError, - Error InvalidInput, - Error LegalHoldError, Error NotATeamMember, - ExternalAccess, - FederatorAccess, - GundeckAccess, - Input Opts, - Input UTCTime, - LegalHoldStore, - MemberStore, TeamStore ] - r - - conversationAction cj = ConversationActionAddMembers (cjUsers cj) (cjRole cj) - ensureAllowed _ cj _ self = ensureConvRoleNotElevated self (cjRole cj) - conversationActionTag' _ _ = AddConversationMember - performAction qusr lcnv conv (ConversationJoin invited role) = do - let newMembers = ulNewMembers lcnv conv . toUserList lcnv $ invited - - lusr <- ensureLocal lcnv qusr - ensureMemberLimit (toList (convLocalMembers conv)) newMembers - ensureAccess conv InviteAccess - checkLocals lusr (convTeam conv) (ulLocals newMembers) - checkRemotes lusr (ulRemotes newMembers) - checkLHPolicyConflictsLocal (ulLocals newMembers) - checkLHPolicyConflictsRemote (FutureWork (ulRemotes newMembers)) - - addMembersToLocalConversation lcnv newMembers role - where - checkLocals :: - Members - '[ BrigAccess, - Error ActionError, - Error ConversationError, - Error NotATeamMember, - TeamStore - ] - r => - Local UserId -> - Maybe TeamId -> - [UserId] -> - Sem r () - checkLocals lusr (Just tid) newUsers = do - tms <- - Map.fromList . map (view userId &&& id) - <$> E.selectTeamMembers tid newUsers - let userMembershipMap = map (id &&& flip Map.lookup tms) newUsers - ensureAccessRole (convAccessRoles conv) userMembershipMap - ensureConnectedOrSameTeam lusr newUsers - checkLocals lusr Nothing newUsers = do - ensureAccessRole (convAccessRoles conv) (zip newUsers $ repeat Nothing) - ensureConnectedOrSameTeam lusr newUsers - - checkRemotes :: - Members - '[ BrigAccess, - Error ActionError, - Error FederationError, - FederatorAccess - ] - r => - Local UserId -> - [Remote UserId] -> - Sem r () - checkRemotes lusr remotes = do - -- if federator is not configured, we fail early, so we avoid adding - -- remote members to the database - unless (null remotes) $ - unlessM E.isFederationConfigured $ - throw FederationNotConfigured - ensureConnectedToRemotes lusr remotes - - checkLHPolicyConflictsLocal :: - Members - '[ ConversationStore, - Error ActionError, - Error ConversationError, - Error LegalHoldError, - Error InvalidInput, - ExternalAccess, - FederatorAccess, - GundeckAccess, - Input Opts, - Input UTCTime, - LegalHoldStore, - MemberStore, - TeamStore - ] - r => - [UserId] -> - Sem r () - checkLHPolicyConflictsLocal newUsers = do - let convUsers = convLocalMembers conv - - allNewUsersGaveConsent <- allLegalholdConsentGiven newUsers - - whenM (anyLegalholdActivated (lmId <$> convUsers)) $ - unless allNewUsersGaveConsent $ - throw MissingLegalholdConsent - - whenM (anyLegalholdActivated newUsers) $ do - unless allNewUsersGaveConsent $ - throw MissingLegalholdConsent - - convUsersLHStatus <- do - uidsStatus <- getLHStatusForUsers (lmId <$> convUsers) - pure $ zipWith (\mem (_, status) -> (mem, status)) convUsers uidsStatus - - if any - ( \(mem, status) -> - lmConvRoleName mem == roleNameWireAdmin - && consentGiven status == ConsentGiven - ) - convUsersLHStatus - then do - for_ convUsersLHStatus $ \(mem, status) -> - when (consentGiven status == ConsentNotGiven) $ do - let qvictim = qUntagged (qualifyAs lcnv (lmId mem)) - void . runError @NoChanges $ - updateLocalConversation lcnv qvictim Nothing $ - ConversationLeave (pure qvictim) - else throw MissingLegalholdConsent - - checkLHPolicyConflictsRemote :: - FutureWork 'LegalholdPlusFederationNotImplemented [Remote UserId] -> - Sem r () - checkLHPolicyConflictsRemote _remotes = pure () - -instance IsConversationAction ConversationLeave where - type - HasConversationActionEffects ConversationLeave r = - (Members '[MemberStore] r) - conversationAction cl = ConversationActionRemoveMembers (clUsers cl) - conversationActionTag' qusr a - | pure qusr == clUsers a = LeaveConversation - | otherwise = RemoveConversationMember - performAction _qusr lcnv conv action = do - let presentVictims = filter (isConvMember lcnv conv) (toList (clUsers action)) - when (null presentVictims) noChanges - E.deleteMembers (convId conv) (toUserList lcnv presentVictims) - pure (mempty, action) -- FUTUREWORK: should we return the filtered action here? - -instance IsConversationAction ConversationMemberUpdate where - type - HasConversationActionEffects ConversationMemberUpdate r = - (Members '[MemberStore, Error ConversationError] r) - conversationAction cmu = ConversationActionMemberUpdate (cmuTarget cmu) (cmuUpdate cmu) - conversationActionTag' _ _ = ModifyOtherConversationMember - performAction _qusr lcnv conv action = do - void $ ensureOtherMember lcnv (cmuTarget action) conv - E.setOtherMember lcnv (cmuTarget action) (cmuUpdate action) - pure (mempty, action) - -instance IsConversationAction ConversationDelete where - type - HasConversationActionEffects ConversationDelete r = - Members '[Error FederationError, Error NotATeamMember, CodeStore, TeamStore] r - conversationAction ConversationDelete = ConversationActionDelete - ensureAllowed loc ConversationDelete conv self = - for_ (convTeam conv) $ \tid -> do - lusr <- ensureLocal loc (convMemberId loc self) - void $ E.getTeamMember tid (tUnqualified lusr) >>= noteED @NotATeamMember - conversationActionTag' _ _ = DeleteConversation - performAction _ lcnv conv action = do - key <- E.makeKey (tUnqualified lcnv) - E.deleteCode key ReusableCode - case convTeam conv of - Nothing -> E.deleteConversation (tUnqualified lcnv) - Just tid -> E.deleteTeamConversation tid (tUnqualified lcnv) - pure (mempty, action) - -instance IsConversationAction ConversationRename where - type - HasConversationActionEffects ConversationRename r = - Members '[Error ActionError, Error InvalidInput] r - - conversationAction = ConversationActionRename - conversationActionTag' _ _ = ModifyConversationName - performAction _ lcnv _ action = do - cn <- rangeChecked (cupName action) - E.setConversationName (tUnqualified lcnv) cn - pure (mempty, action) - -instance IsConversationAction ConversationMessageTimerUpdate where - type HasConversationActionEffects ConversationMessageTimerUpdate r = () - conversationAction = ConversationActionMessageTimerUpdate - conversationActionTag' _ _ = ModifyConversationMessageTimer - performAction _ lcnv conv action = do - when (convMessageTimer conv == cupMessageTimer action) noChanges - E.setConversationMessageTimer (tUnqualified lcnv) (cupMessageTimer action) - pure (mempty, action) - -instance IsConversationAction ConversationReceiptModeUpdate where - type HasConversationActionEffects ConversationReceiptModeUpdate r = () - conversationAction = ConversationActionReceiptModeUpdate - conversationActionTag' _ _ = ModifyConversationReceiptMode - performAction _ lcnv conv action = do - when (convReceiptMode conv == Just (cruReceiptMode action)) noChanges - E.setConversationReceiptMode (tUnqualified lcnv) (cruReceiptMode action) - pure (mempty, action) - -instance IsConversationAction ConversationAccessData where - type - HasConversationActionEffects ConversationAccessData r = + r => + Local UserId -> + Maybe TeamId -> + [UserId] -> + Sem r () + checkLocals lusr (Just tid) newUsers = do + tms <- + Map.fromList . map (view userId &&& id) + <$> E.selectTeamMembers tid newUsers + let userMembershipMap = map (id &&& flip Map.lookup tms) newUsers + ensureAccessRole (convAccessRoles conv) userMembershipMap + ensureConnectedOrSameTeam lusr newUsers + checkLocals lusr Nothing newUsers = do + ensureAccessRole (convAccessRoles conv) (zip newUsers $ repeat Nothing) + ensureConnectedOrSameTeam lusr newUsers + + checkRemotes :: Members - '[ BotAccess, - BrigAccess, - CodeStore, + '[ BrigAccess, + Error ActionError, + Error FederationError, + FederatorAccess + ] + r => + Local UserId -> + [Remote UserId] -> + Sem r () + checkRemotes lusr remotes = do + -- if federator is not configured, we fail early, so we avoid adding + -- remote members to the database + unless (null remotes) $ + unlessM E.isFederationConfigured $ + throw FederationNotConfigured + ensureConnectedToRemotes lusr remotes + + checkLHPolicyConflictsLocal :: + Members + '[ ConversationStore, Error ActionError, + Error ConversationError, + Error LegalHoldError, Error InvalidInput, ExternalAccess, FederatorAccess, - FireAndForget, GundeckAccess, + Input Opts, + Input UTCTime, + LegalHoldStore, MemberStore, - TeamStore, - Input UTCTime + TeamStore ] - r - conversationAction = ConversationActionAccessUpdate - ensureAllowed _ target conv self = do - -- 'PrivateAccessRole' is for self-conversations, 1:1 conversations and - -- so on; users are not supposed to be able to make other conversations - -- have 'PrivateAccessRole' - when (PrivateAccess `elem` cupAccess target || Set.null (cupAccessRoles target)) $ - throw InvalidTargetAccess - -- Team conversations incur another round of checks - case convTeam conv of - Just _ -> do - -- Access mode change might result in members being removed from the - -- conversation, so the user must have the necessary permission flag - ensureActionAllowed RemoveConversationMember self - Nothing -> - -- not a team conv, so one of the other access roles has to allow this. - when (Set.null $ cupAccessRoles target Set.\\ Set.fromList [TeamMemberAccessRole]) $ - throw InvalidTargetAccess - - conversationActionTag' _ _ = ModifyConversationAccess - performAction qusr lcnv conv action = do - when (convAccessData conv == action) noChanges - -- Remove conversation codes if CodeAccess is revoked - when - ( CodeAccess `elem` convAccess conv - && CodeAccess `notElem` cupAccess action - ) - $ do - key <- E.makeKey (tUnqualified lcnv) - E.deleteCode key ReusableCode - - -- Determine bots and members to be removed - let filterBotsAndMembers = - maybeRemoveBots >=> maybeRemoveGuests >=> maybeRemoveNonTeamMembers >=> maybeRemoveTeamMembers - let current = convBotsAndMembers conv -- initial bots and members - desired <- filterBotsAndMembers current -- desired bots and members - let toRemove = bmDiff current desired -- bots and members to be removed - - -- Update Cassandra - E.setConversationAccess (tUnqualified lcnv) action - E.fireAndForget $ do - -- Remove bots - traverse_ (E.deleteBot (tUnqualified lcnv) . botMemId) (bmBots toRemove) - - -- Update current bots and members - -- current bots and members but only desired bots - let bmToNotify = current {bmBots = bmBots desired} - - -- Remove users and notify everyone - void . for_ (nonEmpty (bmQualifiedMembers lcnv toRemove)) $ \usersToRemove -> do - let rAction = ConversationLeave usersToRemove - void . runError @NoChanges $ performAction qusr lcnv conv rAction - notifyConversationAction qusr Nothing lcnv bmToNotify (conversationAction rAction) - pure (mempty, action) - where - maybeRemoveBots :: Member BrigAccess r => BotsAndMembers -> Sem r BotsAndMembers - maybeRemoveBots bm = - if Set.member ServiceAccessRole (cupAccessRoles action) - then pure bm - else pure $ bm {bmBots = mempty} - - maybeRemoveGuests :: Member BrigAccess r => BotsAndMembers -> Sem r BotsAndMembers - maybeRemoveGuests bm = - if Set.member GuestAccessRole (cupAccessRoles action) - then pure bm - else do - activated <- map User.userId <$> E.lookupActivatedUsers (toList (bmLocals bm)) - -- FUTUREWORK: should we also remove non-activated remote users? - pure $ bm {bmLocals = Set.fromList activated} - - maybeRemoveNonTeamMembers :: Member TeamStore r => BotsAndMembers -> Sem r BotsAndMembers - maybeRemoveNonTeamMembers bm = - if Set.member NonTeamMemberAccessRole (cupAccessRoles action) - then pure bm - else case convTeam conv of - Just tid -> do - onlyTeamUsers <- filterM (fmap isJust . E.getTeamMember tid) (toList (bmLocals bm)) - pure $ bm {bmLocals = Set.fromList onlyTeamUsers, bmRemotes = mempty} - Nothing -> pure bm - - maybeRemoveTeamMembers :: Member TeamStore r => BotsAndMembers -> Sem r BotsAndMembers - maybeRemoveTeamMembers bm = - if Set.member TeamMemberAccessRole (cupAccessRoles action) - then pure bm - else case convTeam conv of - Just tid -> do - noTeamMembers <- filterM (fmap isNothing . E.getTeamMember tid) (toList (bmLocals bm)) - pure $ bm {bmLocals = Set.fromList noTeamMembers} - Nothing -> pure bm - --- | Update a local conversation, and notify all local and remote members. -updateLocalConversation :: - ( IsConversationAction a, - Members + r => + [UserId] -> + Sem r () + checkLHPolicyConflictsLocal newUsers = do + let convUsers = convLocalMembers conv + + allNewUsersGaveConsent <- allLegalholdConsentGiven newUsers + + whenM (anyLegalholdActivated (lmId <$> convUsers)) $ + unless allNewUsersGaveConsent $ + throw MissingLegalholdConsent + + whenM (anyLegalholdActivated newUsers) $ do + unless allNewUsersGaveConsent $ + throw MissingLegalholdConsent + + convUsersLHStatus <- do + uidsStatus <- getLHStatusForUsers (lmId <$> convUsers) + pure $ zipWith (\mem (_, status) -> (mem, status)) convUsers uidsStatus + + if any + ( \(mem, status) -> + lmConvRoleName mem == roleNameWireAdmin + && consentGiven status == ConsentGiven + ) + convUsersLHStatus + then do + for_ convUsersLHStatus $ \(mem, status) -> + when (consentGiven status == ConsentNotGiven) $ do + let lvictim = qualifyAs lcnv (lmId mem) + void . runError @NoChanges $ + updateLocalConversationWithLocalUser @'ConversationLeaveTag lcnv lvictim Nothing $ + pure (qUntagged lvictim) + else throw MissingLegalholdConsent + + checkLHPolicyConflictsRemote :: + FutureWork 'LegalholdPlusFederationNotImplemented [Remote UserId] -> + Sem r () + checkLHPolicyConflictsRemote _remotes = pure () + +performConversationAccessData :: + (HasConversationActionEffects 'ConversationAccessDataTag r) => + Qualified UserId -> + Local ConvId -> + Conversation -> + ConversationAccessData -> + Sem r (BotsAndMembers, ConversationAccessData) +performConversationAccessData qusr lcnv conv action = do + when (convAccessData conv == action) noChanges + -- Remove conversation codes if CodeAccess is revoked + when + ( CodeAccess `elem` convAccess conv + && CodeAccess `notElem` cupAccess action + ) + $ do + key <- E.makeKey (tUnqualified lcnv) + E.deleteCode key ReusableCode + + -- Determine bots and members to be removed + let filterBotsAndMembers = + maybeRemoveBots >=> maybeRemoveGuests >=> maybeRemoveNonTeamMembers >=> maybeRemoveTeamMembers + let current = convBotsAndMembers conv -- initial bots and members + desired <- filterBotsAndMembers current -- desired bots and members + let toRemove = bmDiff current desired -- bots and members to be removed + + -- Update Cassandra + E.setConversationAccess (tUnqualified lcnv) action + E.fireAndForget $ do + -- Remove bots + traverse_ (E.deleteBot (tUnqualified lcnv) . botMemId) (bmBots toRemove) + + -- Update current bots and members + -- current bots and members but only desired bots + let bmToNotify = current {bmBots = bmBots desired} + + -- Remove users and notify everyone + void . for_ (nonEmpty (bmQualifiedMembers lcnv toRemove)) $ \usersToRemove -> do + void . runError @NoChanges $ performAction @'ConversationLeaveTag qusr lcnv conv usersToRemove + notifyConversationAction (sing @'ConversationLeaveTag) qusr Nothing lcnv bmToNotify usersToRemove + pure (mempty, action) + where + maybeRemoveBots :: Member BrigAccess r => BotsAndMembers -> Sem r BotsAndMembers + maybeRemoveBots bm = + if Set.member ServiceAccessRole (cupAccessRoles action) + then pure bm + else pure $ bm {bmBots = mempty} + + maybeRemoveGuests :: Member BrigAccess r => BotsAndMembers -> Sem r BotsAndMembers + maybeRemoveGuests bm = + if Set.member GuestAccessRole (cupAccessRoles action) + then pure bm + else do + activated <- map User.userId <$> E.lookupActivatedUsers (toList (bmLocals bm)) + -- FUTUREWORK: should we also remove non-activated remote users? + pure $ bm {bmLocals = Set.fromList activated} + + maybeRemoveNonTeamMembers :: Member TeamStore r => BotsAndMembers -> Sem r BotsAndMembers + maybeRemoveNonTeamMembers bm = + if Set.member NonTeamMemberAccessRole (cupAccessRoles action) + then pure bm + else case convTeam conv of + Just tid -> do + onlyTeamUsers <- filterM (fmap isJust . E.getTeamMember tid) (toList (bmLocals bm)) + pure $ bm {bmLocals = Set.fromList onlyTeamUsers, bmRemotes = mempty} + Nothing -> pure bm + + maybeRemoveTeamMembers :: Member TeamStore r => BotsAndMembers -> Sem r BotsAndMembers + maybeRemoveTeamMembers bm = + if Set.member TeamMemberAccessRole (cupAccessRoles action) + then pure bm + else case convTeam conv of + Just tid -> do + noTeamMembers <- filterM (fmap isNothing . E.getTeamMember tid) (toList (bmLocals bm)) + pure $ bm {bmLocals = Set.fromList noTeamMembers} + Nothing -> pure bm + +updateLocalConversationWithLocalUser :: + forall tag r. + ( Members '[ ConversationStore, Error ActionError, Error ConversationError, @@ -461,54 +437,117 @@ updateLocalConversation :: Input UTCTime ] r, - HasConversationActionEffects a r + HasConversationActionEffects tag r, + SingI tag ) => Local ConvId -> - Qualified UserId -> + Local UserId -> Maybe ConnId -> - a -> + ConversationAction tag -> Sem r Event -updateLocalConversation lcnv qusr con action = do +updateLocalConversationWithLocalUser lcnv lusr con action = do -- retrieve conversation - (conv, self) <- getConversationAndMemberWithError ConvNotFound qusr lcnv + (conv, self) <- getConversationAndMemberWithError ConvNotFound lusr lcnv -- perform checks - ensureConversationActionAllowed lcnv action conv self + ensureConversationActionAllowed @tag lcnv action conv self -- perform action - (extraTargets, action') <- performAction qusr lcnv conv action + (extraTargets, action') <- performAction @tag (qUntagged lusr) lcnv conv action - -- send notifications to both local and remote users notifyConversationAction - qusr + (sing @tag) + (qUntagged lusr) con lcnv (convBotsAndMembers conv <> extraTargets) - (conversationAction action') + action' + +updateLocalConversationWithRemoteUser :: + forall tag r. + ( Members + '[ ConversationStore, + Error ActionError, + Error ConversationError, + Error InvalidInput, + Error NoChanges, + ExternalAccess, + FederatorAccess, + GundeckAccess, + Input UTCTime + ] + r, + HasConversationActionEffects tag r, + SingI tag + ) => + Sing tag -> + Local ConvId -> + Remote UserId -> + ConversationAction tag -> + Sem r ConversationUpdate +updateLocalConversationWithRemoteUser _tag lcnv rusr action = do + -- retrieve conversation + (conv, self) <- getConversationAndMemberWithError ConvNotFound (qUntagged rusr) lcnv + + -- perform checks + ensureConversationActionAllowed @tag lcnv action conv self + + -- perform action + (extraTargets, action') <- performAction @tag (qUntagged rusr) lcnv conv action + + -- filter out user from rusr's domain, because rusr's backend will update + -- local state and notify its users itself using the ConversationUpdate + -- returned by this function + let targets = convBotsAndMembers conv <> extraTargets + remotes = bmRemotes targets + remotesUserDomain = S.filter ((== tDomain rusr) . tDomain) remotes + remotesOtherDomain = remotes Set.\\ remotesUserDomain + + void $ + notifyConversationAction + (sing @tag) + (qUntagged rusr) + Nothing + lcnv + (targets {bmRemotes = remotesOtherDomain}) + action' --------------------------------------------------------------------------------- --- Utilities + now <- input + + pure $ + ConversationUpdate + { cuTime = now, + cuOrigUserId = qUntagged rusr, + cuConvId = tUnqualified lcnv, + cuAlreadyPresentUsers = tUnqualified <$> S.toList remotesUserDomain, + cuAction = SomeConversationAction @tag sing action' + } + +-- -------------------------------------------------------------------------------- +-- -- Utilities ensureConversationActionAllowed :: + forall tag mem x r. ( IsConvMember mem, - IsConversationAction a, - HasConversationActionEffects a r, - Members '[Error ActionError, Error InvalidInput] r + HasConversationActionEffects tag r, + Members '[Error ActionError, Error InvalidInput] r, + SingI tag ) => Local x -> - a -> + ConversationAction (tag :: ConversationActionTag) -> Conversation -> mem -> Sem r () ensureConversationActionAllowed loc action conv self = do - let tag = conversationActionTag' (convMemberId loc self) action -- general action check - ensureActionAllowed tag self + ensureActionAllowed (conversationActionPermission (demote @tag)) self + -- check if it is a group conversation (except for rename actions) - when (tag /= ModifyConversationName) $ + when (demote @tag /= ConversationRenameTag) $ ensureGroupConversation conv + -- extra action-specific checks - ensureAllowed loc action conv self + ensureAllowed @tag loc action conv self -- | Add users to a conversation without performing any checks. Return extra -- notification targets and the action performed. @@ -525,21 +564,23 @@ addMembersToLocalConversation lcnv users role = do pure (bmFromMembers lmems rmems, action) notifyConversationAction :: + forall tag r. + (SingI tag) => Members '[FederatorAccess, ExternalAccess, GundeckAccess, Input UTCTime] r => + Sing tag -> Qualified UserId -> Maybe ConnId -> Local ConvId -> BotsAndMembers -> - ConversationAction -> + ConversationAction (tag :: ConversationActionTag) -> Sem r Event -notifyConversationAction quid con lcnv targets action = do +notifyConversationAction tag quid con lcnv targets action = do now <- input - let e = conversationActionToEvent now quid (qUntagged lcnv) action + let e = conversationActionToEvent (sing @tag) now quid (qUntagged lcnv) action - -- notify remote participants E.runFederatedConcurrently_ (toList (bmRemotes targets)) $ \ruids -> fedClient @'Galley @"on-conversation-updated" $ - ConversationUpdate now quid (tUnqualified lcnv) (tUnqualified ruids) action + ConversationUpdate now quid (tUnqualified lcnv) (tUnqualified ruids) (SomeConversationAction tag action) -- notify local participants and bots pushConversationEvent con e (qualifyAs lcnv (bmLocals targets)) (bmBots targets) $> e diff --git a/services/galley/src/Galley/API/Create.hs b/services/galley/src/Galley/API/Create.hs index 75a2c5f2a71..de9d2be71f5 100644 --- a/services/galley/src/Galley/API/Create.hs +++ b/services/galley/src/Galley/API/Create.hs @@ -73,8 +73,6 @@ import Wire.API.Team.LegalHold (LegalholdProtectee (LegalholdPlusFederationNotIm -- Group conversations -- | The public-facing endpoint for creating group conversations. --- --- See Note [managed conversations]. createGroupConversation :: Members '[ ConversationStore, @@ -98,27 +96,33 @@ createGroupConversation :: ConnId -> NewConv -> Sem r ConversationResponse -createGroupConversation lusr conn body = do - let tinfo = newConvTeam body - allUsers = newConvMembers lusr body - name <- rangeCheckedMaybe (newConvName body) +createGroupConversation lusr conn newConv = do + let tinfo = newConvTeam newConv + allUsers = newConvMembers lusr newConv + name <- rangeCheckedMaybe (newConvName newConv) o <- input - checkedUsers <- checkedConvSize o allUsers - checkCreateConvPermissions lusr body tinfo allUsers + checkedUsers <- case newConvProtocol newConv of + ProtocolProteus -> checkedConvSize o allUsers + ProtocolMLS -> do + unless (null allUsers) $ throw MLSNonEmptyMemberList + pure mempty + checkCreateConvPermissions lusr newConv tinfo allUsers ensureNoLegalholdConflicts (ulRemotes allUsers) (ulLocals allUsers) conv <- E.createConversation + lusr NewConversation { ncType = RegularConv, ncCreator = tUnqualified lusr, - ncAccess = access body, - ncAccessRoles = accessRoles body, + ncAccess = access newConv, + ncAccessRoles = accessRoles newConv, ncName = name, - ncTeam = fmap cnvTeamId (newConvTeam body), - ncMessageTimer = newConvMessageTimer body, - ncReceiptMode = newConvReceiptMode body, + ncTeam = fmap cnvTeamId (newConvTeam newConv), + ncMessageTimer = newConvMessageTimer newConv, + ncReceiptMode = newConvReceiptMode newConv, ncUsers = checkedUsers, - ncRole = newConvUsersRole body + ncRole = newConvUsersRole newConv, + ncProtocol = newConvProtocol newConv } now <- input -- NOTE: We only send (conversation) events to members of the conversation diff --git a/services/galley/src/Galley/API/Error.hs b/services/galley/src/Galley/API/Error.hs index 8f8b635f22c..e8373ba4413 100644 --- a/services/galley/src/Galley/API/Error.hs +++ b/services/galley/src/Galley/API/Error.hs @@ -109,9 +109,13 @@ instance APIError InvalidInput where data AuthenticationError = ReAuthFailed + | VerificationCodeAuthFailed + | VerificationCodeRequired instance APIError AuthenticationError where toWai ReAuthFailed = reAuthFailed + toWai VerificationCodeAuthFailed = verificationCodeAuthFailed + toWai VerificationCodeRequired = verificationCodeRequired data ConversationError = ConvAccessDenied @@ -120,6 +124,7 @@ data ConversationError | ConvMemberNotFound | NoBindingTeamMembers | GuestLinksDisabled + | MLSNonEmptyMemberList instance APIError ConversationError where toWai ConvAccessDenied = errorDescriptionTypeToWai @ConvAccessDenied @@ -128,6 +133,7 @@ instance APIError ConversationError where toWai ConvMemberNotFound = errorDescriptionTypeToWai @ConvMemberNotFound toWai NoBindingTeamMembers = noBindingTeamMembers toWai GuestLinksDisabled = guestLinksDisabled + toWai MLSNonEmptyMemberList = errorDescriptionTypeToWai @MLSNonEmptyMemberList data TeamError = NoBindingTeam @@ -338,6 +344,12 @@ accessDenied = mkError status403 "access-denied" "You do not have permission to reAuthFailed :: Error reAuthFailed = mkError status403 "access-denied" "This operation requires reauthentication" +verificationCodeRequired :: Error +verificationCodeRequired = mkError status403 "code-authentication-required" "Verification code required." + +verificationCodeAuthFailed :: Error +verificationCodeAuthFailed = mkError status403 "code-authentication-failed" "Code authentication failed." + invalidUUID4 :: Error invalidUUID4 = mkError status400 "client-error" "Invalid UUID v4 format" diff --git a/services/galley/src/Galley/API/Federation.hs b/services/galley/src/Galley/API/Federation.hs index 66dcc5f776b..4278c7d5024 100644 --- a/services/galley/src/Galley/API/Federation.hs +++ b/services/galley/src/Galley/API/Federation.hs @@ -30,6 +30,7 @@ import Data.Map.Lens (toMapOf) import Data.Qualified import Data.Range (Range (fromRange)) import qualified Data.Set as Set +import Data.Singletons (sing) import qualified Data.Text.Lazy as LT import Data.Time.Clock import Galley.API.Action @@ -55,9 +56,9 @@ import qualified Polysemy.TinyLog as P import Servant (ServerT) import Servant.API import qualified System.Logger.Class as Log +import Wire.API.Conversation import qualified Wire.API.Conversation as Public import Wire.API.Conversation.Action -import Wire.API.Conversation.Member (OtherMember (..)) import qualified Wire.API.Conversation.Role as Public import Wire.API.Event.Conversation import Wire.API.Federation.API @@ -81,7 +82,15 @@ federationSitemap = :<|> Named @"on-user-deleted-conversations" onUserDeleted onConversationCreated :: - Members '[BrigAccess, GundeckAccess, ExternalAccess, Input (Local ()), MemberStore, P.TinyLog] r => + Members + '[ BrigAccess, + GundeckAccess, + ExternalAccess, + Input (Local ()), + MemberStore, + P.TinyLog + ] + r => Domain -> F.NewRemoteConversation ConvId -> Sem r () @@ -166,26 +175,34 @@ onConversationUpdated requestingDomain cu = do -- are not in the conversations are being removed or have their membership state -- updated, we do **not** add them to the list of targets, because we have no -- way to make sure that they are actually supposed to receive that notification. - (mActualAction, extraTargets) <- case F.cuAction cu of - ConversationActionAddMembers toAdd role -> do - let (localUsers, remoteUsers) = partitionQualified loc toAdd - addedLocalUsers <- Set.toList <$> addLocalUsersToRemoteConv rconvId (F.cuOrigUserId cu) localUsers - let allAddedUsers = map (qUntagged . qualifyAs loc) addedLocalUsers <> map qUntagged remoteUsers - case allAddedUsers of - [] -> pure (Nothing, []) -- If no users get added, its like no action was performed. - (u : us) -> pure (Just $ ConversationActionAddMembers (u :| us) role, addedLocalUsers) - ConversationActionRemoveMembers toRemove -> do - let localUsers = getLocalUsers (tDomain loc) toRemove - E.deleteMembersInRemoteConversation rconvId localUsers - pure (Just $ F.cuAction cu, []) - ConversationActionRename _ -> pure (Just $ F.cuAction cu, []) - ConversationActionMessageTimerUpdate _ -> pure (Just $ F.cuAction cu, []) - ConversationActionMemberUpdate _ _ -> pure (Just $ F.cuAction cu, []) - ConversationActionReceiptModeUpdate _ -> pure (Just $ F.cuAction cu, []) - ConversationActionAccessUpdate _ -> pure (Just $ F.cuAction cu, []) - ConversationActionDelete -> do - E.deleteMembersInRemoteConversation rconvId presentUsers - pure (Just $ F.cuAction cu, []) + + (mActualAction :: Maybe SomeConversationAction, extraTargets :: [UserId]) <- case F.cuAction cu of + sca@(SomeConversationAction singTag action) -> case singTag of + SConversationJoinTag -> do + let ConversationJoin toAdd role = action + let (localUsers, remoteUsers) = partitionQualified loc toAdd + addedLocalUsers <- Set.toList <$> addLocalUsersToRemoteConv rconvId (F.cuOrigUserId cu) localUsers + let allAddedUsers = map (qUntagged . qualifyAs loc) addedLocalUsers <> map qUntagged remoteUsers + case allAddedUsers of + [] -> pure (Nothing, []) -- If no users get added, its like no action was performed. + (u : us) -> pure (Just (SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (u :| us) role)), addedLocalUsers) + SConversationLeaveTag -> do + let localUsers = getLocalUsers (tDomain loc) action + E.deleteMembersInRemoteConversation rconvId localUsers + pure (Just sca, []) + SConversationRemoveMembersTag -> do + let localUsers = getLocalUsers (tDomain loc) action + E.deleteMembersInRemoteConversation rconvId localUsers + pure (Just sca, []) + SConversationMemberUpdateTag -> + pure (Just sca, []) + SConversationDeleteTag -> do + E.deleteMembersInRemoteConversation rconvId presentUsers + pure (Just sca, []) + SConversationRenameTag -> pure (Just sca, []) + SConversationMessageTimerUpdateTag -> pure (Just sca, []) + SConversationReceiptModeUpdateTag -> pure (Just sca, []) + SConversationAccessDataTag -> pure (Just sca, []) unless allUsersArePresent $ P.warn $ @@ -198,10 +215,9 @@ onConversationUpdated requestingDomain cu = do ) -- Send notifications - for_ mActualAction $ \action -> do - let event = conversationActionToEvent (F.cuTime cu) (F.cuOrigUserId cu) qconvId action + for_ mActualAction $ \(SomeConversationAction tag action) -> do + let event = conversationActionToEvent tag (F.cuTime cu) (F.cuOrigUserId cu) qconvId action targets = nubOrd $ presentUsers <> extraTargets - -- FUTUREWORK: support bots? pushConversationEvent Nothing event (qualifyAs loc targets) [] @@ -247,18 +263,29 @@ leaveConversation :: F.LeaveConversationRequest -> Sem r F.LeaveConversationResponse leaveConversation requestingDomain lc = do - let leaver = Qualified (F.lcLeaver lc) requestingDomain + let leaver :: Remote UserId = qTagUnsafe $ Qualified (F.lcLeaver lc) requestingDomain lcnv <- qualifyLocal (F.lcConvId lc) - fmap F.LeaveConversationResponse - . runError - . mapError handleNoChanges - . mapError handleConvError - . mapError handleActionError - . void - . updateLocalConversation lcnv leaver Nothing - . ConversationLeave - . pure - $ leaver + + res <- + runError + . mapError handleNoChanges + . mapError handleConvError + . mapError handleActionError + $ do + (conv, _self) <- getConversationAndMemberWithError ConvNotFound (qUntagged leaver) lcnv + update <- updateLocalConversationWithRemoteUser (sing @'ConversationLeaveTag) lcnv leaver (pure (qUntagged leaver)) + pure (update, conv) + + case res of + Left err -> pure $ F.LeaveConversationResponse (Left err) + Right (_update, conv) -> do + let action = pure (qUntagged leaver) + + let remotes = filter ((== tDomain leaver) . tDomain) (rmId <$> Data.convRemoteMembers conv) + let botsAndMembers = BotsAndMembers mempty (Set.fromList remotes) mempty + _event <- notifyConversationAction (sing @'ConversationLeaveTag) (qUntagged leaver) Nothing lcnv botsAndMembers action + + pure $ F.LeaveConversationResponse (Right ()) where handleConvError :: ConversationError -> F.RemoveFromConversationError handleConvError _ = F.RemoveFromConversationErrorNotFound @@ -389,8 +416,7 @@ onUserDeleted origDomain udcn = do -- The self conv cannot be on a remote backend. Public.SelfConv -> pure () Public.RegularConv -> do - let action = ConversationActionRemoveMembers (pure untaggedDeletedUser) - + let action = pure untaggedDeletedUser botsAndMembers = convBotsAndMembers conv - void $ notifyConversationAction untaggedDeletedUser Nothing lc botsAndMembers action + void $ notifyConversationAction (sing @'ConversationLeaveTag) untaggedDeletedUser Nothing lc botsAndMembers action pure EmptyResponse diff --git a/services/galley/src/Galley/API/Internal.hs b/services/galley/src/Galley/API/Internal.hs index a40c161c437..e5bbeb7ec80 100644 --- a/services/galley/src/Galley/API/Internal.hs +++ b/services/galley/src/Galley/API/Internal.hs @@ -26,11 +26,11 @@ where import Control.Exception.Safe (catchAny) import Control.Lens hiding ((.=)) -import Data.Data (Proxy (Proxy)) import Data.Id as Id import Data.List1 (maybeList1) import Data.Qualified import Data.Range +import Data.Singletons import Data.String.Conversions (cs) import Data.Time import GHC.TypeLits (AppendSymbol) @@ -89,8 +89,8 @@ import qualified Servant.API as Servant import Servant.Server import System.Logger.Class hiding (Path, name) import qualified System.Logger.Class as Log -import Wire.API.Conversation (ConvIdsPage, pattern GetPaginatedConversationIds) -import Wire.API.Conversation.Action (ConversationAction (ConversationActionRemoveMembers)) +import Wire.API.Conversation +import Wire.API.Conversation.Action import Wire.API.ErrorDescription import Wire.API.Federation.API import Wire.API.Federation.API.Galley @@ -555,7 +555,7 @@ rmUser lusr conn = do cuOrigUserId = qUser, cuConvId = cid, cuAlreadyPresentUsers = tUnqualified remotes, - cuAction = ConversationActionRemoveMembers (pure qUser) + cuAction = SomeConversationAction (sing @'ConversationLeaveTag) (pure qUser) } let rpc = fedClient @'Galley @"on-conversation-updated" convUpdate runFederatedEither remotes rpc diff --git a/services/galley/src/Galley/API/LegalHold.hs b/services/galley/src/Galley/API/LegalHold.hs index ed88f1cb34b..793aa191734 100644 --- a/services/galley/src/Galley/API/LegalHold.hs +++ b/services/galley/src/Galley/API/LegalHold.hs @@ -279,7 +279,7 @@ removeSettings zusr tid (Public.RemoveLegalHoldSettingsRequest mPassword) = do -- Log.field "targets" (toByteString . show $ toByteString <$> zothers) -- . Log.field "action" (Log.val "LegalHold.removeSettings") void $ permissionCheck ChangeLegalHoldTeamSettings zusrMembership - ensureReAuthorised zusr mPassword + ensureReAuthorised zusr mPassword Nothing Nothing removeSettings' @p tid where assertNotWhitelisting :: Sem r () @@ -625,7 +625,7 @@ approveDevice zusr tid luid connId (Public.ApproveLegalHoldForUserRequest mPassw . Log.field "action" (Log.val "LegalHold.approveDevice") unless (zusr == tUnqualified luid) $ throw AccessDenied assertOnTeam (tUnqualified luid) tid - ensureReAuthorised zusr mPassword + ensureReAuthorised zusr mPassword Nothing Nothing userLHStatus <- maybe defUserLegalHoldStatus (view legalHoldStatus) <$> getTeamMember tid (tUnqualified luid) assertUserLHPending userLHStatus @@ -733,7 +733,7 @@ disableForUser zusr tid luid (Public.DisableLegalHoldForUserRequest mPassword) = where disableLH :: UserLegalHoldStatus -> Sem r () disableLH userLHStatus = do - ensureReAuthorised zusr mPassword + ensureReAuthorised zusr mPassword Nothing Nothing removeLegalHoldClientFromUser (tUnqualified luid) LHService.removeLegalHold tid (tUnqualified luid) -- TODO: send event at this point (see also: related TODO in this module in diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index f8714c47a97..fb801f9ad6a 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -390,7 +390,7 @@ deleteTeam zusr zcon tid body = do checkPermissions team = do void $ permissionCheck DeleteTeam =<< E.getTeamMember tid zusr when ((tdTeam team) ^. teamBinding == Binding) $ do - ensureReAuthorised zusr (body ^. tdAuthPassword) + ensureReAuthorised zusr (body ^. tdAuthPassword) (body ^. tdVerificationCode) (Just U.DeleteTeam) -- This can be called by stern internalDeleteBindingTeamWithOneMember :: @@ -1027,7 +1027,7 @@ deleteTeamMember lusr zcon tid remove mBody = do if team ^. teamBinding == Binding && isJust targetMember then do body <- mBody & note (InvalidPayload "missing request body") - ensureReAuthorised (tUnqualified lusr) (body ^. tmdAuthPassword) + ensureReAuthorised (tUnqualified lusr) (body ^. tmdAuthPassword) Nothing Nothing (TeamSize sizeBeforeDelete) <- E.getSize tid -- TeamSize is 'Natural' and subtracting from 0 is an error -- TeamSize could be reported as 0 if team members are added and removed very quickly, diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index 85e358b34b2..a65af30af7b 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -75,6 +75,7 @@ import Data.List1 import qualified Data.Map.Strict as Map import Data.Qualified import qualified Data.Set as Set +import Data.Singletons import Data.Time import Galley.API.Action import Galley.API.Error @@ -328,9 +329,9 @@ updateLocalConversationAccess :: ConnId -> ConversationAccessData -> Sem r (UpdateResult Event) -updateLocalConversationAccess lcnv lusr con = +updateLocalConversationAccess lcnv lusr con update = getUpdateResult - . updateLocalConversation lcnv (qUntagged lusr) (Just con) + (updateLocalConversationWithLocalUser @'ConversationAccessDataTag lcnv lusr (Just con) update) updateRemoteConversationAccess :: Member (Error FederationError) r => @@ -343,14 +344,18 @@ updateRemoteConversationAccess _ _ _ _ = throw FederationNotImplemented updateConversationReceiptMode :: Members - '[ ConversationStore, + '[ BrigAccess, + ConversationStore, Error ActionError, Error ConversationError, Error InvalidInput, ExternalAccess, FederatorAccess, GundeckAccess, - Input UTCTime + Input (Local ()), + Input UTCTime, + MemberStore, + TinyLog ] r => Members '[Error FederationError] r => @@ -407,7 +412,7 @@ updateLocalConversationReceiptMode :: Sem r (UpdateResult Event) updateLocalConversationReceiptMode lcnv lusr con update = getUpdateResult $ - updateLocalConversation lcnv (qUntagged lusr) (Just con) update + updateLocalConversationWithLocalUser @'ConversationReceiptModeUpdateTag lcnv lusr (Just con) update updateRemoteConversationReceiptMode :: Member (Error FederationError) r => @@ -484,7 +489,7 @@ updateLocalConversationMessageTimer :: Sem r (UpdateResult Event) updateLocalConversationMessageTimer lusr con lcnv update = getUpdateResult $ - updateLocalConversation lcnv (qUntagged lusr) (Just con) update + updateLocalConversationWithLocalUser @'ConversationMessageTimerUpdateTag lcnv lusr (Just con) update deleteLocalConversation :: Members @@ -508,7 +513,7 @@ deleteLocalConversation :: Sem r (UpdateResult Event) deleteLocalConversation lusr con lcnv = getUpdateResult $ - updateLocalConversation lcnv (qUntagged lusr) (Just con) ConversationDelete + updateLocalConversationWithLocalUser @'ConversationDeleteTag lcnv lusr (Just con) () getUpdateResult :: Sem (Error NoChanges ': r) a -> Sem r (UpdateResult a) getUpdateResult = fmap (either (const Unchanged) Updated) . runError @@ -554,7 +559,7 @@ addCode :: addCode lusr zcon lcnv = do conv <- E.getConversation (tUnqualified lcnv) >>= note ConvNotFound Query.ensureGuestLinksEnabled (convTeam conv) - ensureConvMember (Data.convLocalMembers conv) (tUnqualified lusr) + ensureConvAdmin (Data.convLocalMembers conv) (tUnqualified lusr) ensureAccess conv CodeAccess ensureGuestsOrNonTeamMembersAllowed conv let (bots, users) = localBotsAndUsers $ Data.convLocalMembers conv @@ -616,7 +621,7 @@ rmCode :: rmCode lusr zcon lcnv = do conv <- E.getConversation (tUnqualified lcnv) >>= note ConvNotFound - ensureConvMember (Data.convLocalMembers conv) (tUnqualified lusr) + ensureConvAdmin (Data.convLocalMembers conv) (tUnqualified lusr) ensureAccess conv CodeAccess let (bots, users) = localBotsAndUsers $ Data.convLocalMembers conv key <- E.makeKey (tUnqualified lcnv) @@ -759,11 +764,12 @@ joinConversation lusr zcon conv access = do (extraTargets, action) <- addMembersToLocalConversation lcnv (UserList users []) roleNameWireMember notifyConversationAction + (sing @'ConversationJoinTag) (qUntagged lusr) (Just zcon) lcnv (convBotsAndMembers conv <> extraTargets) - (conversationAction action) + action addMembersUnqualified :: Members @@ -822,7 +828,7 @@ addMembers :: addMembers lusr zcon cnv (InviteQualified users role) = do let lcnv = qualifyAs lusr cnv getUpdateResult $ - updateLocalConversation lcnv (qUntagged lusr) (Just zcon) $ + updateLocalConversationWithLocalUser @'ConversationJoinTag lcnv lusr (Just zcon) $ ConversationJoin users role updateSelfMember :: @@ -963,7 +969,7 @@ updateOtherMemberLocalConv :: updateOtherMemberLocalConv lcnv lusr con qvictim update = void . getUpdateResult $ do when (qUntagged lusr == qvictim) $ throw InvalidTargetUserOp - updateLocalConversation lcnv (qUntagged lusr) (Just con) $ + updateLocalConversationWithLocalUser @'ConversationMemberUpdateTag lcnv lusr (Just con) $ ConversationMemberUpdate qvictim update updateOtherMemberRemoteConv :: @@ -1078,12 +1084,21 @@ removeMemberFromLocalConv :: Maybe ConnId -> Qualified UserId -> Sem r (Maybe Event) -removeMemberFromLocalConv lcnv lusr con = - fmap hush - . runError @NoChanges - . updateLocalConversation lcnv (qUntagged lusr) con - . ConversationLeave - . pure +removeMemberFromLocalConv lcnv lusr con victim + | qUntagged lusr == victim = + do + fmap hush + . runError @NoChanges + . updateLocalConversationWithLocalUser @'ConversationLeaveTag lcnv lusr con + . pure + $ victim + | otherwise = + do + fmap hush + . runError @NoChanges + . updateLocalConversationWithLocalUser @'ConversationRemoveMembersTag lcnv lusr con + . pure + $ victim -- OTR @@ -1344,7 +1359,7 @@ updateLiveLocalConversationName :: Sem r (Maybe Event) updateLiveLocalConversationName lusr con lcnv rename = fmap hush . runError @NoChanges $ - updateLocalConversation lcnv (qUntagged lusr) (Just con) rename + updateLocalConversationWithLocalUser @'ConversationRenameTag lcnv lusr (Just con) rename isTypingUnqualified :: Members @@ -1544,6 +1559,12 @@ rmBot lusr zcon b = do ------------------------------------------------------------------------------- -- Helpers +ensureConvAdmin :: Member (Error ConversationError) r => [LocalMember] -> UserId -> Sem r () +ensureConvAdmin users uid = + case find ((== uid) . lmId) users of + Nothing -> throw ConvNotFound + Just lm -> unless (lmConvRoleName lm == roleNameWireAdmin) $ throw ConvAccessDenied + ensureConvMember :: Member (Error ConversationError) r => [LocalMember] -> UserId -> Sem r () ensureConvMember users usr = unless (usr `isMember` users) $ throw ConvNotFound diff --git a/services/galley/src/Galley/API/Util.hs b/services/galley/src/Galley/API/Util.hs index e2871d3be75..b81229bd334 100644 --- a/services/galley/src/Galley/API/Util.hs +++ b/services/galley/src/Galley/API/Util.hs @@ -26,6 +26,7 @@ import Control.Arrow (Arrow (second), second) import Control.Lens (set, view, (.~), (^.)) import Control.Monad.Extra (allM, anyM) import Data.ByteString.Conversion +import qualified Data.Code as Code import Data.Domain (Domain) import Data.Id as Id import Data.LegalHold (UserLegalHoldStatus (..), defUserLegalHoldStatus) @@ -59,7 +60,7 @@ import Galley.Types.UserList import Imports hiding (forkIO) import Network.HTTP.Types import Network.Wai -import Network.Wai.Predicate hiding (Error) +import Network.Wai.Predicate hiding (Error, fromEither) import qualified Network.Wai.Utilities as Wai import Polysemy import Polysemy.Error @@ -69,6 +70,7 @@ import Wire.API.ErrorDescription import Wire.API.Federation.API import Wire.API.Federation.API.Galley import Wire.API.Federation.Error +import Wire.API.User (VerificationAction) import qualified Wire.API.User as User type JSON = Media "application" "json" @@ -153,11 +155,11 @@ ensureReAuthorised :: r => UserId -> Maybe PlainTextPassword -> + Maybe Code.Value -> + Maybe VerificationAction -> Sem r () -ensureReAuthorised u secret = do - reAuthed <- reauthUser u (ReAuthUser secret) - unless reAuthed $ - throw ReAuthFailed +ensureReAuthorised u secret mbAction mbCode = + reauthUser u (ReAuthUser secret mbAction mbCode) >>= fromEither -- | Given a member in a conversation, check if the given action -- is permitted. If the user does not have the given permission, throw @@ -715,7 +717,9 @@ fromNewRemoteConversation loc rc@NewRemoteConversation {..} = -- domain. cnvmTeam = Nothing, cnvmMessageTimer = rcMessageTimer, - cnvmReceiptMode = rcReceiptMode + cnvmReceiptMode = rcReceiptMode, + cnvmProtocol = ProtocolProteus, + cnvmGroupId = Nothing } (ConvMembers this others) diff --git a/services/galley/src/Galley/Cassandra/Conversation.hs b/services/galley/src/Galley/Cassandra/Conversation.hs index 99c5ff4fcc7..698a4b0937a 100644 --- a/services/galley/src/Galley/Cassandra/Conversation.hs +++ b/services/galley/src/Galley/Cassandra/Conversation.hs @@ -24,7 +24,9 @@ where import Cassandra hiding (Set) import qualified Cassandra as Cql +import qualified Crypto.Hash.BLAKE2.BLAKE2b as Blake2 import Data.ByteString.Conversion +import Data.Domain import Data.Id import qualified Data.Map as Map import Data.Misc @@ -48,21 +50,25 @@ import Polysemy import Polysemy.Input import Polysemy.TinyLog import qualified System.Logger as Log +import System.Random import qualified UnliftIO import Wire.API.Conversation hiding (Conversation, Member) import Wire.API.Conversation.Role (roleNameWireAdmin) -createConversation :: NewConversation -> Client Conversation -createConversation (NewConversation ty usr acc arole name mtid mtimer recpt users role) = do +createConversation :: Local x -> NewConversation -> Client Conversation +createConversation loc (NewConversation ty usr acc arole name mtid mtimer recpt users role protocol) = do conv <- Id <$> liftIO nextRandom - retry x5 $ case mtid of - Nothing -> - write Cql.insertConv (params LocalQuorum (conv, ty, usr, Cql.Set (toList acc), Cql.Set (toList arole), fmap fromRange name, Nothing, mtimer, recpt)) - Just tid -> batch $ do - setType BatchLogged - setConsistency LocalQuorum - addPrepQuery Cql.insertConv (conv, ty, usr, Cql.Set (toList acc), Cql.Set (toList arole), fmap fromRange name, Just tid, mtimer, recpt) - addPrepQuery Cql.insertTeamConv (tid, conv) + groupId <- case protocol of + ProtocolProteus -> pure Nothing + ProtocolMLS -> fmap Just . liftIO . toGroupId $ (conv, tDomain loc) + retry x5 . batch $ do + setType BatchLogged + setConsistency LocalQuorum + addPrepQuery + Cql.insertConv + (conv, ty, usr, Cql.Set (toList acc), Cql.Set (toList arole), fmap fromRange name, mtid, mtimer, recpt, protocol, groupId) + for_ mtid $ \tid -> addPrepQuery Cql.insertTeamConv (tid, conv) + for_ groupId $ \gid -> addPrepQuery Cql.insertGroupId (gid, conv, tDomain loc) let newUsers = fmap (,role) (fromConvSize users) (lmems, rmems) <- addMembers conv (ulAddLocal (usr, roleNameWireAdmin) newUsers) pure $ @@ -78,8 +84,20 @@ createConversation (NewConversation ty usr acc arole name mtid mtimer recpt user convTeam = mtid, convDeleted = Nothing, convMessageTimer = mtimer, - convReceiptMode = recpt + convReceiptMode = recpt, + convProtocol = Just protocol, + convGroupId = groupId } + where + toGroupId :: MonadIO m => (ConvId, Domain) -> m GroupId + toGroupId (cId, d) = do + g <- newStdGen + let (len, _) = genWord8 g + -- The length can be at most 256 bytes + pure + . GroupId + . Blake2.hash (fromIntegral len) mempty + $ toByteString' cId <> toByteString' d createConnectConversation :: U.UUID U.V4 -> @@ -90,7 +108,7 @@ createConnectConversation a b name = do let conv = localOne2OneConvId a b a' = Id . U.unpack $ a retry x5 $ - write Cql.insertConv (params LocalQuorum (conv, ConnectConv, a', privateOnly, Cql.Set [], fromRange <$> name, Nothing, Nothing, Nothing)) + write Cql.insertConv (params LocalQuorum (conv, ConnectConv, a', privateOnly, Cql.Set [], fromRange <$> name, Nothing, Nothing, Nothing, ProtocolProteus, Nothing)) -- We add only one member, second one gets added later, -- when the other user accepts the connection request. (lmems, rmems) <- addMembers conv (UserList [a'] []) @@ -107,7 +125,9 @@ createConnectConversation a b name = do convTeam = Nothing, convDeleted = Nothing, convMessageTimer = Nothing, - convReceiptMode = Nothing + convReceiptMode = Nothing, + convProtocol = Just ProtocolProteus, + convGroupId = Nothing } createConnectConversationWithRemote :: @@ -117,7 +137,7 @@ createConnectConversationWithRemote :: Client Conversation createConnectConversationWithRemote cid creator m = do retry x5 $ - write Cql.insertConv (params LocalQuorum (cid, ConnectConv, creator, privateOnly, Cql.Set [], Nothing, Nothing, Nothing, Nothing)) + write Cql.insertConv (params LocalQuorum (cid, ConnectConv, creator, privateOnly, Cql.Set [], Nothing, Nothing, Nothing, Nothing, ProtocolProteus, Nothing)) -- We add only one member, second one gets added later, -- when the other user accepts the connection request. (lmems, rmems) <- addMembers cid m @@ -134,7 +154,9 @@ createConnectConversationWithRemote cid creator m = do convTeam = Nothing, convDeleted = Nothing, convMessageTimer = Nothing, - convReceiptMode = Nothing + convReceiptMode = Nothing, + convProtocol = Just ProtocolProteus, + convGroupId = Nothing } createLegacyOne2OneConversation :: @@ -164,11 +186,11 @@ createOne2OneConversation :: Client Conversation createOne2OneConversation conv self other name mtid = do retry x5 $ case mtid of - Nothing -> write Cql.insertConv (params LocalQuorum (conv, One2OneConv, tUnqualified self, privateOnly, Cql.Set [], fromRange <$> name, Nothing, Nothing, Nothing)) + Nothing -> write Cql.insertConv (params LocalQuorum (conv, One2OneConv, tUnqualified self, privateOnly, Cql.Set [], fromRange <$> name, Nothing, Nothing, Nothing, ProtocolProteus, Nothing)) Just tid -> batch $ do setType BatchLogged setConsistency LocalQuorum - addPrepQuery Cql.insertConv (conv, One2OneConv, tUnqualified self, privateOnly, Cql.Set [], fromRange <$> name, Just tid, Nothing, Nothing) + addPrepQuery Cql.insertConv (conv, One2OneConv, tUnqualified self, privateOnly, Cql.Set [], fromRange <$> name, Just tid, Nothing, Nothing, ProtocolProteus, Nothing) addPrepQuery Cql.insertTeamConv (tid, conv) (lmems, rmems) <- addMembers conv (toUserList self [qUntagged self, other]) pure @@ -184,7 +206,9 @@ createOne2OneConversation conv self other name mtid = do convTeam = Nothing, convDeleted = Nothing, convMessageTimer = Nothing, - convReceiptMode = Nothing + convReceiptMode = Nothing, + convProtocol = Just ProtocolProteus, + convGroupId = Nothing } createSelfConversation :: Local UserId -> Maybe (Range 1 256 Text) -> Client Conversation @@ -193,7 +217,7 @@ createSelfConversation lusr name = do conv = selfConv usr lconv = qualifyAs lusr conv retry x5 $ - write Cql.insertConv (params LocalQuorum (conv, SelfConv, usr, privateOnly, Cql.Set [], fromRange <$> name, Nothing, Nothing, Nothing)) + write Cql.insertConv (params LocalQuorum (conv, SelfConv, usr, privateOnly, Cql.Set [], fromRange <$> name, Nothing, Nothing, Nothing, ProtocolProteus, Nothing)) (lmems, rmems) <- addMembers (tUnqualified lconv) (UserList [tUnqualified lusr] []) pure Conversation @@ -208,7 +232,9 @@ createSelfConversation lusr name = do convTeam = Nothing, convDeleted = Nothing, convMessageTimer = Nothing, - convReceiptMode = Nothing + convReceiptMode = Nothing, + convProtocol = Just ProtocolProteus, + convGroupId = Nothing } deleteConversation :: ConvId -> Client () @@ -228,10 +254,10 @@ conversationMeta conv = fmap toConvMeta <$> retry x1 (query1 Cql.selectConv (params LocalQuorum (Identity conv))) where - toConvMeta (t, c, a, r, r', n, i, _, mt, rm) = + toConvMeta (t, c, a, r, r', n, i, _, mt, rm, p, gid) = let mbAccessRolesV2 = Set.fromList . Cql.fromSet <$> r' accessRoles = maybeRole t $ parseAccessRoles r mbAccessRolesV2 - in ConversationMetadata t c (defAccess t a) accessRoles n i mt rm + in ConversationMetadata t c (defAccess t a) accessRoles n i mt rm (fromMaybe ProtocolProteus p) gid isConvAlive :: ConvId -> Client Bool isConvAlive cid = do @@ -304,8 +330,8 @@ localConversations ids = do let m = Map.fromList $ map - ( \(cId, cType, uId, access, aRolesFromLegacy, aRoles, name, tId, del, timer, rm) -> - (cId, (cType, uId, access, aRolesFromLegacy, aRoles, name, tId, del, timer, rm)) + ( \(cId, cType, uId, access, aRolesFromLegacy, aRoles, name, tId, del, timer, rm, p, gid) -> + (cId, (cType, uId, access, aRolesFromLegacy, aRoles, name, tId, del, timer, rm, p, gid)) ) cs return $ map (`Map.lookup` m) ids @@ -346,22 +372,44 @@ toConv :: ConvId -> [LocalMember] -> [RemoteMember] -> - Maybe (ConvType, UserId, Maybe (Cql.Set Access), Maybe AccessRoleLegacy, Maybe (Cql.Set AccessRoleV2), Maybe Text, Maybe TeamId, Maybe Bool, Maybe Milliseconds, Maybe ReceiptMode) -> + Maybe (ConvType, UserId, Maybe (Cql.Set Access), Maybe AccessRoleLegacy, Maybe (Cql.Set AccessRoleV2), Maybe Text, Maybe TeamId, Maybe Bool, Maybe Milliseconds, Maybe ReceiptMode, Maybe Protocol, Maybe GroupId) -> Maybe Conversation toConv cid mms remoteMems conv = f mms <$> conv where - f ms (cty, uid, acc, role, roleV2, nme, ti, del, timer, rm) = + f ms (cty, uid, acc, role, roleV2, nme, ti, del, timer, rm, prot, gid) = let mbAccessRolesV2 = Set.fromList . Cql.fromSet <$> roleV2 accessRoles = maybeRole cty $ parseAccessRoles role mbAccessRolesV2 - in Conversation cid cty uid nme (defAccess cty acc) accessRoles ms remoteMems ti del timer rm + in Conversation + cid + cty + uid + nme + (defAccess cty acc) + accessRoles + ms + remoteMems + ti + del + timer + rm + prot + gid + +mapGroupId :: GroupId -> Qualified ConvId -> Client () +mapGroupId gId conv = + write Cql.insertGroupId (params LocalQuorum (gId, qUnqualified conv, qDomain conv)) + +lookupGroupId :: GroupId -> Client (Maybe (Qualified ConvId)) +lookupGroupId gId = + uncurry Qualified <$$> retry x1 (query1 Cql.lookupGroupId (params LocalQuorum (Identity gId))) interpretConversationStoreToCassandra :: Members '[Embed IO, Input ClientState, TinyLog] r => Sem (ConversationStore ': r) a -> Sem r a interpretConversationStoreToCassandra = interpret $ \case - CreateConversation nc -> embedClient $ createConversation nc + CreateConversation loc nc -> embedClient $ createConversation loc nc CreateConnectConversation x y name -> embedClient $ createConnectConversation x y name CreateConnectConversationWithRemote cid lusr mems -> @@ -384,3 +432,5 @@ interpretConversationStoreToCassandra = interpret $ \case SetConversationReceiptMode cid value -> embedClient $ updateConvReceiptMode cid value SetConversationMessageTimer cid value -> embedClient $ updateConvMessageTimer cid value DeleteConversation cid -> embedClient $ deleteConversation cid + GetConversationIdByGroupId gId -> embedClient $ lookupGroupId gId + SetGroupId gId cid -> embedClient $ mapGroupId gId cid diff --git a/services/galley/src/Galley/Cassandra/Instances.hs b/services/galley/src/Galley/Cassandra/Instances.hs index 0e3ebaf5ec1..ab1a69e7b6d 100644 --- a/services/galley/src/Galley/Cassandra/Instances.hs +++ b/services/galley/src/Galley/Cassandra/Instances.hs @@ -25,13 +25,17 @@ where import Cassandra.CQL import Control.Error (note) +import Data.ByteString.Conversion +import qualified Data.ByteString.Lazy as LBS import Data.Domain (Domain, domainText, mkDomain) +import qualified Data.Text.Encoding as T import Galley.Types import Galley.Types.Bot () import Galley.Types.Teams import Galley.Types.Teams.Intra import Galley.Types.Teams.SearchVisibility import Imports +import Wire.API.Team import qualified Wire.API.Team.Feature as Public deriving instance Cql MutedStatus @@ -168,3 +172,29 @@ instance Cql Public.EnforceAppLock where 1 -> pure (Public.EnforceAppLock True) _ -> Left "fromCql EnforceAppLock: int out of range" fromCql _ = Left "fromCql EnforceAppLock: int expected" + +instance Cql Protocol where + ctype = Tagged IntColumn + + toCql ProtocolProteus = CqlInt 0 + toCql ProtocolMLS = CqlInt 1 + + fromCql (CqlInt i) = case i of + 0 -> return ProtocolProteus + 1 -> return ProtocolMLS + n -> Left $ "unexpected protocol: " ++ show n + fromCql _ = Left "protocol: int expected" + +instance Cql GroupId where + ctype = Tagged BlobColumn + + toCql = CqlBlob . LBS.fromStrict . unGroupId + + fromCql (CqlBlob b) = Right . GroupId . LBS.toStrict $ b + fromCql _ = Left "group_id: blob expected" + +instance Cql Icon where + ctype = Tagged TextColumn + toCql = CqlText . T.decodeUtf8 . toByteString' + fromCql (CqlText txt) = pure . fromRight DefaultIcon . runParser parser . T.encodeUtf8 $ txt + fromCql _ = Left "Icon: Text expected" diff --git a/services/galley/src/Galley/Cassandra/Queries.hs b/services/galley/src/Galley/Cassandra/Queries.hs index 4d199920a53..1d4db29ba6c 100644 --- a/services/galley/src/Galley/Cassandra/Queries.hs +++ b/services/galley/src/Galley/Cassandra/Queries.hs @@ -36,10 +36,11 @@ import Galley.Types.Teams.Intra import Galley.Types.Teams.SearchVisibility import Imports import Text.RawString.QQ +import Wire.API.Team -- Teams -------------------------------------------------------------------- -selectTeam :: PrepQuery R (Identity TeamId) (UserId, Text, Text, Maybe Text, Bool, Maybe TeamStatus, Maybe (Writetime TeamStatus), Maybe TeamBinding) +selectTeam :: PrepQuery R (Identity TeamId) (UserId, Text, Icon, Maybe Text, Bool, Maybe TeamStatus, Maybe (Writetime TeamStatus), Maybe TeamBinding) selectTeam = "select creator, name, icon, icon_key, deleted, status, writetime(status), binding from team where team = ?" selectTeamName :: PrepQuery R (Identity TeamId) (Identity Text) @@ -135,7 +136,7 @@ selectUserTeamsIn = "select team from user_team where user = ? and team in ? ord selectUserTeamsFrom :: PrepQuery R (UserId, TeamId) (Identity TeamId) selectUserTeamsFrom = "select team from user_team where user = ? and team > ? order by team" -insertTeam :: PrepQuery W (TeamId, UserId, Text, Text, Maybe Text, TeamStatus, TeamBinding) () +insertTeam :: PrepQuery W (TeamId, UserId, Text, Icon, Maybe Text, TeamStatus, TeamBinding) () insertTeam = "insert into team (team, creator, name, icon, icon_key, deleted, status, binding) values (?, ?, ?, ?, ?, false, ?, ?)" insertTeamConv :: PrepQuery W (TeamId, ConvId) () @@ -188,11 +189,11 @@ updateTeamStatus = "update team set status = ? where team = ?" -- Conversations ------------------------------------------------------------ -selectConv :: PrepQuery R (Identity ConvId) (ConvType, UserId, Maybe (C.Set Access), Maybe AccessRoleLegacy, Maybe (C.Set AccessRoleV2), Maybe Text, Maybe TeamId, Maybe Bool, Maybe Milliseconds, Maybe ReceiptMode) -selectConv = "select type, creator, access, access_role, access_roles_v2, name, team, deleted, message_timer, receipt_mode from conversation where conv = ?" +selectConv :: PrepQuery R (Identity ConvId) (ConvType, UserId, Maybe (C.Set Access), Maybe AccessRoleLegacy, Maybe (C.Set AccessRoleV2), Maybe Text, Maybe TeamId, Maybe Bool, Maybe Milliseconds, Maybe ReceiptMode, Maybe Protocol, Maybe GroupId) +selectConv = "select type, creator, access, access_role, access_roles_v2, name, team, deleted, message_timer, receipt_mode, protocol, group_id from conversation where conv = ?" -selectConvs :: PrepQuery R (Identity [ConvId]) (ConvId, ConvType, UserId, Maybe (C.Set Access), Maybe AccessRoleLegacy, Maybe (C.Set AccessRoleV2), Maybe Text, Maybe TeamId, Maybe Bool, Maybe Milliseconds, Maybe ReceiptMode) -selectConvs = "select conv, type, creator, access, access_role, access_roles_v2, name, team, deleted, message_timer, receipt_mode from conversation where conv in ?" +selectConvs :: PrepQuery R (Identity [ConvId]) (ConvId, ConvType, UserId, Maybe (C.Set Access), Maybe AccessRoleLegacy, Maybe (C.Set AccessRoleV2), Maybe Text, Maybe TeamId, Maybe Bool, Maybe Milliseconds, Maybe ReceiptMode, Maybe Protocol, Maybe GroupId) +selectConvs = "select conv, type, creator, access, access_role, access_roles_v2, name, team, deleted, message_timer, receipt_mode, protocol, group_id from conversation where conv in ?" selectReceiptMode :: PrepQuery R (Identity ConvId) (Identity (Maybe ReceiptMode)) selectReceiptMode = "select receipt_mode from conversation where conv = ?" @@ -200,8 +201,8 @@ selectReceiptMode = "select receipt_mode from conversation where conv = ?" isConvDeleted :: PrepQuery R (Identity ConvId) (Identity (Maybe Bool)) isConvDeleted = "select deleted from conversation where conv = ?" -insertConv :: PrepQuery W (ConvId, ConvType, UserId, C.Set Access, C.Set AccessRoleV2, Maybe Text, Maybe TeamId, Maybe Milliseconds, Maybe ReceiptMode) () -insertConv = "insert into conversation (conv, type, creator, access, access_roles_v2, name, team, message_timer, receipt_mode) values (?, ?, ?, ?, ?, ?, ?, ?, ?)" +insertConv :: PrepQuery W (ConvId, ConvType, UserId, C.Set Access, C.Set AccessRoleV2, Maybe Text, Maybe TeamId, Maybe Milliseconds, Maybe ReceiptMode, Protocol, Maybe GroupId) () +insertConv = "insert into conversation (conv, type, creator, access, access_roles_v2, name, team, message_timer, receipt_mode, protocol, group_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" updateConvAccess :: PrepQuery W (C.Set Access, C.Set AccessRoleV2, ConvId) () updateConvAccess = "update conversation set access = ?, access_roles_v2 = ? where conv = ?" @@ -252,6 +253,14 @@ insertUserConv = "insert into user (user, conv) values (?, ?)" deleteUserConv :: PrepQuery W (UserId, ConvId) () deleteUserConv = "delete from user where user = ? and conv = ?" +-- MLS Conversations -------------------------------------------------------- + +insertGroupId :: PrepQuery W (GroupId, ConvId, Domain) () +insertGroupId = "INSERT INTO group_id_conv_id (group_id, conv_id, domain) VALUES (?, ?, ?)" + +lookupGroupId :: PrepQuery R (Identity GroupId) (ConvId, Domain) +lookupGroupId = "SELECT (conv_id, domain) from group_id_conv_id where group_id = ?" + -- Members ------------------------------------------------------------------ type MemberStatus = Int32 diff --git a/services/galley/src/Galley/Cassandra/Team.hs b/services/galley/src/Galley/Cassandra/Team.hs index c0ae2938f10..629dc165767 100644 --- a/services/galley/src/Galley/Cassandra/Team.hs +++ b/services/galley/src/Galley/Cassandra/Team.hs @@ -62,6 +62,7 @@ import Imports hiding (Set, max) import Polysemy import Polysemy.Input import qualified UnliftIO +import Wire.API.Team (Icon (..)) import Wire.API.Team.Member interpretTeamStoreToCassandra :: @@ -137,11 +138,11 @@ createTeam :: Maybe TeamId -> UserId -> Range 1 256 Text -> - Range 1 256 Text -> + Icon -> Maybe (Range 1 256 Text) -> TeamBinding -> Client Team -createTeam t uid (fromRange -> n) (fromRange -> i) k b = do +createTeam t uid (fromRange -> n) i k b = do tid <- maybe (Id <$> liftIO nextRandom) return t retry x5 $ write Cql.insertTeam (params LocalQuorum (tid, uid, n, i, fromRange <$> k, initialStatus b, b)) pure (newTeam tid uid n i b & teamIconKey .~ (fromRange <$> k)) diff --git a/services/galley/src/Galley/Data/Conversation.hs b/services/galley/src/Galley/Data/Conversation.hs index 9875af57a4e..bb3eebd6985 100644 --- a/services/galley/src/Galley/Data/Conversation.hs +++ b/services/galley/src/Galley/Data/Conversation.hs @@ -78,6 +78,8 @@ convMetadata c = (convTeam c) (convMessageTimer c) (convReceiptMode c) + (fromMaybe ProtocolProteus . convProtocol $ c) + (convGroupId c) convAccessData :: Conversation -> ConversationAccessData convAccessData conv = diff --git a/services/galley/src/Galley/Data/Conversation/Types.hs b/services/galley/src/Galley/Data/Conversation/Types.hs index fa50d7b7b11..1165b9af640 100644 --- a/services/galley/src/Galley/Data/Conversation/Types.hs +++ b/services/galley/src/Galley/Data/Conversation/Types.hs @@ -43,7 +43,9 @@ data Conversation = Conversation convDeleted :: Maybe Bool, -- | Global message timer convMessageTimer :: Maybe Milliseconds, - convReceiptMode :: Maybe ReceiptMode + convReceiptMode :: Maybe ReceiptMode, + convProtocol :: Maybe Protocol, + convGroupId :: Maybe GroupId } deriving (Show) @@ -57,5 +59,6 @@ data NewConversation = NewConversation ncMessageTimer :: Maybe Milliseconds, ncReceiptMode :: Maybe ReceiptMode, ncUsers :: ConvSizeChecked UserList UserId, - ncRole :: RoleName + ncRole :: RoleName, + ncProtocol :: Protocol } diff --git a/services/galley/src/Galley/Effects/BrigAccess.hs b/services/galley/src/Galley/Effects/BrigAccess.hs index 8bae5c20b8d..fbdba863305 100644 --- a/services/galley/src/Galley/Effects/BrigAccess.hs +++ b/services/galley/src/Galley/Effects/BrigAccess.hs @@ -57,6 +57,7 @@ import Brig.Types.User import Data.Id import Data.Misc import Data.Qualified +import Galley.API.Error (AuthenticationError) import Galley.External.LegalHoldService.Types import Imports import Network.HTTP.Types.Status @@ -85,7 +86,7 @@ data BrigAccess m a where Maybe Relation -> BrigAccess m [ConnectionStatusV2] PutConnectionInternal :: UpdateConnectionsInternal -> BrigAccess m Status - ReauthUser :: UserId -> ReAuthUser -> BrigAccess m Bool + ReauthUser :: UserId -> ReAuthUser -> BrigAccess m (Either AuthenticationError ()) LookupActivatedUsers :: [UserId] -> BrigAccess m [User] GetUsers :: [UserId] -> BrigAccess m [UserAccount] DeleteUser :: UserId -> BrigAccess m () diff --git a/services/galley/src/Galley/Effects/ConversationStore.hs b/services/galley/src/Galley/Effects/ConversationStore.hs index d0233ce930e..add6e8ba793 100644 --- a/services/galley/src/Galley/Effects/ConversationStore.hs +++ b/services/galley/src/Galley/Effects/ConversationStore.hs @@ -34,6 +34,7 @@ module Galley.Effects.ConversationStore isConversationAlive, getRemoteConversationStatus, selectConversations, + getConversationIdByGroupId, -- * Update conversation setConversationType, @@ -42,6 +43,7 @@ module Galley.Effects.ConversationStore setConversationReceiptMode, setConversationMessageTimer, acceptConnectConversation, + setGroupId, -- * Delete conversation deleteConversation, @@ -61,7 +63,7 @@ import Polysemy import Wire.API.Conversation hiding (Conversation, Member) data ConversationStore m a where - CreateConversation :: NewConversation -> ConversationStore m Conversation + CreateConversation :: Local x -> NewConversation -> ConversationStore m Conversation CreateConnectConversation :: UUID V4 -> UUID V4 -> @@ -105,6 +107,8 @@ data ConversationStore m a where SetConversationAccess :: ConvId -> ConversationAccessData -> ConversationStore m () SetConversationReceiptMode :: ConvId -> ReceiptMode -> ConversationStore m () SetConversationMessageTimer :: ConvId -> Maybe Milliseconds -> ConversationStore m () + GetConversationIdByGroupId :: GroupId -> ConversationStore m (Maybe (Qualified ConvId)) + SetGroupId :: GroupId -> Qualified ConvId -> ConversationStore m () makeSem ''ConversationStore diff --git a/services/galley/src/Galley/Effects/TeamStore.hs b/services/galley/src/Galley/Effects/TeamStore.hs index 9fa4dd42d63..cc2495e7ccb 100644 --- a/services/galley/src/Galley/Effects/TeamStore.hs +++ b/services/galley/src/Galley/Effects/TeamStore.hs @@ -85,6 +85,7 @@ import Imports import Polysemy import Polysemy.Error import qualified Proto.TeamEvents as E +import Wire.API.Team (Icon) data TeamStore m a where CreateTeamMember :: TeamId -> TeamMember -> TeamStore m () @@ -93,7 +94,7 @@ data TeamStore m a where Maybe TeamId -> UserId -> Range 1 256 Text -> - Range 1 256 Text -> + Icon -> Maybe (Range 1 256 Text) -> TeamBinding -> TeamStore m Team diff --git a/services/galley/src/Galley/Intra/Client.hs b/services/galley/src/Galley/Intra/Client.hs index 902d662b1fc..b38c3abe2b3 100644 --- a/services/galley/src/Galley/Intra/Client.hs +++ b/services/galley/src/Galley/Intra/Client.hs @@ -133,6 +133,8 @@ addLegalHoldClientToUser uid connId prekeys lastPrekey' = do Nothing Nothing Nothing + mempty + Nothing -- | Calls 'Brig.API.removeLegalHoldClientH'. removeLegalHoldClientFromUser :: diff --git a/services/galley/src/Galley/Intra/User.hs b/services/galley/src/Galley/Intra/User.hs index efc2597b3da..0dc72d8ba6a 100644 --- a/services/galley/src/Galley/Intra/User.hs +++ b/services/galley/src/Galley/Intra/User.hs @@ -36,8 +36,9 @@ import Bilge.RPC import Brig.Types.Connection (Relation (..), UpdateConnectionsInternal (..), UserIds (..)) import qualified Brig.Types.Intra as Brig import Brig.Types.User (User) +import Control.Error hiding (bool, isRight) import Control.Lens (view, (^.)) -import Control.Monad.Catch (throwM) +import Control.Monad.Catch import Data.ByteString.Char8 (pack) import qualified Data.ByteString.Char8 as BSC import Data.ByteString.Conversion @@ -45,6 +46,7 @@ import Data.Id import Data.Proxy import Data.Qualified import Data.String.Conversions +import qualified Data.Text.Lazy as Lazy import Galley.API.Error import Galley.Env import Galley.Intra.Util @@ -55,6 +57,7 @@ import qualified Network.HTTP.Client.Internal as Http import Network.HTTP.Types.Method import Network.HTTP.Types.Status import Network.Wai.Utilities.Error +import qualified Network.Wai.Utilities.Error as Wai import Servant.API ((:<|>) ((:<|>))) import qualified Servant.Client as Client import Util.Options @@ -136,14 +139,22 @@ deleteBot cid bot = do reAuthUser :: UserId -> Brig.ReAuthUser -> - App Bool + App (Either AuthenticationError ()) reAuthUser uid auth = do let req = method GET . paths ["/i/users", toByteString' uid, "reauthenticate"] . json auth - st <- statusCode . responseStatus <$> call Brig (check [status200, status403] . req) - return $ st == 200 + resp <- call Brig (check [status200, status403] . req) + pure $ case (statusCode . responseStatus $ resp, errorLabel resp) of + (200, _) -> Right () + (403, Just "code-authentication-required") -> Left VerificationCodeRequired + (403, Just "code-authentication-failed") -> Left VerificationCodeAuthFailed + (403, _) -> Left ReAuthFailed + (_, _) -> Left ReAuthFailed + where + errorLabel :: ResponseLBS -> Maybe Lazy.Text + errorLabel = fmap Wai.label . responseJsonMaybe check :: [Status] -> Request -> Request check allowed r = @@ -239,6 +250,7 @@ getAccountFeatureConfigClientM :: :<|> _ ) :<|> _ + :<|> _ ) = Client.client (Proxy @IAPI.API) runHereClientM :: HasCallStack => Client.ClientM a -> App (Either Client.ClientError a) diff --git a/services/galley/src/Galley/Validation.hs b/services/galley/src/Galley/Validation.hs index 0e599a212d9..4e988a9f117 100644 --- a/services/galley/src/Galley/Validation.hs +++ b/services/galley/src/Galley/Validation.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + -- This file is part of the Wire Server implementation. -- -- Copyright (C) 2022 Wire Swiss GmbH @@ -48,6 +50,10 @@ rangeCheckedMaybe (Just a) = Just <$> rangeChecked a newtype ConvSizeChecked f a = ConvSizeChecked {fromConvSize :: f a} deriving (Functor, Foldable, Traversable) +deriving newtype instance Semigroup (f a) => Semigroup (ConvSizeChecked f a) + +deriving newtype instance Monoid (f a) => Monoid (ConvSizeChecked f a) + checkedConvSize :: (Member (Error InvalidInput) r, Foldable f) => Opts -> diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index fa070f2565b..cb4ce4e4972 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -59,6 +59,7 @@ import qualified Data.Map.Strict as Map import Data.Qualified import Data.Range import qualified Data.Set as Set +import Data.Singletons import qualified Data.Text as T import qualified Data.Text.Ascii as Ascii import Data.Time.Clock (getCurrentTime) @@ -71,6 +72,7 @@ import Galley.Types.Conversations.Intra import Galley.Types.Conversations.Members import Galley.Types.Conversations.Roles import qualified Galley.Types.Teams as Teams +import Galley.Types.UserList import Gundeck.Types.Notification import Imports import qualified Network.HTTP.Types as HTTP @@ -118,7 +120,9 @@ tests s = "Main Conversations API" [ test s "status" status, test s "metrics" metrics, - test s "create conversation" postConvOk, + test s "create Proteus conversation" postProteusConvOk, + test s "fail to create MLS conversation" postMLSConvFail, + test s "create MLS conversation" postMLSConvOk, test s "create conversation with remote users" postConvWithRemoteUsersOk, test s "get empty conversations" getConvsOk, test s "get conversations by ids" getConvsOk2, @@ -252,8 +256,8 @@ metrics = do -- Should contain the request duration metric in its output const (Just "TYPE http_request_duration_seconds histogram") =~= responseBody -postConvOk :: TestM () -postConvOk = do +postProteusConvOk :: TestM () +postProteusConvOk = do c <- view tsCannon qalice <- randomQualifiedUser let alice = qUnqualified qalice @@ -283,6 +287,36 @@ postConvOk = do EdConversation c' -> assertConvEquals cnv c' _ -> assertFailure "Unexpected event data" +postMLSConvFail :: TestM () +postMLSConvFail = do + qalice <- randomQualifiedUser + let alice = qUnqualified qalice + lAlice <- flip toLocalUnsafe alice <$> viewFederationDomain + bob <- randomUser + connectUsers alice (list1 bob []) + postMLSConv lAlice (UserList [bob] []) Nothing [] Nothing Nothing !!! do + const 400 === statusCode + const (Just "non-empty-member-list") === fmap label . responseJsonError + +postMLSConvOk :: TestM () +postMLSConvOk = do + c <- view tsCannon + qalice <- randomQualifiedUser + bob <- randomUser + let alice = qUnqualified qalice + lAlice <- flip toLocalUnsafe alice <$> viewFederationDomain + let nameMaxSize = T.replicate 256 "a" + connectUsers alice (list1 bob []) + WS.bracketR2 c alice bob $ \(wsA, wsB) -> do + rsp <- + postMLSConv lAlice mempty (Just nameMaxSize) [] Nothing Nothing + pure rsp !!! do + const 201 === statusCode + const Nothing === fmap label . responseJsonError + cid <- assertConv rsp RegularConv alice qalice [] (Just nameMaxSize) Nothing + WS.assertNoEvent (2 # Second) [wsB] + checkConvCreateEvent cid wsA + postConvWithRemoteUsersOk :: TestM () postConvWithRemoteUsersOk = do c <- view tsCannon @@ -298,14 +332,14 @@ postConvWithRemoteUsersOk = do mapM_ (connectWithRemoteUser alice) [qChad, qCharlie, qDee] -- Ensure name is within range, max size is 256 - postConvQualified alice defNewConv {newConvName = Just (T.replicate 257 "a"), newConvQualifiedUsers = [qAlex, qAmy, qChad, qCharlie, qDee]} + postConvQualified alice defNewProteusConv {newConvName = Just (T.replicate 257 "a"), newConvQualifiedUsers = [qAlex, qAmy, qChad, qCharlie, qDee]} !!! const 400 === statusCode let nameMaxSize = T.replicate 256 "a" WS.bracketR3 c alice alex amy $ \(wsAlice, wsAlex, wsAmy) -> do (rsp, federatedRequests) <- withTempMockFederator (const ()) $ - postConvQualified alice defNewConv {newConvName = Just nameMaxSize, newConvQualifiedUsers = [qAlex, qAmy, qChad, qCharlie, qDee]} + postConvQualified alice defNewProteusConv {newConvName = Just nameMaxSize, newConvQualifiedUsers = [qAlex, qAmy, qChad, qCharlie, qDee]} do @@ -786,7 +820,7 @@ postMessageQualifiedLocalOwningBackendMissingClients = do resp <- postConvWithRemoteUsers aliceUnqualified - defNewConv {newConvQualifiedUsers = [bobOwningDomain, chadOwningDomain, deeRemote]} + defNewProteusConv {newConvQualifiedUsers = [bobOwningDomain, chadOwningDomain, deeRemote]} let convId = (`Qualified` owningDomain) . decodeConvId $ resp -- Missing Bob, chadClient2 and Dee @@ -857,7 +891,7 @@ postMessageQualifiedLocalOwningBackendRedundantAndDeletedClients = do resp <- postConvWithRemoteUsers aliceUnqualified - defNewConv {newConvQualifiedUsers = [bobOwningDomain, chadOwningDomain, deeRemote]} + defNewProteusConv {newConvQualifiedUsers = [bobOwningDomain, chadOwningDomain, deeRemote]} let convId = (`Qualified` owningDomain) . decodeConvId $ resp WS.bracketR3 cannon bobUnqualified chadUnqualified nonMemberUnqualified $ \(wsBob, wsChad, wsNonMember) -> do @@ -947,7 +981,7 @@ postMessageQualifiedLocalOwningBackendIgnoreMissingClients = do resp <- postConvWithRemoteUsers aliceUnqualified - defNewConv {newConvQualifiedUsers = [bobOwningDomain, chadOwningDomain, deeRemote]} + defNewProteusConv {newConvQualifiedUsers = [bobOwningDomain, chadOwningDomain, deeRemote]} let convId = (`Qualified` owningDomain) . decodeConvId $ resp let brigApi _ = @@ -1074,7 +1108,7 @@ postMessageQualifiedLocalOwningBackendFailedToSendClients = do resp <- postConvWithRemoteUsers aliceUnqualified - defNewConv {newConvQualifiedUsers = [bobOwningDomain, chadOwningDomain, deeRemote]} + defNewProteusConv {newConvQualifiedUsers = [bobOwningDomain, chadOwningDomain, deeRemote]} let convId = (`Qualified` owningDomain) . decodeConvId $ resp WS.bracketR2 cannon bobUnqualified chadUnqualified $ \(wsBob, wsChad) -> do @@ -1247,6 +1281,8 @@ testPostCodeRejectedIfGuestLinksDisabled = do setStatus Public.TeamFeatureEnabled checkPostCode 200 +-- @SF.Separation @TSFI.RESTfulAPI @S2 +-- Check if guests cannot join anymore if guest invite feature was disabled on team level testJoinTeamConvGuestLinksDisabled :: TestM () testJoinTeamConvGuestLinksDisabled = do galley <- view tsGalley @@ -1302,6 +1338,8 @@ testJoinTeamConvGuestLinksDisabled = do postJoinCodeConv bob' cCode !!! const 200 === statusCode checkFeatureStatus Public.TeamFeatureEnabled +-- @END + testJoinNonTeamConvGuestLinksDisabled :: TestM () testJoinNonTeamConvGuestLinksDisabled = do galley <- view tsGalley @@ -1326,6 +1364,14 @@ testJoinNonTeamConvGuestLinksDisabled = do const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither const 200 === statusCode +-- @SF.Separation @TSFI.RESTfulAPI @S2 +-- This test case covers a negative check that if access code of a guest link is revoked no further +-- people can join the group conversation. Additionally it covers: +-- Random users can use invite link +-- Reusing previously used link yields same conv (idempotency) +-- Guest can use invite link +-- Guest cannot create invite link +-- Non-admin cannot create invite link postJoinCodeConvOk :: TestM () postJoinCodeConvOk = do c <- view tsCannon @@ -1350,6 +1396,8 @@ postJoinCodeConvOk = do postJoinCodeConv bob incorrectCode !!! const 404 === statusCode -- correct code works postJoinCodeConv bob payload !!! const 200 === statusCode + -- non-admin cannot create invite link + postConvCode bob conv !!! const 403 === statusCode -- test no-op postJoinCodeConv bob payload !!! const 204 === statusCode -- eve cannot join @@ -1362,11 +1410,15 @@ postJoinCodeConvOk = do let nonActivatedAccess = ConversationAccessData (Set.singleton CodeAccess) accessRolesWithGuests putAccessUpdate alice conv nonActivatedAccess !!! const 200 === statusCode postJoinCodeConv eve payload !!! const 200 === statusCode + -- guest cannot create invite link + postConvCode eve conv !!! const 403 === statusCode -- after removing CodeAccess, no further people can join let noCodeAccess = ConversationAccessData (Set.singleton InviteAccess) accessRoles putAccessUpdate alice conv noCodeAccess !!! const 200 === statusCode postJoinCodeConv dave payload !!! const 404 === statusCode +-- @END + postConvertCodeConv :: TestM () postConvertCodeConv = do c <- view tsCannon @@ -1489,7 +1541,7 @@ testAccessUpdateGuestRemoved = do responseJsonError =<< postConvWithRemoteUsers (qUnqualified alice) - defNewConv + defNewProteusConv { newConvQualifiedUsers = [bob, charlie, dee], newConvTeam = Just (ConvTeamInfo tid) } @@ -1518,7 +1570,7 @@ testAccessUpdateGuestRemoved = do [ frComponent freq == Galley, frRPC freq == "on-conversation-updated", fmap F.cuAction (eitherDecode (frBody freq)) - == Right (ConversationActionRemoveMembers (charlie :| [dee])) + == Right (SomeConversationAction (sing @'ConversationLeaveTag) (charlie :| [dee])) ] -- only alice and bob remain @@ -1671,7 +1723,7 @@ paginateConvListIds = do F.cuOrigUserId = qChad, F.cuConvId = conv, F.cuAlreadyPresentUsers = [], - F.cuAction = ConversationActionAddMembers (pure qAlice) roleNameWireMember + F.cuAction = SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (pure qAlice) roleNameWireMember) } runFedClient @"on-conversation-updated" fedGalleyClient chadDomain cu @@ -1687,7 +1739,7 @@ paginateConvListIds = do F.cuOrigUserId = qDee, F.cuConvId = conv, F.cuAlreadyPresentUsers = [], - F.cuAction = ConversationActionAddMembers (pure qAlice) roleNameWireMember + F.cuAction = SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (pure qAlice) roleNameWireMember) } runFedClient @"on-conversation-updated" fedGalleyClient deeDomain cu @@ -1732,7 +1784,7 @@ paginateConvListIdsPageEndingAtLocalsAndDomain = do F.cuOrigUserId = qChad, F.cuConvId = conv, F.cuAlreadyPresentUsers = [], - F.cuAction = ConversationActionAddMembers (pure qAlice) roleNameWireMember + F.cuAction = SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (pure qAlice) roleNameWireMember) } runFedClient @"on-conversation-updated" fedGalleyClient chadDomain cu @@ -1750,7 +1802,7 @@ paginateConvListIdsPageEndingAtLocalsAndDomain = do F.cuOrigUserId = qDee, F.cuConvId = conv, F.cuAlreadyPresentUsers = [], - F.cuAction = ConversationActionAddMembers (pure qAlice) roleNameWireMember + F.cuAction = SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (pure qAlice) roleNameWireMember) } runFedClient @"on-conversation-updated" fedGalleyClient deeDomain cu @@ -1813,7 +1865,7 @@ postConvQualifiedFailNotConnected = do alice <- randomUser bob <- randomQualifiedUser jane <- randomQualifiedUser - postConvQualified alice defNewConv {newConvQualifiedUsers = [bob, jane]} !!! do + postConvQualified alice defNewProteusConv {newConvQualifiedUsers = [bob, jane]} !!! do const 403 === statusCode const (Just "not-connected") === fmap label . responseJsonUnsafe @@ -1842,7 +1894,7 @@ postConvQualifiedFailNumMembers = do alice <- randomUser bob : others <- replicateM n randomQualifiedUser connectLocalQualifiedUsers alice (list1 bob others) - postConvQualified alice defNewConv {newConvQualifiedUsers = (bob : others)} !!! do + postConvQualified alice defNewProteusConv {newConvQualifiedUsers = (bob : others)} !!! do const 400 === statusCode const (Just "client-error") === fmap label . responseJsonUnsafe @@ -1870,7 +1922,7 @@ postConvQualifiedFailBlocked = do connectLocalQualifiedUsers alice (list1 bob [jane]) putConnectionQualified jane alice Blocked !!! const 200 === statusCode - postConvQualified alice defNewConv {newConvQualifiedUsers = [bob, jane]} !!! do + postConvQualified alice defNewProteusConv {newConvQualifiedUsers = [bob, jane]} !!! do const 403 === statusCode const (Just "not-connected") === fmap label . responseJsonUnsafe @@ -1878,7 +1930,7 @@ postConvQualifiedNoConnection :: TestM () postConvQualifiedNoConnection = do alice <- randomUser bob <- flip Qualified (Domain "far-away.example.com") <$> randomId - postConvQualified alice defNewConv {newConvQualifiedUsers = [bob]} + postConvQualified alice defNewProteusConv {newConvQualifiedUsers = [bob]} !!! const 403 === statusCode postTeamConvQualifiedNoConnection :: TestM () @@ -1888,14 +1940,14 @@ postTeamConvQualifiedNoConnection = do charlie <- randomQualifiedUser postConvQualified (qUnqualified alice) - defNewConv + defNewProteusConv { newConvQualifiedUsers = [bob], newConvTeam = Just (ConvTeamInfo tid) } !!! const 403 === statusCode postConvQualified (qUnqualified alice) - defNewConv + defNewProteusConv { newConvQualifiedUsers = [charlie], newConvTeam = Just (ConvTeamInfo tid) } @@ -1908,7 +1960,7 @@ postConvQualifiedNonExistentDomain = do connectWithRemoteUser alice bob postConvQualified alice - defNewConv {newConvQualifiedUsers = [bob]} + defNewProteusConv {newConvQualifiedUsers = [bob]} !!! do const 422 === statusCode @@ -1929,7 +1981,7 @@ postConvQualifiedFederationNotEnabled = do -- FUTUREWORK: figure out how to use functions in the TestM monad inside withSettingsOverrides and remove this duplication postConvHelper :: (MonadIO m, MonadHttp m) => (Request -> Request) -> UserId -> [Qualified UserId] -> m ResponseLBS postConvHelper g zusr newUsers = do - let conv = NewConv [] newUsers (Just "gossip") (Set.fromList []) Nothing Nothing Nothing Nothing roleNameWireAdmin + let conv = NewConv [] newUsers (Just "gossip") (Set.fromList []) Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolProteus post $ g . path "/conversations" . zUser zusr . zConn "conn" . zType "access" . json conv postSelfConvOk :: TestM () @@ -1958,7 +2010,7 @@ postConvO2OFailWithSelf :: TestM () postConvO2OFailWithSelf = do g <- view tsGalley alice <- randomUser - let inv = NewConv [alice] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin + let inv = NewConv [alice] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolProteus post (g . path "/conversations/one2one" . zUser alice . zConn "conn" . zType "access" . json inv) !!! do const 403 === statusCode const (Just "invalid-op") === fmap label . responseJsonUnsafe @@ -2135,7 +2187,7 @@ getConvQualifiedOk = do decodeConvId <$> postConvQualified alice - defNewConv + defNewProteusConv { newConvQualifiedUsers = [bob, chuck], newConvName = Just "gossip" } @@ -2161,6 +2213,8 @@ accessConvMeta = do Nothing Nothing Nothing + ProtocolProteus + Nothing get (g . paths ["i/conversations", toByteString' conv, "meta"] . zUser alice) !!! do const 200 === statusCode const (Just meta) === (decode <=< responseBody) @@ -2244,7 +2298,7 @@ testDeleteTeamConversationWithRemoteMembers = do liftIO $ do let convUpdates = mapMaybe (eitherToMaybe . parseFedRequest) received - convUpdate <- case filter ((== ConversationActionDelete) . cuAction) convUpdates of + convUpdate <- case filter ((== (SomeConversationAction (sing @'ConversationDeleteTag) ())) . cuAction) convUpdates of [] -> assertFailure "No ConversationUpdate requests received" [convDelete] -> pure convDelete _ -> assertFailure "Multiple ConversationUpdate requests received" @@ -2296,7 +2350,7 @@ testGetQualifiedRemoteConv = do connectWithRemoteUser aliceId bobQ registerRemoteConv remoteConvId bobId Nothing (Set.fromList [aliceAsOtherMember]) - let mockConversation = mkConv convId bobId roleNameWireAdmin [bobAsOtherMember] + let mockConversation = mkProteusConv convId bobId roleNameWireAdmin [bobAsOtherMember] remoteConversationResponse = GetConversationsResponse [mockConversation] expected = Conversation @@ -2401,8 +2455,8 @@ testBulkGetQualifiedConvs = do let bobAsOtherMember = OtherMember bobQ Nothing roleNameWireAdmin carlAsOtherMember = OtherMember carlQ Nothing roleNameWireAdmin - mockConversationA = mkConv (qUnqualified remoteConvIdA) bobId roleNameWireAdmin [bobAsOtherMember] - mockConversationB = mkConv (qUnqualified remoteConvIdB) carlId roleNameWireAdmin [carlAsOtherMember] + mockConversationA = mkProteusConv (qUnqualified remoteConvIdA) bobId roleNameWireAdmin [bobAsOtherMember] + mockConversationB = mkProteusConv (qUnqualified remoteConvIdB) carlId roleNameWireAdmin [carlAsOtherMember] req = ListConversations . unsafeRange $ [ localConvId, @@ -2650,7 +2704,7 @@ deleteMembersConvLocalQualifiedOk = do decodeConvId <$> postConvQualified alice - defNewConv + defNewProteusConv { newConvQualifiedUsers = [qBob, qEve], newConvName = Just "federated gossip" } @@ -2684,7 +2738,7 @@ deleteLocalMemberConvLocalQualifiedOk = do decodeConvId <$> postConvWithRemoteUsers alice - defNewConv {newConvQualifiedUsers = [qBob, qEve]} + defNewProteusConv {newConvQualifiedUsers = [qBob, qEve]} let qconvId = Qualified convId localDomain let mockReturnEve = onlyMockedFederatedBrigResponse [(qEve, "Eve")] @@ -2742,7 +2796,7 @@ deleteRemoteMemberConvLocalQualifiedOk = do fmap decodeConvId $ postConvQualified alice - defNewConv {newConvQualifiedUsers = [qBob, qChad, qDee, qEve]} + defNewProteusConv {newConvQualifiedUsers = [qBob, qChad, qDee, qEve]} do let e = List1.head (WS.unpackPayload n) @@ -3219,7 +3273,7 @@ putRemoteConvMemberOk update = do cuConvId = qUnqualified qconv, cuAlreadyPresentUsers = [], cuAction = - ConversationActionAddMembers (pure qalice) roleNameWireMember + SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (pure qalice) roleNameWireMember) } runFedClient @"on-conversation-updated" fedGalleyClient remoteDomain cu @@ -3258,7 +3312,7 @@ putRemoteConvMemberOk update = do -- Fetch remote conversation let bobAsLocal = LocalMember (qUnqualified qbob) defMemberStatus Nothing roleNameWireAdmin let mockConversation = - mkConv + mkProteusConv (qUnqualified qconv) (qUnqualified qbob) roleNameWireMember @@ -3342,7 +3396,7 @@ putReceiptModeWithRemotesOk = do resp <- postConvWithRemoteUsers bob - defNewConv {newConvQualifiedUsers = [qalice]} + defNewProteusConv {newConvQualifiedUsers = [qalice]} let qconv = decodeQualifiedConvId resp WS.bracketR c bob $ \wsB -> do @@ -3358,8 +3412,7 @@ putReceiptModeWithRemotesOk = do Right cu <- pure . eitherDecode . frBody $ req F.cuConvId cu @?= qUnqualified qconv F.cuAction cu - @?= ConversationActionReceiptModeUpdate - (ConversationReceiptModeUpdate (ReceiptMode 43)) + @?= SomeConversationAction (sing @'ConversationReceiptModeUpdateTag) (ConversationReceiptModeUpdate (ReceiptMode 43)) void . liftIO . WS.assertMatch (5 # Second) wsB $ \n -> do let e = List1.head (WS.unpackPayload n) @@ -3459,9 +3512,9 @@ removeUser = do connectWithRemoteUser alexDel' dory convA1 <- decodeConvId <$> postConv alice' [alexDel'] (Just "gossip") [] Nothing Nothing - convA2 <- decodeConvId <$> postConvWithRemoteUsers alice' defNewConv {newConvQualifiedUsers = [alexDel, amy, berta, dwight]} + convA2 <- decodeConvId <$> postConvWithRemoteUsers alice' defNewProteusConv {newConvQualifiedUsers = [alexDel, amy, berta, dwight]} convA3 <- decodeConvId <$> postConv alice' [amy'] (Just "gossip3") [] Nothing Nothing - convA4 <- decodeConvId <$> postConvWithRemoteUsers alice' defNewConv {newConvQualifiedUsers = [alexDel, bart, carl]} + convA4 <- decodeConvId <$> postConvWithRemoteUsers alice' defNewProteusConv {newConvQualifiedUsers = [alexDel, bart, carl]} convB1 <- randomId -- a remote conversation at 'bDomain' that Alice, AlexDel and Bart will be in convB2 <- randomId -- a remote conversation at 'bDomain' that AlexDel and Bart will be in convC1 <- randomId -- a remote conversation at 'cDomain' that AlexDel and Carl will be in @@ -3542,25 +3595,25 @@ removeUser = do bConvUpdates <- mapM (assertRight . eitherDecode . frBody) bConvUpdateRPCs bConvUpdatesA2 <- assertOne $ filter (\cu -> cuConvId cu == convA2) bConvUpdates - cuAction bConvUpdatesA2 @?= ConversationActionRemoveMembers (pure alexDel) + cuAction bConvUpdatesA2 @?= SomeConversationAction (sing @'ConversationLeaveTag) (pure alexDel) cuAlreadyPresentUsers bConvUpdatesA2 @?= [qUnqualified berta] bConvUpdatesA4 <- assertOne $ filter (\cu -> cuConvId cu == convA4) bConvUpdates - cuAction bConvUpdatesA4 @?= ConversationActionRemoveMembers (pure alexDel) + cuAction bConvUpdatesA4 @?= SomeConversationAction (sing @'ConversationLeaveTag) (pure alexDel) cuAlreadyPresentUsers bConvUpdatesA4 @?= [qUnqualified bart] liftIO $ do cConvUpdateRPC <- assertOne $ filter (matchFedRequest cDomain "on-conversation-updated") fedRequests Right convUpdate <- pure . eitherDecode . frBody $ cConvUpdateRPC cuConvId convUpdate @?= convA4 - cuAction convUpdate @?= ConversationActionRemoveMembers (pure alexDel) + cuAction convUpdate @?= SomeConversationAction (sing @'ConversationLeaveTag) (pure alexDel) cuAlreadyPresentUsers convUpdate @?= [qUnqualified carl] liftIO $ do dConvUpdateRPC <- assertOne $ filter (matchFedRequest dDomain "on-conversation-updated") fedRequests Right convUpdate <- pure . eitherDecode . frBody $ dConvUpdateRPC cuConvId convUpdate @?= convA2 - cuAction convUpdate @?= ConversationActionRemoveMembers (pure alexDel) + cuAction convUpdate @?= SomeConversationAction (sing @'ConversationLeaveTag) (pure alexDel) cuAlreadyPresentUsers convUpdate @?= [qUnqualified dwight] -- Check memberships @@ -3631,7 +3684,7 @@ testOne2OneConversationRequest shouldBeLocal actor desired = do Excluded -> members @?= Nothing False -> do found <- do - let rconv = mkConv (qUnqualified convId) (tUnqualified bob) roleNameWireAdmin [] + let rconv = mkProteusConv (qUnqualified convId) (tUnqualified bob) roleNameWireAdmin [] (resp, _) <- withTempMockFederator (const (F.GetConversationsResponse [rconv])) $ getConvQualified (tUnqualified alice) convId diff --git a/services/galley/test/integration/API/Federation.hs b/services/galley/test/integration/API/Federation.hs index b43e9030bcc..fb55fb0d223 100644 --- a/services/galley/test/integration/API/Federation.hs +++ b/services/galley/test/integration/API/Federation.hs @@ -53,6 +53,7 @@ import qualified Data.ProtoLens as Protolens import Data.Qualified import Data.Range import qualified Data.Set as Set +import Data.Singletons import Data.String.Conversions import Data.Time.Clock import Data.Timeout (TimeoutUnit (..), (#)) @@ -68,12 +69,12 @@ import qualified Test.Tasty.Cannon as WS import Test.Tasty.HUnit import TestHelpers import TestSetup -import Wire.API.Conversation.Action (ConversationAction (..)) -import Wire.API.Conversation.Member (Member (..)) +import Wire.API.Conversation +import Wire.API.Conversation.Action import Wire.API.Conversation.Role import Wire.API.Event.Conversation import Wire.API.Federation.API.Common -import Wire.API.Federation.API.Galley (GetConversationsRequest (..), GetConversationsResponse (..), RemoteConvMembers (..), RemoteConversation (..)) +import Wire.API.Federation.API.Galley import qualified Wire.API.Federation.API.Galley as FedGalley import Wire.API.Federation.Component import Wire.API.Message (ClientMismatchStrategy (..), MessageSendingStatus (mssDeletedClients, mssFailedToSend, mssRedundantClients), mkQualifiedOtrPayload, mssMissingClients) @@ -123,7 +124,7 @@ getConversationsAllFound = do responseJsonError =<< postConvWithRemoteUsers bob - defNewConv {newConvQualifiedUsers = [aliceQ, carlQ]} + defNewProteusConv {newConvQualifiedUsers = [aliceQ, carlQ]} -- create a one-to-one conversation between bob and alice do @@ -253,7 +254,7 @@ addLocalUser = do FedGalley.cuConvId = conv, FedGalley.cuAlreadyPresentUsers = [charlie], FedGalley.cuAction = - ConversationActionAddMembers (qalice :| [qdee]) roleNameWireMember + SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (qalice :| [qdee]) roleNameWireMember) } WS.bracketRN c [alice, charlie, dee] $ \[wsA, wsC, wsD] -> do runFedClient @"on-conversation-updated" fedGalleyClient remoteDomain cu @@ -307,7 +308,7 @@ addUnconnectedUsersOnly = do FedGalley.cuConvId = conv, FedGalley.cuAlreadyPresentUsers = [alice], FedGalley.cuAction = - ConversationActionAddMembers (qCharlie :| []) roleNameWireMember + SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (qCharlie :| []) roleNameWireMember) } -- Alice receives no notifications from this runFedClient @"on-conversation-updated" fedGalleyClient remoteDomain cu @@ -341,7 +342,7 @@ removeLocalUser = do FedGalley.cuConvId = conv, FedGalley.cuAlreadyPresentUsers = [], FedGalley.cuAction = - ConversationActionAddMembers (pure qAlice) roleNameWireMember + SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (pure qAlice) roleNameWireMember) } cuRemove = FedGalley.ConversationUpdate @@ -350,7 +351,7 @@ removeLocalUser = do FedGalley.cuConvId = conv, FedGalley.cuAlreadyPresentUsers = [alice], FedGalley.cuAction = - ConversationActionRemoveMembers (pure qAlice) + SomeConversationAction (sing @'ConversationLeaveTag) (pure qAlice) } connectWithRemoteUser alice qBob @@ -414,7 +415,7 @@ removeRemoteUser = do FedGalley.cuConvId = conv, FedGalley.cuAlreadyPresentUsers = [alice, charlie, dee], FedGalley.cuAction = - ConversationActionRemoveMembers (pure user) + SomeConversationAction (sing @'ConversationLeaveTag) (pure user) } WS.bracketRN c [alice, charlie, dee, flo] $ \[wsA, wsC, wsD, wsF] -> do @@ -438,7 +439,7 @@ removeRemoteUser = do wsAssertMembersLeave qconv qBob [qFlo] WS.assertNoEvent (1 # Second) [wsC, wsF, wsD] -notifyUpdate :: [Qualified UserId] -> ConversationAction -> EventType -> EventData -> TestM () +notifyUpdate :: [Qualified UserId] -> SomeConversationAction -> EventType -> EventData -> TestM () notifyUpdate extras action etype edata = do c <- view tsCannon qalice <- randomQualifiedUser @@ -483,14 +484,14 @@ notifyUpdate extras action etype edata = do notifyConvRename :: TestM () notifyConvRename = do let d = ConversationRename "gossip++" - notifyUpdate [] (ConversationActionRename d) ConvRename (EdConvRename d) + notifyUpdate [] (SomeConversationAction (sing @'ConversationRenameTag) d) ConvRename (EdConvRename d) notifyMessageTimer :: TestM () notifyMessageTimer = do let d = ConversationMessageTimerUpdate (Just 5000) notifyUpdate [] - (ConversationActionMessageTimerUpdate d) + (SomeConversationAction (sing @'ConversationMessageTimerUpdateTag) d) ConvMessageTimerUpdate (EdConvMessageTimerUpdate d) @@ -499,7 +500,7 @@ notifyReceiptMode = do let d = ConversationReceiptModeUpdate (ReceiptMode 42) notifyUpdate [] - (ConversationActionReceiptModeUpdate d) + (SomeConversationAction (sing @'ConversationReceiptModeUpdateTag) d) ConvReceiptModeUpdate (EdConvReceiptModeUpdate d) @@ -508,7 +509,7 @@ notifyAccess = do let d = ConversationAccessData (Set.fromList [InviteAccess, LinkAccess]) (Set.fromList [TeamMemberAccessRole]) notifyUpdate [] - (ConversationActionAccessUpdate d) + (SomeConversationAction (sing @'ConversationAccessDataTag) d) ConvAccessUpdate (EdConvAccessUpdate d) @@ -528,7 +529,7 @@ notifyMemberUpdate = do } notifyUpdate [qdee] - (ConversationActionMemberUpdate qdee (OtherMemberUpdate (Just roleNameWireAdmin))) + (SomeConversationAction (sing @'ConversationMemberUpdateTag) (ConversationMemberUpdate qdee (OtherMemberUpdate (Just roleNameWireAdmin)))) MemberStateUpdate (EdMemberUpdate d) @@ -567,7 +568,7 @@ notifyDeletedConversation = do FedGalley.cuOrigUserId = qbob, FedGalley.cuConvId = qUnqualified qconv, FedGalley.cuAlreadyPresentUsers = [alice], - FedGalley.cuAction = ConversationActionDelete + FedGalley.cuAction = SomeConversationAction (sing @'ConversationDeleteTag) () } runFedClient @"on-conversation-updated" fedGalleyClient bobDomain cu @@ -624,7 +625,7 @@ addRemoteUser = do FedGalley.cuConvId = qUnqualified qconv, FedGalley.cuAlreadyPresentUsers = map qUnqualified [qalice, qcharlie], FedGalley.cuAction = - ConversationActionAddMembers (qdee :| [qeve, qflo]) roleNameWireMember + SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (qdee :| [qeve, qflo]) roleNameWireMember) } WS.bracketRN c (map qUnqualified [qalice, qcharlie, qdee, qflo]) $ \[wsA, wsC, wsD, wsF] -> do runFedClient @"on-conversation-updated" fedGalleyClient bdom cu @@ -668,7 +669,7 @@ leaveConversationSuccess = do decodeConvId <$> postConvQualified alice - defNewConv + defNewProteusConv { newConvQualifiedUsers = [qBob, qChad, qDee, qEve] } let qconvId = Qualified convId localDomain @@ -697,8 +698,8 @@ leaveConversationSuccess = do let [remote1GalleyFederatedRequest] = fedRequestsForDomain remoteDomain1 Galley federatedRequests [remote2GalleyFederatedRequest] = fedRequestsForDomain remoteDomain2 Galley federatedRequests - assertRemoveUpdate remote1GalleyFederatedRequest qconvId qChad [qUnqualified qChad, qUnqualified qDee] qChad - assertRemoveUpdate remote2GalleyFederatedRequest qconvId qChad [qUnqualified qEve] qChad + assertLeaveUpdate remote1GalleyFederatedRequest qconvId qChad [qUnqualified qChad, qUnqualified qDee] qChad + assertLeaveUpdate remote2GalleyFederatedRequest qconvId qChad [qUnqualified qEve] qChad leaveConversationNonExistent :: TestM () leaveConversationNonExistent = do @@ -773,7 +774,7 @@ onMessageSent = do FedGalley.cuConvId = conv, FedGalley.cuAlreadyPresentUsers = [], FedGalley.cuAction = - ConversationActionAddMembers (pure qalice) roleNameWireMember + SomeConversationAction (sing @'ConversationJoinTag) (ConversationJoin (pure qalice) roleNameWireMember) } runFedClient @"on-conversation-updated" fedGalleyClient bdom cu @@ -859,7 +860,7 @@ sendMessage = do fmap decodeConvId $ postConvQualified aliceId - defNewConv + defNewProteusConv { newConvQualifiedUsers = [bob, chad] } ( postConvWithRemoteUsers (tUnqualified alice) - defNewConv {newConvQualifiedUsers = [qUntagged bob, alex, bart, carl]} + defNewProteusConv {newConvQualifiedUsers = [qUntagged bob, alex, bart, carl]} do @@ -1046,7 +1047,7 @@ onUserDeleted = do FedGalley.cuOrigUserId bobDomainRPCReq @?= qUntagged bob FedGalley.cuConvId bobDomainRPCReq @?= qUnqualified groupConvId sort (FedGalley.cuAlreadyPresentUsers bobDomainRPCReq) @?= sort [tUnqualified bob, qUnqualified bart] - FedGalley.cuAction bobDomainRPCReq @?= ConversationActionRemoveMembers (pure $ qUntagged bob) + FedGalley.cuAction bobDomainRPCReq @?= (SomeConversationAction (sing @'ConversationLeaveTag) (pure $ qUntagged bob)) -- Assertions about RPC to 'cDomain' cDomainRPC <- assertOne $ filter (\c -> frTargetDomain c == cDomain) rpcCalls @@ -1054,4 +1055,4 @@ onUserDeleted = do FedGalley.cuOrigUserId cDomainRPCReq @?= qUntagged bob FedGalley.cuConvId cDomainRPCReq @?= qUnqualified groupConvId FedGalley.cuAlreadyPresentUsers cDomainRPCReq @?= [qUnqualified carl] - FedGalley.cuAction cDomainRPCReq @?= ConversationActionRemoveMembers (pure $ qUntagged bob) + FedGalley.cuAction cDomainRPCReq @?= (SomeConversationAction (sing @'ConversationLeaveTag) (pure $ qUntagged bob)) diff --git a/services/galley/test/integration/API/MessageTimer.hs b/services/galley/test/integration/API/MessageTimer.hs index f40e23dd53a..a7683ef883c 100644 --- a/services/galley/test/integration/API/MessageTimer.hs +++ b/services/galley/test/integration/API/MessageTimer.hs @@ -32,6 +32,7 @@ import Data.List1 import qualified Data.List1 as List1 import Data.Misc import Data.Qualified +import Data.Singletons import Federator.MockServer (FederatedRequest (..)) import Galley.Types import Galley.Types.Conversations.Roles @@ -157,7 +158,7 @@ messageTimerChangeWithRemotes = do resp <- postConvWithRemoteUsers bob - defNewConv {newConvQualifiedUsers = [qalice]} + defNewProteusConv {newConvQualifiedUsers = [qalice]} let qconv = decodeQualifiedConvId resp WS.bracketR c bob $ \wsB -> do @@ -174,8 +175,7 @@ messageTimerChangeWithRemotes = do Right cu <- pure . eitherDecode . frBody $ req F.cuConvId cu @?= qUnqualified qconv F.cuAction cu - @?= ConversationActionMessageTimerUpdate - (ConversationMessageTimerUpdate timer1sec) + @?= SomeConversationAction (sing @'ConversationMessageTimerUpdateTag) (ConversationMessageTimerUpdate timer1sec) void . liftIO . WS.assertMatch (5 # Second) wsB $ \n -> do let e = List1.head (WS.unpackPayload n) diff --git a/services/galley/test/integration/API/Roles.hs b/services/galley/test/integration/API/Roles.hs index fda229a8e21..3764d8afa43 100644 --- a/services/galley/test/integration/API/Roles.hs +++ b/services/galley/test/integration/API/Roles.hs @@ -29,6 +29,7 @@ import Data.List1 import qualified Data.List1 as List1 import Data.Qualified import qualified Data.Set as Set +import Data.Singletons import Federator.MockServer (FederatedRequest (..)) import Galley.Types import Galley.Types.Conversations.Roles @@ -41,6 +42,7 @@ import qualified Test.Tasty.Cannon as WS import Test.Tasty.HUnit import TestHelpers import TestSetup +import Wire.API.Conversation import Wire.API.Conversation.Action import Wire.API.Event.Conversation import qualified Wire.API.Federation.API.Galley as F @@ -171,7 +173,7 @@ roleUpdateRemoteMember = do resp <- postConvWithRemoteUsers bob - defNewConv {newConvQualifiedUsers = [qalice, qcharlie]} + defNewProteusConv {newConvQualifiedUsers = [qalice, qcharlie]} let qconv = decodeQualifiedConvId resp WS.bracketR c bob $ \wsB -> do @@ -203,7 +205,7 @@ roleUpdateRemoteMember = do Right cu <- pure . eitherDecode . frBody $ req F.cuConvId cu @?= qUnqualified qconv F.cuAction cu - @?= ConversationActionMemberUpdate qcharlie (OtherMemberUpdate (Just roleNameWireMember)) + @?= SomeConversationAction (sing @'ConversationMemberUpdateTag) (ConversationMemberUpdate qcharlie (OtherMemberUpdate (Just roleNameWireMember))) sort (F.cuAlreadyPresentUsers cu) @?= sort [qUnqualified qalice, qUnqualified qcharlie] liftIO . WS.assertMatch_ (5 # Second) wsB $ \n -> do @@ -240,7 +242,7 @@ roleUpdateWithRemotes = do resp <- postConvWithRemoteUsers bob - defNewConv {newConvQualifiedUsers = [qalice, qcharlie]} + defNewProteusConv {newConvQualifiedUsers = [qalice, qcharlie]} let qconv = decodeQualifiedConvId resp WS.bracketR2 c bob charlie $ \(wsB, wsC) -> do @@ -272,7 +274,7 @@ roleUpdateWithRemotes = do Right cu <- pure . eitherDecode . frBody $ req F.cuConvId cu @?= qUnqualified qconv F.cuAction cu - @?= ConversationActionMemberUpdate qcharlie (OtherMemberUpdate (Just roleNameWireAdmin)) + @?= SomeConversationAction (sing @'ConversationMemberUpdateTag) (ConversationMemberUpdate qcharlie (OtherMemberUpdate (Just roleNameWireAdmin))) F.cuAlreadyPresentUsers cu @?= [qUnqualified qalice] liftIO . WS.assertMatchN_ (5 # Second) [wsB, wsC] $ \n -> do @@ -298,7 +300,7 @@ accessUpdateWithRemotes = do resp <- postConvWithRemoteUsers bob - defNewConv {newConvQualifiedUsers = [qalice, qcharlie]} + defNewProteusConv {newConvQualifiedUsers = [qalice, qcharlie]} let qconv = decodeQualifiedConvId resp let access = ConversationAccessData (Set.singleton CodeAccess) (Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole, GuestAccessRole, ServiceAccessRole]) @@ -315,7 +317,7 @@ accessUpdateWithRemotes = do frRPC req @?= "on-conversation-updated" Right cu <- pure . eitherDecode . frBody $ req F.cuConvId cu @?= qUnqualified qconv - F.cuAction cu @?= ConversationActionAccessUpdate access + F.cuAction cu @?= SomeConversationAction (sing @'ConversationAccessDataTag) access F.cuAlreadyPresentUsers cu @?= [qUnqualified qalice] liftIO . WS.assertMatchN_ (5 # Second) [wsB, wsC] $ \n -> do diff --git a/services/galley/test/integration/API/Teams.hs b/services/galley/test/integration/API/Teams.hs index f16d2178e71..c827543df9f 100644 --- a/services/galley/test/integration/API/Teams.hs +++ b/services/galley/test/integration/API/Teams.hs @@ -36,6 +36,7 @@ import Control.Retry import Data.Aeson hiding (json) import Data.ByteString.Conversion import Data.ByteString.Lazy (fromStrict) +import qualified Data.Code as Code import Data.Csv (FromNamedRecord (..), decodeByName) import qualified Data.Currency as Currency import Data.Default @@ -49,6 +50,7 @@ import Data.Qualified import Data.Range import qualified Data.Set as Set import qualified Data.Text as T +import Data.Text.Ascii (AsciiChars (validate)) import qualified Data.UUID as UUID import qualified Data.UUID.Util as UUID import qualified Data.UUID.V1 as UUID @@ -75,10 +77,12 @@ import Test.Tasty.HUnit import TestHelpers (test, viewFederationDomain) import TestSetup (TestM, TestSetup, tsBrig, tsCannon, tsGConf, tsGalley) import UnliftIO (mapConcurrently, mapConcurrently_) +import Wire.API.Team (Icon (..)) import Wire.API.Team.Export (TeamExportUser (..)) import qualified Wire.API.Team.Feature as Public import qualified Wire.API.Team.Member as Member import qualified Wire.API.Team.Member as TM +import qualified Wire.API.User as Public import qualified Wire.API.User as U tests :: IO TestSetup -> TestTree @@ -120,6 +124,7 @@ tests s = test s "add team conversation (no role as argument)" testAddTeamConvLegacy, test s "add team conversation with role" testAddTeamConvWithRole, test s "add team conversation as partner (fail)" testAddTeamConvAsExternalPartner, + test s "add team MLS conversation" testCreateTeamMLSConv, -- Queue is emptied here to ensure that lingering events do not affect other tests test s "add team member to conversation without connection" (testAddTeamMemberToConv >> ensureQueueEmpty), test s "update conversation as member" (testUpdateTeamConv RoleMember roleNameWireAdmin), @@ -127,6 +132,13 @@ tests s = test s "delete binding team internal single member" testDeleteBindingTeamSingleMember, test s "delete binding team (owner has passwd)" (testDeleteBindingTeam True), test s "delete binding team (owner has no passwd)" (testDeleteBindingTeam False), + testGroup + "delete team - verification code" + [ test s "success" testDeleteTeamVerificationCodeSuccess, + test s "wrong code" testDeleteTeamVerificationCodeWrongCode, + test s "missing code" testDeleteTeamVerificationCodeMissingCode, + test s "expired code" testDeleteTeamVerificationCodeExpiredCode + ], test s "delete team conversation" testDeleteTeamConv, test s "update team data" testUpdateTeam, test s "update team data icon validation" testUpdateTeamIconValidation, @@ -204,7 +216,7 @@ testCreateMultipleBindingTeams = do _ <- Util.createBindingTeamInternal "foo" owner assertQueue "create team" tActivate -- Cannot create more teams if bound (used internal API) - let nt = NonBindingNewTeam $ newNewTeam (unsafeRange "owner") (unsafeRange "icon") + let nt = NonBindingNewTeam $ newNewTeam (unsafeRange "owner") DefaultIcon post (g . path "/teams" . zUser owner . zConn "conn" . json nt) !!! const 403 === statusCode -- If never used the internal API, can create multiple teams @@ -865,6 +877,31 @@ testAddTeamConvWithRole = do -- ... but not to regular ones. Util.assertNotConvMember (mem1 ^. userId) cid2 +testCreateTeamMLSConv :: TestM () +testCreateTeamMLSConv = do + c <- view tsCannon + owner <- Util.randomUser + lOwner <- flip toLocalUnsafe owner <$> viewFederationDomain + extern <- Util.randomUser + tid <- Util.createNonBindingTeam "foo" owner [] + WS.bracketR2 c owner extern $ \(wsOwner, wsExtern) -> do + lConvId <- + Util.createMLSTeamConv + lOwner + tid + mempty + (Just "Team MLS conversation") + Nothing + Nothing + Nothing + Nothing + Right conv <- fmap cnvMetadata . responseJsonError <$> getConv owner (tUnqualified lConvId) + liftIO $ do + assertEqual "protocol mismatch" ProtocolMLS (cnvmProtocol conv) + assertEqual "group ID mismatch" True (isJust . cnvmGroupId $ conv) + checkConvCreateEvent (tUnqualified lConvId) wsOwner + WS.assertNoEvent (2 # Second) [wsExtern] + testAddTeamConvAsExternalPartner :: TestM () testAddTeamConvAsExternalPartner = do (owner, tid) <- Util.createBindingTeam @@ -1052,6 +1089,116 @@ testDeleteBindingTeamSingleMember = do -- Let's clean the queue, just in case ensureQueueEmpty +testDeleteTeamVerificationCodeSuccess :: TestM () +testDeleteTeamVerificationCodeSuccess = do + g <- view tsGalley + (owner, tid) <- Util.createBindingTeam' + let Just email = U.userEmail owner + setFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge tid Public.Unlocked + setTeamSndFactorPasswordChallenge tid Public.TeamFeatureEnabled + generateVerificationCode $ Public.SendVerificationCode Public.DeleteTeam email + code <- getVerificationCode (U.userId owner) Public.DeleteTeam + delete + ( g + . paths ["teams", toByteString' tid] + . zUser (U.userId owner) + . zConn "conn" + . json (newTeamDeleteDataWithCode (Just Util.defPassword) (Just code)) + ) + !!! do + const 202 === statusCode + tryAssertQueue 10 "team delete, should be there" tDelete + assertQueueEmpty + +testDeleteTeamVerificationCodeMissingCode :: TestM () +testDeleteTeamVerificationCodeMissingCode = do + g <- view tsGalley + (owner, tid) <- Util.createBindingTeam' + setFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge tid Public.Unlocked + setTeamSndFactorPasswordChallenge tid Public.TeamFeatureEnabled + let Just email = U.userEmail owner + generateVerificationCode $ Public.SendVerificationCode Public.DeleteTeam email + delete + ( g + . paths ["teams", toByteString' tid] + . zUser (U.userId owner) + . zConn "conn" + . json (newTeamMemberDeleteData (Just Util.defPassword)) + ) + !!! do + const 403 === statusCode + const "code-authentication-required" === (Error.label . responseJsonUnsafeWithMsg "error label") + assertQueueEmpty + +testDeleteTeamVerificationCodeExpiredCode :: TestM () +testDeleteTeamVerificationCodeExpiredCode = do + g <- view tsGalley + (owner, tid) <- Util.createBindingTeam' + setFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge tid Public.Unlocked + setTeamSndFactorPasswordChallenge tid Public.TeamFeatureEnabled + let Just email = U.userEmail owner + generateVerificationCode $ Public.SendVerificationCode Public.DeleteTeam email + code <- getVerificationCode (U.userId owner) Public.DeleteTeam + -- wait > 5 sec for the code to expire (assumption: setVerificationTimeout in brig.integration.yaml is set to <= 5 sec) + threadDelay $ (5 * 1000 * 1000) + 600 * 1000 + delete + ( g + . paths ["teams", toByteString' tid] + . zUser (U.userId owner) + . zConn "conn" + . json (newTeamDeleteDataWithCode (Just Util.defPassword) (Just code)) + ) + !!! do + const 403 === statusCode + const "code-authentication-failed" === (Error.label . responseJsonUnsafeWithMsg "error label") + assertQueueEmpty + +testDeleteTeamVerificationCodeWrongCode :: TestM () +testDeleteTeamVerificationCodeWrongCode = do + g <- view tsGalley + (owner, tid) <- Util.createBindingTeam' + setFeatureLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge tid Public.Unlocked + setTeamSndFactorPasswordChallenge tid Public.TeamFeatureEnabled + let Just email = U.userEmail owner + generateVerificationCode $ Public.SendVerificationCode Public.DeleteTeam email + let wrongCode = Code.Value $ unsafeRange (fromRight undefined (validate "123456")) + delete + ( g + . paths ["teams", toByteString' tid] + . zUser (U.userId owner) + . zConn "conn" + . json (newTeamDeleteDataWithCode (Just Util.defPassword) (Just wrongCode)) + ) + !!! do + const 403 === statusCode + const "code-authentication-failed" === (Error.label . responseJsonUnsafeWithMsg "error label") + assertQueueEmpty + +setFeatureLockStatus :: forall (a :: Public.TeamFeatureName). (Public.KnownTeamFeatureName a) => TeamId -> Public.LockStatusValue -> TestM () +setFeatureLockStatus tid status = do + g <- view tsGalley + put (g . paths ["i", "teams", toByteString' tid, "features", toByteString' $ Public.knownTeamFeatureName @a, toByteString' status]) !!! const 200 === statusCode + +generateVerificationCode :: Public.SendVerificationCode -> TestM () +generateVerificationCode req = do + brig <- view tsBrig + let js = RequestBodyLBS $ encode req + post (brig . paths ["verification-code", "send"] . contentJson . body js) !!! const 200 === statusCode + +setTeamSndFactorPasswordChallenge :: TeamId -> Public.TeamFeatureStatusValue -> TestM () +setTeamSndFactorPasswordChallenge tid status = do + g <- view tsGalley + let js = RequestBodyLBS $ encode $ Public.TeamFeatureStatusNoConfig status + put (g . paths ["i", "teams", toByteString' tid, "features", toByteString' Public.TeamFeatureSndFactorPasswordChallenge] . contentJson . body js) !!! const 200 === statusCode + +getVerificationCode :: UserId -> Public.VerificationAction -> TestM Code.Value +getVerificationCode uid action = do + brig <- view tsBrig + resp <- + get (brig . paths ["i", "users", toByteString' uid, "verification-code", toByteString' action]) + TestM () testDeleteBindingTeam ownerHasPassword = do g <- view tsGalley @@ -1532,6 +1679,11 @@ testBillingInLargeTeamWithoutIndexedBillingTeamMembers = do . json change ) +-- | @SF.Management @TSFI.RESTfulAPI @S2 +-- This test covers: +-- Promotion, demotion of team roles. +-- Demotion by superior roles is allowed. +-- Demotion by inferior roles is NOT allowed. testUpdateTeamMember :: TestM () testUpdateTeamMember = do g <- view tsGalley @@ -1596,6 +1748,8 @@ testUpdateTeamMember = do e ^. eventTeam @?= tid e ^. eventData @?= EdMemberUpdate uid mPerm +-- @END + testUpdateTeamStatus :: TestM () testUpdateTeamStatus = do g <- view tsGalley diff --git a/services/galley/test/integration/API/Teams/Feature.hs b/services/galley/test/integration/API/Teams/Feature.hs index 7d122c4a398..f74e1877b53 100644 --- a/services/galley/test/integration/API/Teams/Feature.hs +++ b/services/galley/test/integration/API/Teams/Feature.hs @@ -73,7 +73,7 @@ tests s = test s "ConversationGuestLinks - public API" testGuestLinksPublic, test s "ConversationGuestLinks - internal API" testGuestLinksInternal, test s "ConversationGuestLinks - lock status" $ testSimpleFlagWithLockStatus @'Public.TeamFeatureGuestLinks Public.TeamFeatureEnabled Public.Unlocked, - test s "SndFactorPasswordChallenge - lock status" $ testSimpleFlagWithLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge Public.TeamFeatureDisabled Public.Unlocked + test s "SndFactorPasswordChallenge - lock status" $ testSimpleFlagWithLockStatus @'Public.TeamFeatureSndFactorPasswordChallenge Public.TeamFeatureDisabled Public.Locked ] testSSO :: TestM () @@ -681,7 +681,7 @@ testAllFeatures = do Public.Unlocked, toS TeamFeatureValidateSAMLEmails .= Public.TeamFeatureStatusNoConfig TeamFeatureEnabled, toS TeamFeatureGuestLinks .= Public.TeamFeatureStatusNoConfigAndLockStatus TeamFeatureEnabled Public.Unlocked, - toS TeamFeatureSndFactorPasswordChallenge .= Public.TeamFeatureStatusNoConfigAndLockStatus TeamFeatureDisabled Public.Unlocked + toS TeamFeatureSndFactorPasswordChallenge .= Public.TeamFeatureStatusNoConfigAndLockStatus TeamFeatureDisabled Public.Locked ] toS :: TeamFeatureName -> Aeson.Key toS = AesonKey.fromText . TE.decodeUtf8 . toByteString' diff --git a/services/galley/test/integration/API/Teams/LegalHold.hs b/services/galley/test/integration/API/Teams/LegalHold.hs index 22c7e9398cf..ad65f1640ce 100644 --- a/services/galley/test/integration/API/Teams/LegalHold.hs +++ b/services/galley/test/integration/API/Teams/LegalHold.hs @@ -1742,6 +1742,7 @@ instance FromJSON Ev.ClientEvent where Nothing Nothing (Client.ClientCapabilityList mempty) + mempty instance FromJSON Ev.ConnectionEvent where parseJSON = Aeson.withObject "ConnectionEvent" $ \o -> do diff --git a/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs b/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs index 1ed84312094..505750a9859 100644 --- a/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs +++ b/services/galley/test/integration/API/Teams/LegalHold/DisabledByDefault.hs @@ -1276,6 +1276,7 @@ instance FromJSON Ev.ClientEvent where Nothing Nothing (Client.ClientCapabilityList mempty) + mempty instance FromJSON Ev.ConnectionEvent where parseJSON = Aeson.withObject "ConnectionEvent" $ \o -> do diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs index 73fd845971e..50eb657990b 100644 --- a/services/galley/test/integration/API/Util.hs +++ b/services/galley/test/integration/API/Util.hs @@ -41,7 +41,6 @@ import qualified Data.ByteString.Lazy as Lazy import qualified Data.CaseInsensitive as CI import qualified Data.Code as Code import qualified Data.Currency as Currency -import Data.Data (Proxy (Proxy)) import Data.Default import Data.Domain import qualified Data.Handle as Handle @@ -60,10 +59,12 @@ import Data.Qualified import Data.Range import Data.Serialize (runPut) import qualified Data.Set as Set +import Data.Singletons import Data.String.Conversions (ST, cs) import qualified Data.Text.Encoding as Text import qualified Data.Text.Lazy.Encoding as T import Data.Time (getCurrentTime) +import Data.Tuple.Extra import qualified Data.UUID as UUID import Data.UUID.V4 import Federator.MockServer (FederatedRequest (..)) @@ -79,6 +80,7 @@ import Galley.Types.Conversations.Roles hiding (DeleteConversation) import Galley.Types.Teams hiding (Event, EventType (..), self) import qualified Galley.Types.Teams as Team import Galley.Types.Teams.Intra +import Galley.Types.UserList import Gundeck.Types.Notification ( Notification (..), NotificationId, @@ -119,8 +121,9 @@ import Wire.API.Message import qualified Wire.API.Message.Proto as Proto import Wire.API.Routes.Internal.Brig.Connection import Wire.API.Routes.MultiTablePaging +import Wire.API.Team (Icon (..)) import Wire.API.Team.Member (mkNewTeamMember) -import Wire.API.User.Client (ClientCapability (..), UserClientsFull (UserClientsFull)) +import Wire.API.User.Client import qualified Wire.API.User.Client as Client import Wire.API.User.Identity (mkSimpleSampleUref) @@ -145,13 +148,17 @@ symmPermissions p = let s = Set.fromList p in fromJust (newPermissions s s) createBindingTeam :: HasCallStack => TestM (UserId, TeamId) createBindingTeam = do - ownerid <- randomTeamCreator - teams <- getTeams ownerid [] + (first Brig.Types.userId) <$> createBindingTeam' + +createBindingTeam' :: HasCallStack => TestM (User, TeamId) +createBindingTeam' = do + owner <- randomTeamCreator' + teams <- getTeams (Brig.Types.userId owner) [] let [team] = view teamListTeams teams let tid = view teamId team SQS.assertQueue "create team" SQS.tActivate refreshIndex - pure (ownerid, tid) + pure (owner, tid) createBindingTeamWithMembers :: HasCallStack => Int -> TestM (TeamId, UserId, [UserId]) createBindingTeamWithMembers numUsers = do @@ -225,7 +232,7 @@ createNonBindingTeam :: HasCallStack => Text -> UserId -> [TeamMember] -> TestM createNonBindingTeam name owner mems = do g <- view tsGalley let mm = if null mems then Nothing else Just $ unsafeRange (take 127 mems) - let nt = NonBindingNewTeam $ newNewTeam (unsafeRange name) (unsafeRange "icon") & newTeamMembers .~ mm + let nt = NonBindingNewTeam $ newNewTeam (unsafeRange name) DefaultIcon & newTeamMembers .~ mm resp <- post (g . path "/teams" . zUser owner . zConn "conn" . zType "access" . json nt) Text -> UserId -> TestM T createBindingTeamInternalNoActivate name owner = do g <- view tsGalley tid <- randomId - let nt = BindingNewTeam $ newNewTeam (unsafeRange name) (unsafeRange "icon") + let nt = BindingNewTeam $ newNewTeam (unsafeRange name) DefaultIcon _ <- put (g . paths ["/i/teams", toByteString' tid] . zUser owner . zConn "conn" . zType "access" . json nt) >= \r -> fromBS (getHeader' "Location" r) -createTeamConv :: HasCallStack => UserId -> TeamId -> [UserId] -> Maybe Text -> Maybe (Set Access) -> Maybe Milliseconds -> TestM ConvId +createTeamConv :: + HasCallStack => + UserId -> + TeamId -> + [UserId] -> + Maybe Text -> + Maybe (Set Access) -> + Maybe Milliseconds -> + TestM ConvId createTeamConv u tid us name acc mtimer = createTeamConvAccess u tid us name acc Nothing mtimer (Just roleNameWireAdmin) -createTeamConvWithRole :: HasCallStack => UserId -> TeamId -> [UserId] -> Maybe Text -> Maybe (Set Access) -> Maybe Milliseconds -> RoleName -> TestM ConvId +createTeamConvWithRole :: + HasCallStack => + UserId -> + TeamId -> + [UserId] -> + Maybe Text -> + Maybe (Set Access) -> + Maybe Milliseconds -> + RoleName -> + TestM ConvId createTeamConvWithRole u tid us name acc mtimer convRole = createTeamConvAccess u tid us name acc Nothing mtimer (Just convRole) -createTeamConvAccess :: HasCallStack => UserId -> TeamId -> [UserId] -> Maybe Text -> Maybe (Set Access) -> Maybe (Set AccessRoleV2) -> Maybe Milliseconds -> Maybe RoleName -> TestM ConvId +createTeamConvAccess :: + HasCallStack => + UserId -> + TeamId -> + [UserId] -> + Maybe Text -> + Maybe (Set Access) -> + Maybe (Set AccessRoleV2) -> + Maybe Milliseconds -> + Maybe RoleName -> + TestM ConvId createTeamConvAccess u tid us name acc role mtimer convRole = do r <- createTeamConvAccessRaw u tid us name acc role mtimer convRole TeamId -> [UserId] -> Maybe Text -> Maybe (Set Access) -> Maybe (Set AccessRoleV2) -> Maybe Milliseconds -> Maybe RoleName -> TestM ResponseLBS +createTeamConvAccessRaw :: + UserId -> + TeamId -> + [UserId] -> + Maybe Text -> + Maybe (Set Access) -> + Maybe (Set AccessRoleV2) -> + Maybe Milliseconds -> + Maybe RoleName -> + TestM ResponseLBS createTeamConvAccessRaw u tid us name acc role mtimer convRole = do g <- view tsGalley let tinfo = ConvTeamInfo tid let conv = - NewConv us [] name (fromMaybe (Set.fromList []) acc) role (Just tinfo) mtimer Nothing (fromMaybe roleNameWireAdmin convRole) + NewConv us [] name (fromMaybe (Set.fromList []) acc) role (Just tinfo) mtimer Nothing (fromMaybe roleNameWireAdmin convRole) ProtocolProteus post ( g . path "/conversations" @@ -525,6 +568,45 @@ createTeamConvAccessRaw u tid us name acc role mtimer convRole = do . json conv ) +-- | Create a team MLS conversation +createMLSTeamConv :: + (HasGalley m, MonadIO m, MonadHttp m, HasCallStack) => + Local UserId -> + TeamId -> + UserList UserId -> + Maybe Text -> + Maybe (Set Access) -> + Maybe (Set AccessRoleV2) -> + Maybe Milliseconds -> + Maybe RoleName -> + m (Local ConvId) +createMLSTeamConv lusr tid users name access role timer convRole = do + g <- viewGalley + let conv = + NewConv + { newConvUsers = [], + newConvQualifiedUsers = ulAll lusr users, + newConvName = name, + newConvAccess = fromMaybe Set.empty access, + newConvAccessRoles = role, + newConvTeam = Just . ConvTeamInfo $ tid, + newConvMessageTimer = timer, + newConvUsersRole = fromMaybe roleNameWireAdmin convRole, + newConvReceiptMode = Nothing, + newConvProtocol = ProtocolMLS + } + r <- + post + ( g + . path "/conversations" + . zUser (tUnqualified lusr) + . zConn "conn" + . zType "access" + . json conv + ) + convId <- fromBS (getHeader' "Location" r) + pure $ qualifyAs lusr convId + updateTeamConv :: UserId -> ConvId -> ConversationRename -> TestM ResponseLBS updateTeamConv zusr convid upd = do g <- view tsGalley @@ -541,14 +623,46 @@ createOne2OneTeamConv :: UserId -> UserId -> Maybe Text -> TeamId -> TestM Respo createOne2OneTeamConv u1 u2 n tid = do g <- view tsGalley let conv = - NewConv [u2] [] n mempty Nothing (Just $ ConvTeamInfo tid) Nothing Nothing roleNameWireAdmin + NewConv [u2] [] n mempty Nothing (Just $ ConvTeamInfo tid) Nothing Nothing roleNameWireAdmin ProtocolProteus post $ g . path "/conversations/one2one" . zUser u1 . zConn "conn" . zType "access" . json conv -postConv :: UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe (Set AccessRoleV2) -> Maybe Milliseconds -> TestM ResponseLBS +postConv :: + UserId -> + [UserId] -> + Maybe Text -> + [Access] -> + Maybe (Set AccessRoleV2) -> + Maybe Milliseconds -> + TestM ResponseLBS postConv u us name a r mtimer = postConvWithRole u us name a r mtimer roleNameWireAdmin -defNewConv :: NewConv -defNewConv = NewConv [] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin +-- | Create a group MLS conversation +postMLSConv :: + Local UserId -> + UserList UserId -> + Maybe Text -> + [Access] -> + Maybe (Set AccessRoleV2) -> + Maybe Milliseconds -> + TestM ResponseLBS +postMLSConv lusr us name access r timer = + postConvQualified + (tUnqualified lusr) + NewConv + { newConvUsers = [], + newConvQualifiedUsers = ulAll lusr us, + newConvName = name, + newConvAccess = Set.fromList access, + newConvAccessRoles = r, + newConvTeam = Nothing, + newConvMessageTimer = timer, + newConvUsersRole = roleNameWireAdmin, + newConvReceiptMode = Nothing, + newConvProtocol = ProtocolMLS + } + +defNewProteusConv :: NewConv +defNewProteusConv = NewConv [] [] Nothing mempty Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolProteus postConvQualified :: (HasCallStack, HasGalley m, MonadIO m, MonadMask m, MonadHttp m) => @@ -583,7 +697,7 @@ postConvWithRemoteUsers u n = postTeamConv :: TeamId -> UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe (Set AccessRoleV2) -> Maybe Milliseconds -> TestM ResponseLBS postTeamConv tid u us name a r mtimer = do g <- view tsGalley - let conv = NewConv us [] name (Set.fromList a) r (Just (ConvTeamInfo tid)) mtimer Nothing roleNameWireAdmin + let conv = NewConv us [] name (Set.fromList a) r (Just (ConvTeamInfo tid)) mtimer Nothing roleNameWireAdmin ProtocolProteus post $ g . path "/conversations" . zUser u . zConn "conn" . zType "access" . json conv deleteTeamConv :: (HasGalley m, MonadIO m, MonadHttp m) => TeamId -> ConvId -> UserId -> m ResponseLBS @@ -596,11 +710,19 @@ deleteTeamConv tid convId zusr = do . zConn "conn" ) -postConvWithRole :: UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe (Set AccessRoleV2) -> Maybe Milliseconds -> RoleName -> TestM ResponseLBS +postConvWithRole :: + UserId -> + [UserId] -> + Maybe Text -> + [Access] -> + Maybe (Set AccessRoleV2) -> + Maybe Milliseconds -> + RoleName -> + TestM ResponseLBS postConvWithRole u members name access arole timer role = postConvQualified u - defNewConv + defNewProteusConv { newConvUsers = members, newConvName = name, newConvAccess = Set.fromList access, @@ -612,7 +734,7 @@ postConvWithRole u members name access arole timer role = postConvWithReceipt :: UserId -> [UserId] -> Maybe Text -> [Access] -> Maybe (Set AccessRoleV2) -> Maybe Milliseconds -> ReceiptMode -> TestM ResponseLBS postConvWithReceipt u us name a r mtimer rcpt = do g <- view tsGalley - let conv = NewConv us [] name (Set.fromList a) r Nothing mtimer (Just rcpt) roleNameWireAdmin + let conv = NewConv us [] name (Set.fromList a) r Nothing mtimer (Just rcpt) roleNameWireAdmin ProtocolProteus post $ g . path "/conversations" . zUser u . zConn "conn" . zType "access" . json conv postSelfConv :: UserId -> TestM ResponseLBS @@ -623,7 +745,7 @@ postSelfConv u = do postO2OConv :: UserId -> UserId -> Maybe Text -> TestM ResponseLBS postO2OConv u1 u2 n = do g <- view tsGalley - let conv = NewConv [u2] [] n mempty Nothing Nothing Nothing Nothing roleNameWireAdmin + let conv = NewConv [u2] [] n mempty Nothing Nothing Nothing Nothing roleNameWireAdmin ProtocolProteus post $ g . path "/conversations/one2one" . zUser u1 . zConn "conn" . zType "access" . json conv postConnectConv :: UserId -> UserId -> Text -> Text -> Maybe Text -> TestM ResponseLBS @@ -1559,7 +1681,17 @@ assertRemoveUpdate req qconvId remover alreadyPresentUsers victim = liftIO $ do cuOrigUserId cu @?= remover cuConvId cu @?= qUnqualified qconvId sort (cuAlreadyPresentUsers cu) @?= sort alreadyPresentUsers - cuAction cu @?= ConversationActionRemoveMembers (pure victim) + cuAction cu @?= SomeConversationAction (sing @'ConversationRemoveMembersTag) (pure victim) + +assertLeaveUpdate :: (MonadIO m, HasCallStack) => FederatedRequest -> Qualified ConvId -> Qualified UserId -> [UserId] -> Qualified UserId -> m () +assertLeaveUpdate req qconvId remover alreadyPresentUsers victim = liftIO $ do + frRPC req @?= "on-conversation-updated" + frOriginDomain req @?= qDomain qconvId + let Just cu = decode (frBody req) + cuOrigUserId cu @?= remover + cuConvId cu @?= qUnqualified qconvId + sort (cuAlreadyPresentUsers cu) @?= sort alreadyPresentUsers + cuAction cu @?= SomeConversationAction (sing @'ConversationLeaveTag) (pure victim) ------------------------------------------------------------------------------- -- Helpers @@ -1788,8 +1920,14 @@ randomQualifiedId domain = Qualified <$> randomId <*> pure domain randomTeamCreator :: HasCallStack => TestM UserId randomTeamCreator = qUnqualified <$> randomUser' True True True +randomTeamCreator' :: HasCallStack => TestM User +randomTeamCreator' = randomUser'' True True True + randomUser' :: HasCallStack => Bool -> Bool -> Bool -> TestM (Qualified UserId) -randomUser' isCreator hasPassword hasEmail = userQualifiedId . selfUser <$> randomUserProfile' isCreator hasPassword hasEmail +randomUser' isCreator hasPassword hasEmail = userQualifiedId <$> randomUser'' isCreator hasPassword hasEmail + +randomUser'' :: HasCallStack => Bool -> Bool -> Bool -> TestM User +randomUser'' isCreator hasPassword hasEmail = selfUser <$> randomUserProfile' isCreator hasPassword hasEmail randomUserProfile' :: HasCallStack => Bool -> Bool -> Bool -> TestM SelfProfile randomUserProfile' isCreator hasPassword hasEmail = do @@ -1800,7 +1938,7 @@ randomUserProfile' isCreator hasPassword hasEmail = do ["name" .= fromEmail e] <> ["password" .= defPassword | hasPassword] <> ["email" .= fromEmail e | hasEmail] - <> ["team" .= Team.BindingNewTeam (Team.newNewTeam (unsafeRange "teamName") (unsafeRange "defaultIcon")) | isCreator] + <> ["team" .= Team.BindingNewTeam (Team.newNewTeam (unsafeRange "teamName") DefaultIcon) | isCreator] responseJsonUnsafe <$> (post (b . path "/i/users" . json p) TestM UserId @@ -2102,13 +2240,13 @@ someLastPrekeys = lastPrekey "pQABARn//wKhAFgg1rZEY6vbAnEz+Ern5kRny/uKiIrXTb/usQxGnceV2HADoQChAFgglacihnqg/YQJHkuHNFU7QD6Pb3KN4FnubaCF2EVOgRkE9g==" ] -mkConv :: +mkProteusConv :: ConvId -> UserId -> RoleName -> [OtherMember] -> RemoteConversation -mkConv cnvId creator selfRole otherMembers = +mkProteusConv cnvId creator selfRole otherMembers = RemoteConversation cnvId ( ConversationMetadata @@ -2120,6 +2258,8 @@ mkConv cnvId creator selfRole otherMembers = Nothing Nothing Nothing + ProtocolProteus + Nothing ) (RemoteConvMembers selfRole otherMembers) @@ -2234,7 +2374,7 @@ putCapabilities zusr cid caps = do ( brig . zUser zusr . paths ["clients", toByteString' cid] - . json (UpdateClient mempty Nothing Nothing (Just . Set.fromList $ caps)) + . json defUpdateClient {updateClientCapabilities = Just (Set.fromList caps)} . expect2xx ) @@ -2434,7 +2574,7 @@ checkTeamUpdateEvent tid upd w = WS.assertMatch_ checkTimeout w $ \notif -> do e ^. eventTeam @?= tid e ^. eventData @?= EdTeamUpdate upd -checkConvCreateEvent :: HasCallStack => ConvId -> WS.WebSocket -> TestM () +checkConvCreateEvent :: (MonadIO m, MonadCatch m) => HasCallStack => ConvId -> WS.WebSocket -> m () checkConvCreateEvent cid w = WS.assertMatch_ checkTimeout w $ \notif -> do ntfTransient notif @?= False let e = List1.head (WS.unpackPayload notif) @@ -2526,6 +2666,10 @@ assertOne :: (HasCallStack, MonadIO m, Show a) => [a] -> m a assertOne [a] = pure a assertOne xs = liftIO . assertFailure $ "Expected exactly one element, found " <> show xs +assertNone :: (HasCallStack, MonadIO m, Show a) => [a] -> m () +assertNone [] = pure () +assertNone xs = liftIO . assertFailure $ "Expected exactly no elements, found " <> show xs + assertJust :: (HasCallStack, MonadIO m) => Maybe a -> m a assertJust (Just a) = pure a assertJust Nothing = liftIO $ assertFailure "Expected Just, got Nothing" diff --git a/services/galley/test/unit/Test/Galley/Mapping.hs b/services/galley/test/unit/Test/Galley/Mapping.hs index 7da112f4762..cebd5fcb31a 100644 --- a/services/galley/test/unit/Test/Galley/Mapping.hs +++ b/services/galley/test/unit/Test/Galley/Mapping.hs @@ -129,6 +129,8 @@ genConversation = <*> pure (Just False) <*> pure Nothing <*> pure Nothing + <*> pure (Just ProtocolProteus) + <*> pure Nothing newtype RandomConversation = RandomConversation {unRandomConversation :: Data.Conversation} diff --git a/services/spar/src/Spar/Error.hs b/services/spar/src/Spar/Error.hs index 9864bf82140..d4ce48c6ef8 100644 --- a/services/spar/src/Spar/Error.hs +++ b/services/spar/src/Spar/Error.hs @@ -88,6 +88,8 @@ data SparCustomError | SparCannotCreateUsersOnReplacedIdP LT | SparCouldNotParseRfcResponse LT LT | SparReAuthRequired + | SparReAuthCodeAuthFailed + | SparReAuthCodeAuthRequired | SparCouldNotRetrieveCookie | SparCassandraError LT | SparCassandraTTLError TTLError @@ -146,6 +148,8 @@ renderSparError (SAML.CustomError (SparCannotCreateUsersOnReplacedIdP replacingI -- RFC-specific errors renderSparError (SAML.CustomError (SparCouldNotParseRfcResponse service msg)) = Right $ Wai.mkError status502 "bad-upstream" ("Could not parse " <> service <> " response body: " <> msg) renderSparError (SAML.CustomError SparReAuthRequired) = Right $ Wai.mkError status403 "access-denied" "This operation requires reauthentication." +renderSparError (SAML.CustomError SparReAuthCodeAuthFailed) = Right $ Wai.mkError status403 "code-authentication-failed" "Reauthentication failed with invalid verification code." +renderSparError (SAML.CustomError SparReAuthCodeAuthRequired) = Right $ Wai.mkError status403 "code-authentication-required" "Reauthentication failed. Verification code required." renderSparError (SAML.CustomError SparCouldNotRetrieveCookie) = Right $ Wai.mkError status502 "bad-upstream" "Unable to get a cookie from an upstream server." renderSparError (SAML.CustomError (SparCassandraError msg)) = Right $ Wai.mkError status500 "server-error" msg -- TODO: should we be more specific here and make it 'db-error'? renderSparError (SAML.CustomError (SparCassandraTTLError ttlerr)) = Right $ Wai.mkError status400 "ttl-error" (cs $ show ttlerr) diff --git a/services/spar/src/Spar/Intra/Brig.hs b/services/spar/src/Spar/Intra/Brig.hs index b5815ff5219..3e0a04cd898 100644 --- a/services/spar/src/Spar/Intra/Brig.hs +++ b/services/spar/src/Spar/Intra/Brig.hs @@ -48,9 +48,11 @@ import Brig.Types.User import Brig.Types.User.Auth (SsoLogin (..)) import Control.Monad.Except import Data.ByteString.Conversion +import Data.Code as Code import Data.Handle (Handle (fromHandle)) import Data.Id (Id (Id), TeamId, UserId) import Data.Misc (PlainTextPassword) +import qualified Data.Text.Lazy as Lazy import Imports import Network.HTTP.Types.Method import qualified Network.Wai.Utilities.Error as Wai @@ -60,13 +62,13 @@ import qualified System.Logger.Class as Log import Web.Cookie import Wire.API.User import Wire.API.User.RichInfo as RichInfo -import Wire.API.User.Scim (ValidExternalId (..), runValidExternalId) +import Wire.API.User.Scim (ValidExternalId (..), runValidExternalIdEither) ---------------------------------------------------------------------- -- | FUTUREWORK: this is redundantly defined in "Spar.Intra.BrigApp". veidToUserSSOId :: ValidExternalId -> UserSSOId -veidToUserSSOId = runValidExternalId UserSSOId (UserScimExternalId . fromEmail) +veidToUserSSOId = runValidExternalIdEither UserSSOId (UserScimExternalId . fromEmail) -- | Similar to 'Network.Wire.Client.API.Auth.tokenResponse', but easier: we just need to set the -- cookie in the response, and the redirect will make the client negotiate a fresh auth token. @@ -319,22 +321,25 @@ ensureReAuthorised :: (HasCallStack, MonadSparToBrig m) => Maybe UserId -> Maybe PlainTextPassword -> + Maybe Code.Value -> + Maybe VerificationAction -> m () -ensureReAuthorised Nothing _ = throwSpar SparMissingZUsr -ensureReAuthorised (Just uid) secret = do +ensureReAuthorised Nothing _ _ _ = throwSpar SparMissingZUsr +ensureReAuthorised (Just uid) secret mbCode mbAction = do resp <- call $ method GET . paths ["/i/users", toByteString' uid, "reauthenticate"] - . json (ReAuthUser secret) - let sCode = statusCode resp - if - | sCode == 200 -> - pure () - | sCode == 403 -> - throwSpar SparReAuthRequired - | otherwise -> - rethrow "brig" resp + . json (ReAuthUser secret mbCode mbAction) + case (statusCode resp, errorLabel resp) of + (200, _) -> pure () + (403, Just "code-authentication-required") -> throwSpar SparReAuthCodeAuthRequired + (403, Just "code-authentication-failed") -> throwSpar SparReAuthCodeAuthFailed + (403, _) -> throwSpar SparReAuthRequired + (_, _) -> rethrow "brig" resp + where + errorLabel :: ResponseLBS -> Maybe Lazy.Text + errorLabel = fmap Wai.label . responseJsonMaybe -- | Get persistent cookie from brig and redirect user past login process. -- diff --git a/services/spar/src/Spar/Intra/BrigApp.hs b/services/spar/src/Spar/Intra/BrigApp.hs index 2d4b91c7afe..1c5506baa58 100644 --- a/services/spar/src/Spar/Intra/BrigApp.hs +++ b/services/spar/src/Spar/Intra/BrigApp.hs @@ -63,13 +63,13 @@ import qualified Spar.Sem.BrigAccess as BrigAccess import Spar.Sem.GalleyAccess (GalleyAccess) import qualified Spar.Sem.GalleyAccess as GalleyAccess import Wire.API.User -import Wire.API.User.Scim (ValidExternalId (..), runValidExternalId) +import Wire.API.User.Scim (ValidExternalId (..), runValidExternalIdEither) ---------------------------------------------------------------------- -- | FUTUREWORK: this is redundantly defined in "Spar.Intra.Brig" veidToUserSSOId :: ValidExternalId -> UserSSOId -veidToUserSSOId = runValidExternalId UserSSOId (UserScimExternalId . fromEmail) +veidToUserSSOId = runValidExternalIdEither UserSSOId (UserScimExternalId . fromEmail) veidFromUserSSOId :: MonadError String m => UserSSOId -> m ValidExternalId veidFromUserSSOId = \case @@ -112,12 +112,12 @@ veidFromBrigUser usr mIssuer = case (userSSOId usr, userEmail usr, mIssuer) of mkUserName :: Maybe Text -> ValidExternalId -> Either String Name mkUserName (Just n) = const $ mkName n mkUserName Nothing = - runValidExternalId + runValidExternalIdEither (\uref -> mkName (CI.original . SAML.unsafeShowNameID $ uref ^. SAML.uidSubject)) (\email -> mkName (fromEmail email)) renderValidExternalId :: ValidExternalId -> Maybe Text -renderValidExternalId = runValidExternalId urefToExternalId (Just . fromEmail) +renderValidExternalId = runValidExternalIdEither urefToExternalId (Just . fromEmail) ---------------------------------------------------------------------- diff --git a/services/spar/src/Spar/Scim/Auth.hs b/services/spar/src/Spar/Scim/Auth.hs index 43797008658..d6f81484838 100644 --- a/services/spar/src/Spar/Scim/Auth.hs +++ b/services/spar/src/Spar/Scim/Auth.hs @@ -66,8 +66,9 @@ import qualified Web.Scim.Class.Auth as Scim.Class.Auth import qualified Web.Scim.Handler as Scim import qualified Web.Scim.Schema.Error as Scim import Wire.API.Routes.Public.Spar (APIScimToken) +import Wire.API.User as User import Wire.API.User.Saml (Opts, maxScimTokens) -import Wire.API.User.Scim +import Wire.API.User.Scim as Api -- | An instance that tells @hscim@ how authentication should be done for SCIM routes. instance Member ScimTokenStore r => Scim.Class.Auth.AuthDB SparTag (Sem r) where @@ -125,10 +126,10 @@ createScimToken :: -- | Request body CreateScimToken -> Sem r CreateScimTokenResponse -createScimToken zusr CreateScimToken {..} = do +createScimToken zusr Api.CreateScimToken {..} = do let descr = createScimTokenDescr teamid <- Intra.Brig.authorizeScimTokenManagement zusr - BrigAccess.ensureReAuthorised zusr createScimTokenPassword + BrigAccess.ensureReAuthorised zusr createScimTokenPassword createScimTokenCode (Just User.CreateScimToken) tokenNumber <- fmap length $ ScimTokenStore.lookupByTeam teamid maxTokens <- inputs maxScimTokens unless (tokenNumber < maxTokens) $ diff --git a/services/spar/src/Spar/Scim/User.hs b/services/spar/src/Spar/Scim/User.hs index 8ce22b50358..6f425e370fc 100644 --- a/services/spar/src/Spar/Scim/User.hs +++ b/services/spar/src/Spar/Scim/User.hs @@ -141,6 +141,7 @@ instance . logTokenInfo tokeninfo . logFilter filter' ) + logScimUserIds $ do mIdpConfig <- maybe (pure Nothing) (lift . IdPConfigStore.getConfig) stiIdP case filter' of @@ -164,6 +165,7 @@ instance . logUser uid . logTokenInfo tokeninfo ) + logScimUserId $ do mIdpConfig <- maybe (pure Nothing) (lift . IdPConfigStore.getConfig) stiIdP let notfound = Scim.notFound "User" (idToText uid) @@ -185,12 +187,7 @@ instance deleteUser :: ScimTokenInfo -> UserId -> Scim.ScimHandler (Sem r) () deleteUser tokeninfo uid = - logScim - ( logFunction "Spar.Scim.User.deleteUser" - . logUser uid - . logTokenInfo tokeninfo - ) - $ deleteScimUser tokeninfo uid + deleteScimUser tokeninfo uid ---------------------------------------------------------------------------- -- User creation and validation @@ -344,8 +341,14 @@ mkValidExternalId (Just idp) (Just extid) = do Scim.InvalidValue (Just $ "Can't construct a subject ID from externalId: " <> Text.pack err) -logScim :: forall r a. (Member (Logger (Msg -> Msg)) r) => (Msg -> Msg) -> Scim.ScimHandler (Sem r) a -> Scim.ScimHandler (Sem r) a -logScim context action = +logScim :: + forall r a. + (Member (Logger (Msg -> Msg)) r) => + (Msg -> Msg) -> + (a -> (Msg -> Msg)) -> + Scim.ScimHandler (Sem r) a -> + Scim.ScimHandler (Sem r) a +logScim context postcontext action = flip mapExceptT action $ \action' -> do eith <- action' case eith of @@ -357,7 +360,7 @@ logScim context action = Logger.warn $ context . Log.msg errorMsg pure (Left e) Right x -> do - Logger.info $ context . Log.msg @Text "call without exception" + Logger.info $ context . postcontext x . Log.msg @Text "call without exception" pure (Right x) logEmail :: Email -> (Msg -> Msg) @@ -372,6 +375,12 @@ logVSU (ST.ValidScimUser veid handl _name _richInfo _active) = logTokenInfo :: ScimTokenInfo -> (Msg -> Msg) logTokenInfo ScimTokenInfo {stiTeam} = logTeam stiTeam +logScimUserId :: Scim.StoredUser ST.SparTag -> (Msg -> Msg) +logScimUserId = logUser . Scim.id . Scim.thing + +logScimUserIds :: Scim.ListResponse (Scim.StoredUser ST.SparTag) -> (Msg -> Msg) +logScimUserIds lresp = foldl' (.) id (logScimUserId <$> Scim.resources lresp) + veidEmail :: ST.ValidExternalId -> Maybe Email veidEmail (ST.EmailAndUref email _) = Just email veidEmail (ST.UrefOnly _) = Nothing @@ -420,6 +429,7 @@ createValidScimUser tokeninfo@ScimTokenInfo {stiTeam} vsu@(ST.ValidScimUser veid . logVSU vsu . logTokenInfo tokeninfo ) + logScimUserId $ do -- ensure uniqueness constraints of all affected identifiers. -- {if we crash now, retry POST will just work} @@ -432,7 +442,7 @@ createValidScimUser tokeninfo@ScimTokenInfo {stiTeam} vsu@(ST.ValidScimUser veid buid <- lift $ do buid <- - ST.runValidExternalId + ST.runValidExternalIdEither ( \uref -> do -- FUTUREWORK: outsource this and some other fragments from @@ -476,7 +486,11 @@ createValidScimUser tokeninfo@ScimTokenInfo {stiTeam} vsu@(ST.ValidScimUser veid createValidScimUserSpar stiTeam buid storedUser veid -- If applicable, trigger email validation procedure on brig. - lift $ ST.runValidExternalId (validateEmailIfExists buid) (\_ -> pure ()) veid + lift $ + ST.runValidExternalIdEither + (validateEmailIfExists buid) + (\_ -> pure () {- nothing to do; user is sent an invitation that validates the address implicitly -}) + veid -- TODO: suspension via scim is brittle, and may leave active users behind: if we don't -- reach the following line due to a crash, the user will be active. @@ -504,8 +518,12 @@ createValidScimUserSpar :: m () createValidScimUserSpar stiTeam uid storedUser veid = lift $ do ScimUserTimesStore.write storedUser - ST.runValidExternalId - ((`SAMLUserStore.insert` uid)) + -- This uses the "both" variant to always write all applicable index tables, even if + -- `spar.scim_external` is never consulted as long as there is an IdP. This is hoped to + -- mitigate logic errors in this code and corner cases. (eg., if the IdP is later removed?) + ST.runValidExternalIdBoth + (>>) + (`SAMLUserStore.insert` uid) (\email -> ScimExternalIdStore.insert stiTeam email uid) veid @@ -538,6 +556,7 @@ updateValidScimUser tokinfo@ScimTokenInfo {stiTeam} uid newValidScimUser = . logUser uid . logTokenInfo tokinfo ) + logScimUserId $ do oldScimStoredUser :: Scim.StoredUser ST.SparTag <- Scim.getUser tokinfo uid @@ -555,11 +574,11 @@ updateValidScimUser tokinfo@ScimTokenInfo {stiTeam} uid newValidScimUser = newScimStoredUser :: Scim.StoredUser ST.SparTag <- updScimStoredUser (synthesizeScimUser newValidScimUser) oldScimStoredUser - case ( oldValidScimUser ^. ST.vsuExternalId, - newValidScimUser ^. ST.vsuExternalId - ) of - (old, new) | old /= new -> updateVsuUref stiTeam uid old new - _ -> pure () + do + let old = oldValidScimUser ^. ST.vsuExternalId + new = newValidScimUser ^. ST.vsuExternalId + when (old /= new) $ do + updateVsuUref stiTeam uid old new when (newValidScimUser ^. ST.vsuName /= oldValidScimUser ^. ST.vsuName) $ do BrigAccess.setName uid (newValidScimUser ^. ST.vsuName) @@ -593,13 +612,13 @@ updateVsuUref :: ST.ValidExternalId -> Sem r () updateVsuUref team uid old new = do - let geturef = ST.runValidExternalId Just (const Nothing) + let geturef = ST.runValidExternalIdEither Just (const Nothing) case (geturef old, geturef new) of (mo, mn@(Just newuref)) | mo /= mn -> validateEmailIfExists uid newuref _ -> pure () - old & ST.runValidExternalId (SAMLUserStore.delete uid) (ScimExternalIdStore.delete team) - new & ST.runValidExternalId (`SAMLUserStore.insert` uid) (\email -> ScimExternalIdStore.insert team email uid) + old & ST.runValidExternalIdBoth (>>) (SAMLUserStore.delete uid) (ScimExternalIdStore.delete team) + new & ST.runValidExternalIdBoth (>>) (`SAMLUserStore.insert` uid) (\email -> ScimExternalIdStore.insert team email uid) BrigAccess.setVeid uid new @@ -676,6 +695,7 @@ deleteScimUser tokeninfo@ScimTokenInfo {stiTeam, stiIdP} uid = . logTokenInfo tokeninfo . logUser uid ) + (const id) $ do mbBrigUser <- lift (Brig.getBrigUser Brig.WithPendingInvitations uid) case mbBrigUser of @@ -697,7 +717,8 @@ deleteScimUser tokeninfo@ScimTokenInfo {stiTeam, stiIdP} uid = Left _ -> pure () Right veid -> lift $ - ST.runValidExternalId + ST.runValidExternalIdBoth + (>>) (SAMLUserStore.delete uid) (ScimExternalIdStore.delete stiTeam) veid @@ -759,7 +780,8 @@ assertExternalIdInAllowedValues :: Members '[BrigAccess, ScimExternalIdStore, SA assertExternalIdInAllowedValues allowedValues errmsg tid veid = do isGood <- lift $ - ST.runValidExternalId + ST.runValidExternalIdBoth + (\ma mb -> (&&) <$> ma <*> mb) ( \uref -> getUserIdByUref (Just tid) uref <&> \case (Spar.App.GetUserFound uid) -> Just uid `elem` allowedValues @@ -811,6 +833,7 @@ synthesizeStoredUser usr veid = . maybe id logTeam (userTeam . accountUser $ usr) . maybe id logEmail (veidEmail veid) ) + logScimUserId $ do let uid = userId (accountUser usr) accStatus = accountStatus usr @@ -915,8 +938,24 @@ getUserById midp stiTeam uid = do -- function to move it under scim control. assertExternalIdNotUsedElsewhere stiTeam veid uid createValidScimUserSpar stiTeam uid storedUser veid + lift $ do + when (veidChanged (accountUser brigUser) veid) $ do + BrigAccess.setVeid uid veid + when (managedByChanged (accountUser brigUser)) $ do + BrigAccess.setManagedBy uid ManagedByScim pure storedUser _ -> Applicative.empty + where + veidChanged :: BT.User -> ST.ValidExternalId -> Bool + veidChanged usr veid = case BT.userIdentity usr of + Nothing -> True + Just (BT.FullIdentity _ _) -> True + Just (BT.EmailIdentity _) -> True + Just (BT.PhoneIdentity _) -> True + Just (BT.SSOIdentity ssoid _ _) -> Brig.veidToUserSSOId veid /= ssoid + + managedByChanged :: BT.User -> Bool + managedByChanged usr = userManagedBy usr /= ManagedByScim scimFindUserByHandle :: forall r. @@ -965,7 +1004,7 @@ scimFindUserByEmail mIdpConfig stiTeam email = do -- throwing errors returned by 'mkValidExternalId' here, but *not* throw an error if the externalId is -- a UUID, or any other text that is valid according to SCIM. veid <- MaybeT (either (const Nothing) Just <$> runExceptT (mkValidExternalId mIdpConfig (pure email))) - uid <- MaybeT . lift $ ST.runValidExternalId withUref withEmailOnly veid + uid <- MaybeT . lift $ ST.runValidExternalIdEither withUref withEmailOnly veid brigUser <- MaybeT . lift . BrigAccess.getAccount Brig.WithPendingInvitations $ uid getUserById mIdpConfig stiTeam . userId . accountUser $ brigUser where diff --git a/services/spar/src/Spar/Sem/BrigAccess.hs b/services/spar/src/Spar/Sem/BrigAccess.hs index ee1730875c3..8819c666894 100644 --- a/services/spar/src/Spar/Sem/BrigAccess.hs +++ b/services/spar/src/Spar/Sem/BrigAccess.hs @@ -41,6 +41,7 @@ where import Brig.Types.Intra import Brig.Types.User +import Data.Code as Code import Data.Handle (Handle) import Data.Id (TeamId, UserId) import Data.Misc (PlainTextPassword) @@ -48,6 +49,7 @@ import Imports import Polysemy import qualified SAML2.WebSSO as SAML import Web.Cookie +import Wire.API.User (VerificationAction) import Wire.API.User.RichInfo as RichInfo import Wire.API.User.Scim (ValidExternalId (..)) @@ -66,7 +68,7 @@ data BrigAccess m a where GetRichInfo :: UserId -> BrigAccess m RichInfo CheckHandleAvailable :: Handle -> BrigAccess m Bool Delete :: UserId -> BrigAccess m () - EnsureReAuthorised :: Maybe UserId -> Maybe PlainTextPassword -> BrigAccess m () + EnsureReAuthorised :: Maybe UserId -> Maybe PlainTextPassword -> Maybe Code.Value -> Maybe VerificationAction -> BrigAccess m () SsoLogin :: UserId -> BrigAccess m SetCookie GetStatus :: UserId -> BrigAccess m AccountStatus GetStatusMaybe :: UserId -> BrigAccess m (Maybe AccountStatus) diff --git a/services/spar/src/Spar/Sem/BrigAccess/Http.hs b/services/spar/src/Spar/Sem/BrigAccess/Http.hs index 55673fcd415..3f63bc1d600 100644 --- a/services/spar/src/Spar/Sem/BrigAccess/Http.hs +++ b/services/spar/src/Spar/Sem/BrigAccess/Http.hs @@ -54,7 +54,7 @@ brigAccessToHttp mgr req = GetRichInfo itlu -> Intra.getBrigUserRichInfo itlu CheckHandleAvailable h -> Intra.checkHandleAvailable h Delete itlu -> Intra.deleteBrigUser itlu - EnsureReAuthorised mitlu mp -> Intra.ensureReAuthorised mitlu mp + EnsureReAuthorised mitlu mp mc ma -> Intra.ensureReAuthorised mitlu mp mc ma SsoLogin itlu -> Intra.ssoLogin itlu GetStatus itlu -> Intra.getStatus itlu GetStatusMaybe itlu -> Intra.getStatusMaybe itlu diff --git a/services/spar/test-integration/Test/Spar/APISpec.hs b/services/spar/test-integration/Test/Spar/APISpec.hs index 7d129a03ceb..d1d68a8b4eb 100644 --- a/services/spar/test-integration/Test/Spar/APISpec.hs +++ b/services/spar/test-integration/Test/Spar/APISpec.hs @@ -201,6 +201,7 @@ specFinalizeLogin :: SpecWith TestEnv specFinalizeLogin = do describe "POST /sso/finalize-login" $ do -- @SF.Channel @TSFI.RESTfulAPI @S2 @S3 + -- Send authentication error and no cookie if response from SSO IdP was rejected context "rejectsSAMLResponseSayingAccessNotGranted" $ do it "responds with a very peculiar 'forbidden' HTTP response" $ do (_, tid, idp, (_, privcreds)) <- registerTestIdPWithMeta @@ -297,6 +298,7 @@ specFinalizeLogin = do loginSuccess =<< submitAuthnResponse tid3 authnresp -- @SF.Channel @TSFI.RESTfulAPI @S2 @S3 + -- Do not authenticate if SSO IdP response is for different team context "rejectsSAMLResponseInWrongTeam" $ do it "fails" $ do skipIdPAPIVersions @@ -420,6 +422,7 @@ specFinalizeLogin = do g "" = "" -- @SF.Channel @TSFI.RESTfulAPI @S2 @S3 + -- Do not authenticate if SSO IdP response is for unknown issuer it "rejectsSAMLResponseFromWrongIssuer" $ do let mkareq = negotiateAuthnRequest mkaresp privcreds idp spmeta authnreq = @@ -441,6 +444,7 @@ specFinalizeLogin = do -- @END -- @SF.Channel @TSFI.RESTfulAPI @S2 @S3 + -- Do not authenticate if SSO IdP response is signed with wrong key it "rejectsSAMLResponseSignedWithWrongKey" $ do (_, _, _, (_, badprivcreds)) <- registerTestIdPWithMeta let mkareq = negotiateAuthnRequest @@ -458,6 +462,7 @@ specFinalizeLogin = do -- @END -- @SF.Channel @TSFI.RESTfulAPI @S2 @S3 + -- Do not authenticate if SSO IdP response has no corresponding request anymore it "rejectsSAMLResponseIfRequestIsStale" $ do let mkareq idp = do req <- negotiateAuthnRequest idp @@ -474,6 +479,7 @@ specFinalizeLogin = do -- @END -- @SF.Channel @TSFI.RESTfulAPI @S2 @S3 + -- Do not authenticate if SSO IdP response is gone missing it "rejectsSAMLResponseIfResponseIsStale" $ do let mkareq = negotiateAuthnRequest mkaresp privcreds idp spmeta authnreq = mkAuthnResponse privcreds idp spmeta authnreq True diff --git a/services/spar/test-integration/Test/Spar/DataSpec.hs b/services/spar/test-integration/Test/Spar/DataSpec.hs index f12c1217198..1d0a0503a3e 100644 --- a/services/spar/test-integration/Test/Spar/DataSpec.hs +++ b/services/spar/test-integration/Test/Spar/DataSpec.hs @@ -292,7 +292,7 @@ testDeleteTeam = it "cleans up all the right tables after deletion" $ do mbUser1 <- case veidFromUserSSOId ssoid1 of Right veid -> runSpar $ - runValidExternalId + runValidExternalIdEither SAMLUserStore.get undefined -- could be @Data.lookupScimExternalId@, but we don't hit that path. veid @@ -302,7 +302,7 @@ testDeleteTeam = it "cleans up all the right tables after deletion" $ do mbUser2 <- case veidFromUserSSOId ssoid2 of Right veid -> runSpar $ - runValidExternalId + runValidExternalIdEither SAMLUserStore.get undefined veid diff --git a/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs b/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs index f66ec2296a4..56763e75194 100644 --- a/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs +++ b/services/spar/test-integration/Test/Spar/Scim/AuthSpec.hs @@ -28,12 +28,18 @@ where import Bilge import Bilge.Assert +import qualified Brig.Types.User as Brig import Cassandra as Cas import Control.Lens +import Data.Aeson (encode) import qualified Data.ByteString.Base64 as ES +import Data.ByteString.Conversion (toByteString') +import qualified Data.Code as Code import Data.Id (ScimTokenId, TeamId, UserId, randomId) import Data.Misc (PlainTextPassword (..)) +import Data.Range (unsafeRange) import Data.String.Conversions (cs) +import Data.Text.Ascii (AsciiChars (validate)) import Data.Time (UTCTime) import Data.Time.Clock (getCurrentTime) import qualified Galley.Types.Teams as Galley @@ -44,6 +50,8 @@ import qualified SAML2.WebSSO.Test.Util as SAML import Spar.Scim import Text.RawString.QQ (r) import Util +import qualified Wire.API.Team.Feature as Public +import qualified Wire.API.User as Public -- | Tests for authentication and operations with provisioning tokens ('ScimToken's). spec :: SpecWith TestEnv @@ -65,6 +73,7 @@ specCreateToken = describe "POST /auth-tokens" $ do it "requires the team to have no more than one IdP" testNumIdPs it "authorizes only admins and owners" testCreateTokenAuthorizesOnlyAdmins it "requires a password" testCreateTokenRequiresPassword + it "works with verification code" testCreateTokenWithVerificationCode -- FUTUREWORK: we should also test that for a password-less user, e.g. for an SSO user, -- reauthentication is not required. We currently (2019-03-05) can't test that because @@ -93,6 +102,58 @@ testCreateToken = do listUsers_ (Just token) (Just fltr) (env ^. teSpar) !!! const 200 === statusCode +testCreateTokenWithVerificationCode :: TestSpar () +testCreateTokenWithVerificationCode = do + env <- ask + (owner, teamId, _) <- registerTestIdP + unlockFeature (env ^. teGalley) teamId + setSndFactorPasswordChallengeStatus (env ^. teGalley) teamId Public.TeamFeatureEnabled + user <- getUserBrig owner + let email = fromMaybe undefined (Brig.userEmail =<< user) + + let reqMissingCode = CreateScimToken "testCreateToken" (Just defPassword) Nothing + createTokenFailsWith owner reqMissingCode 403 "code-authentication-required" + + requestVerificationCode (env ^. teBrig) email Public.CreateScimToken + let wrongCode = Code.Value $ unsafeRange (fromRight undefined (validate "123456")) + let reqWrongCode = CreateScimToken "testCreateToken" (Just defPassword) (Just wrongCode) + createTokenFailsWith owner reqWrongCode 403 "code-authentication-failed" + + requestVerificationCode (env ^. teBrig) email Public.CreateScimToken + code <- getVerificationCode (env ^. teBrig) owner Public.CreateScimToken + let reqWithCode = CreateScimToken "testCreateToken" (Just defPassword) (Just code) + CreateScimTokenResponse token _ <- createToken owner reqWithCode + + -- Try to do @GET /Users@ and check that it succeeds + let fltr = filterBy "externalId" "67c196a0-cd0e-11ea-93c7-ef550ee48502" + listUsers_ (Just token) (Just fltr) (env ^. teSpar) + !!! const 200 === statusCode + +unlockFeature :: GalleyReq -> TeamId -> TestSpar () +unlockFeature galley tid = + call $ put (galley . paths ["i", "teams", toByteString' tid, "features", toByteString' Public.TeamFeatureSndFactorPasswordChallenge, toByteString' Public.Unlocked]) !!! const 200 === statusCode + +setSndFactorPasswordChallengeStatus :: GalleyReq -> TeamId -> Public.TeamFeatureStatusValue -> TestSpar () +setSndFactorPasswordChallengeStatus galley tid status = do + let js = RequestBodyLBS $ encode $ Public.TeamFeatureStatusNoConfig status + call $ + put (galley . paths ["i", "teams", toByteString' tid, "features", toByteString' Public.TeamFeatureSndFactorPasswordChallenge] . contentJson . body js) + !!! const 200 === statusCode + +requestVerificationCode :: BrigReq -> Brig.Email -> Public.VerificationAction -> TestSpar () +requestVerificationCode brig email action = do + call $ + post (brig . paths ["verification-code", "send"] . contentJson . json (Public.SendVerificationCode action email)) + !!! const 200 === statusCode + +getVerificationCode :: BrigReq -> UserId -> Public.VerificationAction -> TestSpar Code.Value +getVerificationCode brig uid action = do + resp <- + call $ + get (brig . paths ["i", "users", toByteString' uid, "verification-code", toByteString' action]) + TestSpar (UserId, TeamId) @@ -208,7 +211,8 @@ specImportToScimFromInvitation = invite :: HasCallStack => UserId -> TeamId -> TestSpar (UserId, Email) invite owner teamid = do env <- ask - memberInvited <- call (inviteAndRegisterUser (env ^. teBrig) owner teamid) + email <- randomEmail + memberInvited <- call (inviteAndRegisterUser (env ^. teBrig) owner teamid email) let memberIdInvited = userId memberInvited emailInvited = maybe (error "must have email") id (userEmail memberInvited) pure (memberIdInvited, emailInvited) @@ -221,17 +225,19 @@ specImportToScimFromInvitation = idp <- call $ callIdpCreate apiVersion (env ^. teSpar) (Just userid) idpmeta pure (idp, privkey) - reProvisionWithScim :: HasCallStack => Bool -> Maybe (SAML.IdPConfig User.WireIdP) -> TeamId -> UserId -> ReaderT TestEnv IO () - reProvisionWithScim changeHandle mbidp teamid userid = do + reProvisionWithScim :: + HasCallStack => + Bool -> + Maybe (SAML.IdPConfig User.WireIdP) -> + TeamId -> + UserId -> + Email -> + TestSpar (Scim.UserC.StoredUser SparTag) + reProvisionWithScim changeHandle mbidp teamid userid email = do tok :: ScimToken <- do registerScimToken teamid ((^. SAML.idpId) <$> mbidp) - storedUserGot :: Scim.UserC.StoredUser SparTag <- do - resp <- - aFewTimes (getUser_ (Just tok) userid =<< view teSpar) ((== 200) . statusCode) - UserId -> TestSpar (Scim.UserC.StoredUser SparTag) + getStoredUser tok uid = do + resp <- aFewTimes (getUser_ (Just tok) uid =<< view teSpar) ((== 200) . statusCode) UserId -> Scim.User.User SparTag -> TestSpar (Scim.UserC.StoredUser SparTag) + putStoredUser tok uid usr' = do + resp <- + aFewTimes (updateUser_ (Just tok) (Just uid) usr' =<< view teSpar) ((== 200) . statusCode) + (SAML.IdPConfig User.WireIdP, SAML.SignPrivCreds) -> Email -> TestSpar () - signInWithSaml (idp, privCreds) email = do + signInWithSaml :: HasCallStack => (SAML.IdPConfig User.WireIdP, SAML.SignPrivCreds) -> Email -> UserId -> TestSpar () + signInWithSaml (idp, privCreds) email userid = do let uref = SAML.UserRef tenant subj subj = emailToSAMLNameID email tenant = idp ^. SAML.idpMetadata . SAML.edIssuer - void $ createViaSaml idp privCreds uref + mbUid <- createViaSaml idp privCreds uref + liftIO $ mbUid `shouldBe` Just userid + + checkCsvDownload :: + HasCallStack => + UserId -> + TeamId -> + SAML.IdPConfig User.WireIdP -> + Scim.UserC.StoredUser SparTag -> + TestSpar () + checkCsvDownload ownerId teamId idp storedUsr = do + g <- view teGalley + resp <- + call $ + get (g . accept "text/csv" . paths ["teams", toByteString' teamId, "members/csv"] . zUser ownerId) SpecWith TestEnv check changeHandle = it (show changeHandle) $ do (ownerid, teamid) <- createTeam (userid, email) <- invite ownerid teamid - idp <- addSamlIdP ownerid - reProvisionWithScim changeHandle (Just $ fst idp) teamid userid - signInWithSaml idp email + (idp, privcreds) <- addSamlIdP ownerid + storedusr <- reProvisionWithScim changeHandle (Just idp) teamid userid email + signInWithSaml (idp, privcreds) email userid + checkCsvDownload ownerid teamid idp storedusr + +findUserByEmail :: ScimToken -> Email -> TestSpar (Scim.UserC.StoredUser SparTag) +findUserByEmail tok email = do + let fltr = filterBy "externalid" (fromEmail email) + resp <- listUsers_ (Just tok) (Just fltr) =<< view teSpar + let users :: Scim.ListResponse (Scim.UserC.StoredUser SparTag) = responseJsonUnsafe resp + case Scim.resources users of + [fstUser] -> pure fstUser + _ -> error "expected exactly one user" assertSparCassandraUref :: HasCallStack => (SAML.UserRef, Maybe UserId) -> TestSpar () assertSparCassandraUref (uref, urefAnswer) = do @@ -464,11 +527,11 @@ testCsvData tid owner uid mbeid mbsaml hasissuer = do Just (UserScimExternalId _) -> "" Nothing -> "" ('n', CsvExport.tExportSAMLNamedId export) `shouldBe` ('n', haveSubject) - where - decodeCSV :: Csv.FromNamedRecord a => LByteString -> [a] - decodeCSV bstr = - either (error "could not decode csv") id $ - Csv.decodeByName bstr <&> (V.toList . snd) + +decodeCSV :: Csv.FromNamedRecord a => LByteString -> [a] +decodeCSV bstr = + either (error "could not decode csv") id $ + Csv.decodeByName bstr <&> (V.toList . snd) testCreateUserWithPass :: TestSpar () testCreateUserWithPass = do @@ -1045,7 +1108,7 @@ testFindSamlAutoProvisionedUserMigratedWithEmailInTeamWithSSO = do where veidToText :: MonadError String m => ValidExternalId -> m Text veidToText veid = - runValidExternalId + runValidExternalIdEither (\(SAML.UserRef _ subj) -> maybe (throwError "bad uref from brig") (pure . CI.original) $ SAML.shortShowNameID subj) (pure . fromEmail) veid @@ -1055,7 +1118,8 @@ testFindTeamSettingsInvitedUserMigratedWithEmailInTeamWithSSO = do env <- ask (tok, (owner, teamid, _idp)) <- registerIdPAndScimToken - memberInvited <- call (inviteAndRegisterUser (env ^. teBrig) owner teamid) + email <- randomEmail + memberInvited <- call (inviteAndRegisterUser (env ^. teBrig) owner teamid email) let emailInvited = maybe (error "must have email") fromEmail (userEmail memberInvited) memberIdInvited = userId memberInvited @@ -1069,7 +1133,8 @@ testFindTeamSettingsInvitedUserMigratedWithEmailInTeamWithSSOViaUserId = do env <- ask (tok, (owner, teamid, _idp)) <- registerIdPAndScimToken - memberInvited <- call (inviteAndRegisterUser (env ^. teBrig) owner teamid) + email <- randomEmail + memberInvited <- call (inviteAndRegisterUser (env ^. teBrig) owner teamid email) let memberIdInvited = userId memberInvited _ <- getUser tok memberIdInvited @@ -1090,11 +1155,11 @@ testFindNonProvisionedUserNoIdP findBy = do (owner, teamid) <- call $ createUserWithTeam (env ^. teBrig) (env ^. teGalley) tok <- registerScimToken teamid Nothing - uid <- userId <$> call (inviteAndRegisterUser (env ^. teBrig) owner teamid) + email <- randomEmail + uid <- userId <$> call (inviteAndRegisterUser (env ^. teBrig) owner teamid email) handle <- nextHandle runSpar $ BrigAccess.setHandle uid handle Just brigUser <- runSpar $ Intra.getBrigUser Intra.NoPendingInvitations uid - let Just email = userEmail brigUser do -- inspect brig user @@ -1110,7 +1175,7 @@ testFindNonProvisionedUserNoIdP findBy = do liftIO $ users `shouldBe` [uid] Just brigUser' <- runSpar $ Intra.getBrigUser Intra.NoPendingInvitations uid liftIO $ userManagedBy brigUser' `shouldBe` ManagedByScim - liftIO $ brigUser' `shouldBe` brigUser {userManagedBy = ManagedByScim} + liftIO $ brigUser' `shouldBe` scimifyBrigUserHack brigUser email -- | Test that deleted users are not listed. testListNoDeletedUsers :: TestSpar () @@ -1122,6 +1187,8 @@ testListNoDeletedUsers = do let userid = scimUserId storedUser -- Delete the user _ <- deleteUser tok userid + -- Make sure it is deleted in brig before pulling via SCIM (which would recreate it!) + Nothing <- aFewTimes (runSpar (Intra.getBrigUser Intra.WithPendingInvitations userid)) isNothing -- Get all users users <- listUsers tok (Just (filterForStoredUser storedUser)) -- Check that the user is absent @@ -1216,7 +1283,8 @@ testGetNonScimInviteUser = do env <- ask (tok, (owner, tid, _)) <- registerIdPAndScimToken - uidNoSso <- userId <$> call (inviteAndRegisterUser (env ^. teBrig) owner tid) + email <- randomEmail + uidNoSso <- userId <$> call (inviteAndRegisterUser (env ^. teBrig) owner tid email) shouldBeManagedBy uidNoSso ManagedByWire getUser_ (Just tok) uidNoSso (env ^. teSpar) !!! const 200 === statusCode @@ -1233,11 +1301,12 @@ testGetNonScimInviteUserNoIdP = do (owner, tid) <- call $ createUserWithTeam (env ^. teBrig) (env ^. teGalley) tok <- registerScimToken tid Nothing - uidNoSso <- userId <$> call (inviteAndRegisterUser (env ^. teBrig) owner tid) + email <- randomEmail + user <- call (inviteAndRegisterUser (env ^. teBrig) owner tid email) - shouldBeManagedBy uidNoSso ManagedByWire - getUser_ (Just tok) uidNoSso (env ^. teSpar) !!! const 200 === statusCode - shouldBeManagedBy uidNoSso ManagedByScim + shouldBeManagedBy (userId user) ManagedByWire + void $ findUserByEmail tok email + shouldBeManagedBy (userId user) ManagedByScim testGetUserWithNoHandle :: TestSpar () testGetUserWithNoHandle = do @@ -1524,7 +1593,7 @@ testUpdateExternalId withidp = do lookupByValidExternalId :: ValidExternalId -> TestSpar (Maybe UserId) lookupByValidExternalId = - runValidExternalId + runValidExternalIdEither (runSpar . SAMLUserStore.get) ( \email -> do let action = SU.scimFindUserByEmail midp tid $ fromEmail email @@ -1732,7 +1801,7 @@ specDeleteUser = do usr <- runSpar $ Intra.getBrigUser Intra.WithPendingInvitations uid let err = error . ("brig user without UserRef: " <>) . show case (`Intra.veidFromBrigUser` Nothing) <$> usr of - bad@(Just (Right veid)) -> runValidExternalId pure (const $ err bad) veid + bad@(Just (Right veid)) -> runValidExternalIdEither pure (const $ err bad) veid bad -> err bad spar <- view teSpar deleteUser_ (Just tok) (Just uid) spar @@ -1842,7 +1911,8 @@ specDeleteUser = do (owner, tid) <- call $ createUserWithTeam brig galley tok <- registerScimToken tid Nothing - uid <- userId <$> call (inviteAndRegisterUser brig owner tid) + email <- randomEmail + uid <- userId <$> call (inviteAndRegisterUser brig owner tid email) aFewTimes (getUser_ (Just tok) uid spar) ((== 200) . statusCode) !!! const 200 === statusCode diff --git a/services/spar/test-integration/Util/Core.hs b/services/spar/test-integration/Util/Core.hs index 1ef07088d7e..151045517a1 100644 --- a/services/spar/test-integration/Util/Core.hs +++ b/services/spar/test-integration/Util/Core.hs @@ -138,7 +138,7 @@ import qualified Bilge import Bilge.Assert (Assertions, (!!!), ( UserId -> TeamId -> + Email -> m User -inviteAndRegisterUser brig u tid = do - inviteeEmail <- randomEmail +inviteAndRegisterUser brig u tid inviteeEmail = do let invite = stdInvitationRequest inviteeEmail inv <- responseJsonError =<< postInvitation tid u invite Just inviteeCode <- getInvitationCode tid (TeamInvitation.inInvitation inv) @@ -622,7 +623,7 @@ zAuthAccess :: UserId -> SBS -> Request -> Request zAuthAccess u c = header "Z-Type" "access" . zUser u . zConn c newTeam :: Galley.BindingNewTeam -newTeam = Galley.BindingNewTeam $ Galley.newNewTeam (unsafeRange "teamName") (unsafeRange "defaultIcon") +newTeam = Galley.BindingNewTeam $ Galley.newNewTeam (unsafeRange "teamName") DefaultIcon randomEmail :: MonadIO m => m Brig.Email randomEmail = do @@ -1217,7 +1218,7 @@ ssoToUidSpar :: (HasCallStack, MonadIO m, MonadReader TestEnv m) => TeamId -> Br ssoToUidSpar tid ssoid = do veid <- either (error . ("could not parse brig sso_id: " <>)) pure $ Intra.veidFromUserSSOId ssoid runSpar $ - runValidExternalId + runValidExternalIdEither SAMLUserStore.get (ScimExternalIdStore.lookup tid) veid diff --git a/services/spar/test-integration/Util/Scim.hs b/services/spar/test-integration/Util/Scim.hs index 2078964ae55..b74a9406ac8 100644 --- a/services/spar/test-integration/Util/Scim.hs +++ b/services/spar/test-integration/Util/Scim.hs @@ -25,13 +25,16 @@ import Brig.Types.User import Control.Lens import Control.Monad.Random import Data.ByteString.Conversion +import qualified Data.ByteString.Lazy as Lazy import Data.Handle (Handle (Handle)) import Data.Id import Data.String.Conversions (cs) +import qualified Data.Text.Lazy as Lazy import Data.Time import Data.UUID as UUID import Data.UUID.V4 as UUID import Imports +import qualified Network.Wai.Utilities as Error import qualified SAML2.WebSSO as SAML import SAML2.WebSSO.Types (IdPId, idpId) import qualified Spar.Intra.BrigApp as Intra @@ -294,6 +297,24 @@ createToken zusr payload = do + UserId -> + CreateScimToken -> + Int -> + Lazy.Text -> + TestSpar () +createTokenFailsWith zusr payload expectedStatus expectedLabel = do + env <- ask + void $ + createToken_ zusr payload (env ^. teSpar) Maybe Lazy.Text +errorLabel = fmap Error.label . responseJsonMaybe + -- | Delete a SCIM token. deleteToken :: HasCallStack => @@ -546,7 +567,7 @@ instance IsUser ValidScimUser where maybeName = Just (Just . view vsuName) maybeTenant = Just (^? (vsuExternalId . veidUref . SAML.uidTenant)) maybeSubject = Just (^? (vsuExternalId . veidUref . SAML.uidSubject)) - maybeScimExternalId = Just (runValidExternalId Intra.urefToExternalId (Just . fromEmail) . view vsuExternalId) + maybeScimExternalId = Just (runValidExternalIdEither Intra.urefToExternalId (Just . fromEmail) . view vsuExternalId) instance IsUser (WrappedScimStoredUser SparTag) where maybeUserId = Just $ scimUserId . fromWrappedScimStoredUser @@ -585,7 +606,7 @@ instance IsUser User where Intra.veidFromBrigUser usr Nothing & either (const Nothing) - (runValidExternalId Intra.urefToExternalId (Just . fromEmail)) + (runValidExternalIdEither Intra.urefToExternalId (Just . fromEmail)) -- | For all properties that are present in both @u1@ and @u2@, check that they match. -- @@ -625,3 +646,11 @@ whatSparReturnsFor :: HasCallStack => IdP -> Int -> Scim.User.User SparTag -> Ei whatSparReturnsFor idp richInfoSizeLimit = either (Left . show) (Right . synthesizeScimUser) . validateScimUser' "whatSparReturnsFor" (Just idp) richInfoSizeLimit + +-- this is not always correct, but hopefully for the tests that we're using it in it'll do. +scimifyBrigUserHack :: User -> Email -> User +scimifyBrigUserHack usr email = + usr + { userManagedBy = ManagedByScim, + userIdentity = Just (SSOIdentity (UserScimExternalId (fromEmail email)) (Just email) Nothing) + } diff --git a/stack.yaml b/stack.yaml index 8abd0c00e60..eff4883028f 100644 --- a/stack.yaml +++ b/stack.yaml @@ -55,6 +55,7 @@ packages: - services/spar - tools/api-simulations - tools/bonanza +- tools/db/assets - tools/db/auto-whitelist - tools/db/migrate-sso-feature-flag - tools/db/service-backfill diff --git a/tools/db/assets/README.md b/tools/db/assets/README.md new file mode 100644 index 00000000000..2f01092c666 --- /dev/null +++ b/tools/db/assets/README.md @@ -0,0 +1,3 @@ +# Asset Key Validation + +`assets` is a small tool that scans the brig user table, searches for mal-formatted asset keys and prints them. diff --git a/tools/db/assets/app/Main.hs b/tools/db/assets/app/Main.hs new file mode 100644 index 00000000000..d0ea6c3e4e7 --- /dev/null +++ b/tools/db/assets/app/Main.hs @@ -0,0 +1,23 @@ +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 Wire Swiss GmbH +-- +-- This program is free software: you can redistribute it and/or modify it under +-- the terms of the GNU Affero General Public License as published by the Free +-- Software Foundation, either version 3 of the License, or (at your option) any +-- later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Affero General Public License along +-- with this program. If not, see . + +module Main where + +import qualified Assets.Lib as Lib + +main :: IO () +main = Lib.main diff --git a/tools/db/assets/assets.cabal b/tools/db/assets/assets.cabal new file mode 100644 index 00000000000..f4a8fc1ef0b --- /dev/null +++ b/tools/db/assets/assets.cabal @@ -0,0 +1,93 @@ +cabal-version: 2.4 +name: assets +version: 1.0.0 +synopsis: Scan the brig user table, search for malformatted asset keys and print them +category: Network +author: Wire Swiss GmbH +maintainer: Wire Swiss GmbH +copyright: (c) 2022 Wire Swiss GmbH +license: AGPL-3.0-only +build-type: Simple + +library + hs-source-dirs: src + exposed-modules: + Assets.Lib + ghc-options: -O2 -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wpartial-fields -fwarn-tabs -optP-Wno-nonportable-include-path -funbox-strict-fields -threaded -with-rtsopts=-N + build-depends: + aeson + , base + , brig + , brig-types + , bytestring + , cassandra-util + , conduit + , containers + , filepath + , galley + , imports + , iproute + , lens + , megaparsec + , optparse-applicative + , process + , raw-strings-qq + , stache + , bytestring-conversion + , text + , time + , tinylog + , types-common + , uuid + , vector + , wire-api + , attoparsec + default-extensions: + AllowAmbiguousTypes + BangPatterns + ConstraintKinds + DataKinds + DefaultSignatures + DerivingStrategies + DerivingVia + DeriveFunctor + DeriveGeneric + DeriveLift + DeriveTraversable + EmptyCase + FlexibleContexts + FlexibleInstances + FunctionalDependencies + GADTs + InstanceSigs + KindSignatures + LambdaCase + MultiParamTypeClasses + MultiWayIf + NamedFieldPuns + NoImplicitPrelude + OverloadedStrings + PackageImports + PatternSynonyms + PolyKinds + QuasiQuotes + RankNTypes + ScopedTypeVariables + StandaloneDeriving + TemplateHaskell + TupleSections + TypeApplications + TypeFamilies + TypeFamilyDependencies + TypeOperators + UndecidableInstances + ViewPatterns + +executable assets + main-is: Main.hs + build-depends: + base + , assets + hs-source-dirs: app + default-language: Haskell2010 + ghc-options: -O2 -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wpartial-fields -fwarn-tabs -optP-Wno-nonportable-include-path -funbox-strict-fields -threaded -with-rtsopts=-N diff --git a/tools/db/assets/src/Assets/Lib.hs b/tools/db/assets/src/Assets/Lib.hs new file mode 100644 index 00000000000..7c018cea282 --- /dev/null +++ b/tools/db/assets/src/Assets/Lib.hs @@ -0,0 +1,192 @@ +{-# LANGUAGE RecordWildCards #-} + +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 Wire Swiss GmbH +-- +-- This program is free software: you can redistribute it and/or modify it under +-- the terms of the GNU Affero General Public License as published by the Free +-- Software Foundation, either version 3 of the License, or (at your option) any +-- later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Affero General Public License along +-- with this program. If not, see . + +module Assets.Lib where + +import Cassandra as C +import Cassandra.Settings as C +import Control.Lens +import qualified Data.Attoparsec.ByteString.Char8 as Atto (Parser) +import Data.ByteString.Conversion +import Data.Conduit +import qualified Data.Conduit.Combinators as Conduit +import Data.Id (UserId) +import qualified Data.Text.Encoding as T +import Data.Text.Strict.Lens +import Imports +import Options.Applicative +import System.IO (hPutStr) +import qualified System.Logger as Log +import Wire.API.Asset (AssetKey) + +data Opts = Opts + { cHost :: String, + cPort :: Int, + cKeyspace :: C.Keyspace + } + +sampleParser :: Parser Opts +sampleParser = + Opts + <$> strOption + ( long "cassandra-host" + <> short 's' + <> metavar "HOST" + <> help "Cassandra Host" + <> value "localhost" + <> showDefault + ) + <*> option + auto + ( long "cassandra-port" + <> short 'p' + <> metavar "PORT" + <> help "Cassandra Port" + <> value 9042 + <> showDefault + ) + <*> ( C.Keyspace . view packed + <$> strOption + ( long "cassandra-keyspace" + <> short 'k' + <> metavar "STRING" + <> help "Cassandra Keyspace" + <> value "brig_test" + <> showDefault + ) + ) + +main :: IO () +main = do + putStrLn "starting to read users ..." + opts <- execParser (info (helper <*> sampleParser) desc) + logger <- initLogger + client <- initCas opts logger + res <- process client + putStrLn "\n" + print res + where + initLogger = Log.new . Log.setOutput Log.StdOut . Log.setFormat Nothing . Log.setBufSize 0 $ Log.defSettings + initCas Opts {..} l = + C.init + . C.setLogger (C.mkLogger l) + . C.setContacts cHost [] + . C.setPortNumber (fromIntegral cPort) + . C.setKeyspace cKeyspace + . C.setProtocolVersion C.V4 + $ C.defSettings + desc = header "assets" <> progDesc "find invalid asset keys in cassandra brig" <> fullDesc + +selectUsersAll :: C.PrepQuery C.R () UserRow +selectUsersAll = "SELECT id, assets FROM user" + +readUsers :: ClientState -> ConduitM () [UserRow] IO () +readUsers client = + transPipe (runClient client) $ + paginateC selectUsersAll (paramsP LocalQuorum () 500) x5 + +process :: ClientState -> IO Result +process client = + runConduit $ + readUsers client + .| Conduit.mapM (\chunk -> hPutStr stderr "." $> chunk) + .| Conduit.concat + .| Conduit.foldMap checkAssets + where + isInvalid :: AssetText -> Bool + isInvalid asset = isLeft $ runParser (parser :: Atto.Parser AssetKey) $ T.encodeUtf8 (txtAssetKey asset) + + checkAssets :: UserRow -> Result + checkAssets (_, Nothing) = Result 1 0 [] + checkAssets (_, Just []) = Result 1 0 [] + checkAssets row@(_, Just assets) = if any isInvalid assets then Result 0 0 [row] else Result 0 1 [] + +type UserRow = (UserId, Maybe [AssetText]) + +data Result = Result + { noAsset :: Int, + validAsset :: Int, + invalidAsset :: [UserRow] + } + deriving stock (Eq, Generic) + +newtype AssetText = ImageAssetText + { txtAssetKey :: Text + } + deriving stock (Eq, Generic) + +instance Show AssetText where + show (ImageAssetText ak) = show ak + +instance Cql AssetText where + ctype = + Tagged + ( UdtColumn + "asset" + [ ("typ", IntColumn), + ("key", TextColumn) + ] + ) + + fromCql (CqlUdt fs) = do + t <- required "typ" + k <- required "key" + case (t :: Int32) of + 0 -> return $! ImageAssetText k + _ -> Left $ "unexpected user asset type: " ++ show t + where + required :: Cql r => Text -> Either String r + required f = + maybe + (Left ("Asset: Missing required field '" ++ show f ++ "'")) + fromCql + (lookup f fs) + fromCql _ = Left "UserAsset: UDT expected" + + -- Note: Order must match up with the 'ctype' definition. + toCql (ImageAssetText k) = + CqlUdt + [ ("typ", CqlInt 0), + ("key", toCql k) + ] + +instance Show Result where + show (Result n v i) = + "num_no_assets: " + <> show n + <> "\nnum_valid_assets: " + <> show v + <> "\nnum_invalid_assets: " + <> show (length i) + <> "\ninvalid_assets:\n" + <> concat (showRow <$> i) + where + showRow (uid, Nothing) = " - user_id: " <> show uid <> "\n" + showRow (uid, Just as) = " - user_id: " <> show uid <> "\n" <> showAssets as + showAsset a = " key: " <> show (txtAssetKey a) <> "\n" + showAssets assets = concat $ showAsset <$> assets + +instance Semigroup Result where + (<>) (Result n1 v1 i1) (Result n2 v2 i2) = + Result (n1 + n2) (v1 + v2) (i1 <> i2) + +instance Monoid Result where + mempty = Result 0 0 [] + mappend (Result n1 v1 i1) (Result n2 v2 i2) = + Result (n1 + n2) (v1 + v2) (i1 <> i2)